Merge branch 'java8' into 'master'

update to Java8 and compileSdkVersion 29

See merge request fdroid/fdroidclient!974
This commit is contained in:
Hans-Christoph Steiner 2021-02-23 19:32:36 +00:00
commit b21dbb8646
19 changed files with 184 additions and 67 deletions

View File

@ -27,6 +27,7 @@ stages:
artifacts:
name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}"
paths:
- kernel.log
- logcat.txt
- app/core*
- app/*.log
@ -70,6 +71,10 @@ errorprone:
- cat config/errorprone.gradle >> app/build.gradle
- ./gradlew -Dorg.gradle.dependency.verification=lenient assembleDebug
# Run the tests in the emulator. Each step is broken out to run on
# its own since the CI runner can have limited RAM, and the emulator
# can take a while to start.
#
# once these prove stable, the task should be switched to
# connectedCheck to test all the build flavors
.connected-template: &connected-template
@ -90,16 +95,14 @@ errorprone:
- wait-for-emulator
- adb devices
- adb shell input keyevent 82 &
- ./gradlew installFullDebug
- adb shell am start -n org.fdroid.fdroid.debug/org.fdroid.fdroid.views.main.MainActivity
- if [ $AVD_SDK -lt 25 ] || ! emulator -accel-check; then
export FLAG=-Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest;
fi
- ./gradlew connectedFullDebugAndroidTest $FLAG
|| ./gradlew connectedFullDebugAndroidTest $FLAG
|| ./gradlew connectedFullDebugAndroidTest $FLAG
|| (adb -e logcat -d > logcat.txt; exit 1)
connected 22 default armeabi-v7a:
retry: 1
no-accel 22 default x86:
<<: *test-template
<<: *connected-template
@ -107,15 +110,14 @@ connected 22 default armeabi-v7a:
tags:
- fdroid
- kvm
allow_failure: true
only:
variables:
- $RUN_KVM_JOBS
<<: *test-template
<<: *connected-template
connected 26 google_apis x86:
kvm 29 microg x86_64:
<<: *kvm-template
only:
- branches@fdroid/fdroidclient
- branches@eighthave/fdroidclient
deploy_nightly:
extends: .base

View File

@ -21,7 +21,7 @@ def basicApplicationId = "org.fdroid.basic"
def privilegedExtensionApplicationId = '"org.fdroid.fdroid.privileged"'
android {
compileSdkVersion 28
compileSdkVersion 29
defaultConfig {
versionCode 1012000
@ -75,6 +75,8 @@ android {
compileOptions {
compileOptions.encoding = "UTF-8"
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
aaptOptions {

View File

@ -4,13 +4,11 @@
-keep class org.fdroid.fdroid.** {*;}
-dontskipnonpubliclibraryclassmembers
-dontwarn android.test.**
-dontwarn com.android.support.test.**
-dontwarn javax.naming.**
-dontwarn org.slf4j.**
-dontnote org.apache.http.**
-dontnote android.net.http.**
-dontnote android.support.**
-dontnote **ILicensingService
# Needed for espresso https://stackoverflow.com/a/21706087
@ -49,3 +47,8 @@ public static final org.codehaus.jackson.annotate.JsonAutoDetect$Visibility *; }
-keep public class your.class.** {
*;
}
# This is necessary so that RemoteWorkManager can be initialized (also marked with @Keep)
-keep class androidx.work.multiprocess.RemoteWorkManagerClient {
public <init>(...);
}

View File

@ -177,6 +177,7 @@ public class MainActivityEspressoTest {
}
@LargeTest
@Test
public void showSettings() {
ViewInteraction settingsBottonNavButton = onView(
allOf(withText(R.string.menu_settings), isDisplayed()));
@ -211,6 +212,7 @@ public class MainActivityEspressoTest {
}
@LargeTest
@Test
public void showUpdates() {
ViewInteraction updatesBottonNavButton = onView(allOf(withText(R.string.main_menu__updates), isDisplayed()));
updatesBottonNavButton.perform(click());
@ -218,6 +220,7 @@ public class MainActivityEspressoTest {
}
@LargeTest
@Test
public void startSwap() {
if (!BuildConfig.FLAVOR.startsWith("full")) {
return;
@ -233,6 +236,7 @@ public class MainActivityEspressoTest {
}
@LargeTest
@Test
public void showCategories() {
if (!BuildConfig.FLAVOR.startsWith("full")) {
return;
@ -258,6 +262,7 @@ public class MainActivityEspressoTest {
}
@LargeTest
@Test
public void showLatest() {
if (!BuildConfig.FLAVOR.startsWith("full")) {
return;
@ -280,6 +285,7 @@ public class MainActivityEspressoTest {
}
@LargeTest
@Test
public void showSearch() {
onView(allOf(withText(R.string.menu_settings), isDisplayed())).perform(click());
onView(withId(R.id.fab_search)).check(doesNotExist());

View File

@ -17,14 +17,13 @@ import android.os.AsyncTask;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.app.ServiceCompat;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import cc.mvdan.accesspoint.WifiApControl;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.NotificationHelper;
import org.fdroid.fdroid.Preferences;
@ -48,8 +47,6 @@ import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import cc.mvdan.accesspoint.WifiApControl;
/**
* Central service which manages all of the different moving parts of swap which are required
* to enable p2p swapping of apps.
@ -429,7 +426,9 @@ public class SwapService extends Service {
localBroadcastManager.unregisterReceiver(bonjourPeerFound);
localBroadcastManager.unregisterReceiver(bonjourPeerRemoved);
unregisterReceiver(bluetoothScanModeChanged);
if (bluetoothAdapter != null) {
unregisterReceiver(bluetoothScanModeChanged);
}
BluetoothManager.stop(this);

View File

@ -18,7 +18,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/both_parties_need_fdroid_text"
android:layout_marginTop="36dp"
android:tint="?attr/mainTabSwapSplashTint"
app:tint="?attr/mainTabSwapSplashTint"
android:scaleType="fitXY"
tools:targetApi="jelly_bean"/>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="?attr/listPreferredItemHeight"
@ -20,7 +21,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/circle"
android:tint="@color/swap_light_grey_icon"/>
app:tint="@color/swap_light_grey_icon"/>
<ImageView
android:id="@+id/icon"

View File

@ -266,8 +266,11 @@
android:name=".AddRepoIntentService"
android:exported="false"/>
<!-- Warning: Please add all new services to HidingManager -->
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
android:exported="false"
tools:node="remove" />
<activity
android:name=".views.main.MainActivity"

View File

@ -46,11 +46,10 @@ import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.LongSparseArray;
import androidx.core.content.ContextCompat;
import com.nostra13.universalimageloader.cache.disc.DiskCache;
import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache;
import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache;
@ -58,7 +57,8 @@ import com.nostra13.universalimageloader.core.DefaultConfigurationFactory;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.process.BitmapProcessor;
import info.guardianproject.netcipher.NetCipher;
import info.guardianproject.netcipher.proxy.OrbotHelper;
import org.acra.ACRA;
import org.acra.ReportField;
import org.acra.ReportingInteractionMode;
@ -82,17 +82,13 @@ import org.fdroid.fdroid.net.ImageLoaderForUIL;
import org.fdroid.fdroid.panic.HidingManager;
import org.fdroid.fdroid.work.CleanCacheWorker;
import javax.microedition.khronos.opengles.GL10;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.Security;
import java.util.List;
import java.util.UUID;
import javax.microedition.khronos.opengles.GL10;
import info.guardianproject.netcipher.NetCipher;
import info.guardianproject.netcipher.proxy.OrbotHelper;
@ReportsCrashes(mailTo = BuildConfig.ACRA_REPORT_EMAIL,
mode = ReportingInteractionMode.DIALOG,
reportDialogClass = org.fdroid.fdroid.acra.CrashReportActivity.class,
@ -113,7 +109,7 @@ import info.guardianproject.netcipher.proxy.OrbotHelper;
ReportField.STACK_TRACE,
}
)
public class FDroidApp extends Application {
public class FDroidApp extends Application implements androidx.work.Configuration.Provider {
private static final String TAG = "FDroidApp";
private static final String ACRA_ID = BuildConfig.APPLICATION_ID + ":acra";
@ -654,7 +650,7 @@ public class FDroidApp extends Application {
/**
* Put proxy settings (or Tor settings) globally into effect based on whats configured in Preferences.
*
* <p>
* Must be called on App startup and after every proxy configuration change.
*/
public static void configureProxy(Preferences preferences) {
@ -676,4 +672,26 @@ public class FDroidApp extends Application {
public static Context getInstance() {
return instance;
}
/**
* Set up WorkManager on demand to avoid slowing down starts.
*
* @see CleanCacheWorker
* @see org.fdroid.fdroid.work.PopularityContestWorker
* @see org.fdroid.fdroid.work.UpdateWorker
* @see <a href="https://developer.android.com/codelabs/android-adv-workmanager#3">example</a>
*/
@NonNull
@Override
public androidx.work.Configuration getWorkManagerConfiguration() {
if (BuildConfig.DEBUG) {
return new androidx.work.Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.build();
} else {
return new androidx.work.Configuration.Builder()
.setMinimumLoggingLevel(Log.ERROR)
.build();
}
}
}

View File

@ -33,10 +33,6 @@ import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.StatFs;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.text.Editable;
import android.text.Html;
import android.text.SpannableStringBuilder;
@ -52,6 +48,10 @@ import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
@ -406,10 +406,26 @@ public final class Utils {
return ret;
}
/**
* Get the fingerprint used to represent an APK signing key in F-Droid.
* This is a custom fingerprint algorithm that was kind of accidentally
* created, but is still in use.
*
* @see #getPackageSig(PackageInfo)
* @see org.fdroid.fdroid.data.Apk#sig
*/
public static String getsig(byte[] rawCertBytes) {
return Utils.hashBytes(toHexString(rawCertBytes).getBytes(), "md5");
}
/**
* Get the fingerprint used to represent an APK signing key in F-Droid.
* This is a custom fingerprint algorithm that was kind of accidentally
* created, but is still in use.
*
* @see #getsig(byte[])
* @see org.fdroid.fdroid.data.Apk#sig
*/
public static String getPackageSig(PackageInfo info) {
if (info == null || info.signatures == null || info.signatures.length < 1) {
@ -556,7 +572,7 @@ public final class Utils {
}
byte[] mdbytes = md.digest();
return toHexString(mdbytes).toLowerCase(Locale.ENGLISH);
return toHexString(mdbytes);
} catch (IOException e) {
String message = e.getMessage();
if (message.contains("read failed: EIO (I/O error)")) {
@ -576,7 +592,7 @@ public final class Utils {
* Computes the base 16 representation of the byte array argument.
*
* @param bytes an array of bytes.
* @return the bytes represented as a string of hexadecimal digits.
* @return the bytes represented as a string of lowercase hexadecimal digits.
* @see <a href="https://stackoverflow.com/a/9855338">source</a>
*/
public static String toHexString(byte[] bytes) {
@ -589,7 +605,7 @@ public final class Utils {
return new String(hexChars);
}
private static final char[] HEX_LOOKUP_ARRAY = "0123456789ABCDEF".toCharArray();
private static final char[] HEX_LOOKUP_ARRAY = "0123456789abcdef".toCharArray();
public static int parseInt(String str, int fallback) {
if (str == null || str.length() == 0) {

View File

@ -949,24 +949,7 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
}
apkJar.close();
/*
* I don't fully understand the loop used here. I've copied it verbatim
* from getsig.java bundled with FDroidServer. I *believe* it is taking
* the raw byte encoding of the certificate & converting it to a byte
* array of the hex representation of the original certificate byte
* array. This is then MD5 sum'd. It's a really bad way to be doing this
* if I'm right... If I'm not right, I really don't know! see lines
* 67->75 in getsig.java bundled with Fdroidserver
*/
final byte[] fdroidSig = new byte[rawCertBytes.length * 2];
for (int j = 0; j < rawCertBytes.length; j++) {
byte v = rawCertBytes[j];
int d = (v >> 4) & 0xF;
fdroidSig[j * 2] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
d = v & 0xF;
fdroidSig[j * 2 + 1] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
}
apk.sig = Utils.hashBytes(fdroidSig, "md5");
apk.sig = Utils.getsig(rawCertBytes);
}
/**

View File

@ -37,7 +37,6 @@ import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
@ -45,11 +44,9 @@ import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.ashokvarma.bottomnavigation.BottomNavigationBar;
import com.ashokvarma.bottomnavigation.BottomNavigationItem;
import com.ashokvarma.bottomnavigation.TextBadgeItem;
import org.fdroid.fdroid.AppUpdateStatusManager;
import org.fdroid.fdroid.AppUpdateStatusManager.AppUpdateStatus;
import org.fdroid.fdroid.BuildConfig;
@ -305,6 +302,11 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB
}
/**
* Since any app could send this {@link Intent}, and the search terms are
* fed into a SQL query, the data must be strictly sanitized to avoid
* SQL injection attacks.
*/
private void handleSearchOrAppViewIntent(Intent intent) {
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
@ -391,6 +393,8 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB
}
if (!TextUtils.isEmpty(packageName)) {
// sanitize packageName to be a valid Java packageName and prevent exploits
packageName = packageName.replaceAll("[^A-Za-z\\d_.]", "");
Utils.debugLog(TAG, "FDroid launched via app link for '" + packageName + "'");
Intent intentToInvoke = new Intent(this, AppDetailsActivity.class);
intentToInvoke.putExtra(AppDetailsActivity.EXTRA_APPID, packageName);
@ -402,12 +406,19 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB
}
}
/**
* These strings might end up in a SQL query, so strip all non-alpha-num
*/
static String sanitizeSearchTerms(String query) {
return query.replaceAll("[^\\p{L}\\d_ -]", " ");
}
/**
* Initiates the {@link AppListActivity} with the relevant search terms passed in via the query arg.
*/
private void performSearch(String query) {
Intent searchIntent = new Intent(this, AppListActivity.class);
searchIntent.putExtra(AppListActivity.EXTRA_SEARCH_TERMS, query);
searchIntent.putExtra(AppListActivity.EXTRA_SEARCH_TERMS, sanitizeSearchTerms(query));
startActivity(searchIntent);
}

View File

@ -20,6 +20,7 @@
<view class="org.fdroid.fdroid.privileged.views.AppSecurityPermissions$PermissionItemView"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
@ -34,7 +35,7 @@
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"
android:scaleType="fitCenter"
android:tint="@android:color/black" />
app:tint="@android:color/black" />
<ImageView
android:layout_width="wrap_content"

View File

@ -20,6 +20,7 @@
<view class="org.fdroid.fdroid.privileged.views.AppSecurityPermissions$PermissionItemView"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
@ -58,7 +59,7 @@
android:layout_alignParentStart="true"
android:layout_alignBottom="@+id/perm_money_label"
android:scaleType="fitCenter"
android:tint="@color/perms_costs_money"
app:tint="@color/perms_costs_money"
android:tintMode="src_in"
android:src="@drawable/ic_coins_s" />
<TextView

View File

@ -55,7 +55,7 @@
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/empty_state"
android:tint="?attr/mainTabSwapSplashTint"
app:tint="?attr/mainTabSwapSplashTint"
android:scaleType="fitCenter"
android:visibility="gone" />

View File

@ -2,9 +2,9 @@
package org.fdroid.fdroid;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.Signature;
import androidx.test.core.app.ApplicationProvider;
import org.fdroid.fdroid.views.AppDetailsRecyclerViewAdapter;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -12,6 +12,7 @@ import org.robolectric.RobolectricTestRunner;
import java.io.File;
import java.util.Date;
import java.util.Random;
import java.util.TimeZone;
import static org.junit.Assert.assertEquals;
@ -215,4 +216,40 @@ public class UtilsTest {
}
}
}
/**
* Test the replacement for the ancient fingerprint algorithm.
*
* @see org.fdroid.fdroid.data.Apk#sig
*/
@Test
public void testGetsig() {
/*
* I don't fully understand the loop used here. I've copied it verbatim
* from getsig.java bundled with FDroidServer. I *believe* it is taking
* the raw byte encoding of the certificate & converting it to a byte
* array of the hex representation of the original certificate byte
* array. This is then MD5 sum'd. It's a really bad way to be doing this
* if I'm right... If I'm not right, I really don't know! see lines
* 67->75 in getsig.java bundled with Fdroidserver
*/
for (int length : new int[]{256, 345, 1233, 4032, 12092}) {
byte[] rawCertBytes = new byte[length];
new Random().nextBytes(rawCertBytes);
final byte[] fdroidSig = new byte[rawCertBytes.length * 2];
for (int j = 0; j < rawCertBytes.length; j++) {
byte v = rawCertBytes[j];
int d = (v >> 4) & 0xF;
fdroidSig[j * 2] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
d = v & 0xF;
fdroidSig[j * 2 + 1] = (byte) (d >= 10 ? ('a' + d - 10) : ('0' + d));
}
String sig = Utils.hashBytes(fdroidSig, "md5");
assertEquals(sig, Utils.getsig(rawCertBytes));
PackageInfo packageInfo = new PackageInfo();
packageInfo.signatures = new Signature[]{new Signature(rawCertBytes)};
assertEquals(sig, Utils.getPackageSig(packageInfo));
}
}
}

View File

@ -5,6 +5,7 @@ import org.junit.Test;
import java.io.File;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assume.assumeTrue;
public class SanitizedFileTest {
@ -45,4 +46,13 @@ public class SanitizedFileTest {
}
@Test
public void testSanitizeFileName() {
for (String valid : new String[]{"An.stop", "a.0", "packageName", "com.this-and-that", "A_.o"}) {
assertEquals(valid, SanitizedFile.sanitizeFileName(valid));
}
for (String invalid : new String[]{"'--;DROP", "a.0)", "packageName\n"}) {
assertNotEquals(invalid, SanitizedFile.sanitizeFileName(invalid));
}
}
}

View File

@ -0,0 +1,25 @@
package org.fdroid.fdroid.views.main;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
public class MainActivityTest {
@Test
public void testSanitizeSearchTerms() {
for (String valid : new String[]{"private browser", "πÇÇ", "现代 通用字", "български", "عربي"}) {
assertEquals(valid, MainActivity.sanitizeSearchTerms(valid));
}
for (String invalid : new String[]{
"Robert'); DROP TABLE Students; --",
"xxx') OR 1 = 1 -- ]",
"105 OR 1=1;",
"\" OR \"=\"",
}) {
assertNotEquals(invalid, MainActivity.sanitizeSearchTerms(invalid));
}
}
}

View File

@ -115,7 +115,6 @@
<module name="FinalClass" />
<module name="ArrayTypeStyle" />
<module name="ArrayTrailingComma" />
<module name="UpperEll" />
<module name="StringLiteralEquality" />