Compare commits

..

No commits in common. "master" and "1.13-alpha1" have entirely different histories.

37 changed files with 3269 additions and 343 deletions

View File

@ -21,16 +21,16 @@ def basicApplicationId = "org.fdroid.basic"
def privilegedExtensionApplicationId = '"org.fdroid.fdroid.privileged"' def privilegedExtensionApplicationId = '"org.fdroid.fdroid.privileged"'
android { android {
compileSdkVersion 30 compileSdkVersion 29
defaultConfig { defaultConfig {
versionCode 1013001 versionCode 1013001
versionName getVersionName() versionName getVersionName()
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
minSdkVersion 24 minSdkVersion 22
//noinspection ExpiredTargetSdkVersion //noinspection ExpiredTargetSdkVersion
targetSdkVersion 28 targetSdkVersion 25
/* /*
The Android Testing Support Library collects analytics to continuously improve the testing The Android Testing Support Library collects analytics to continuously improve the testing
experience. More specifically, it uploads a hash of the package name of the application experience. More specifically, it uploads a hash of the package name of the application
@ -142,9 +142,10 @@ android {
} }
dependencies { dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.vectordrawable:vectordrawable:1.1.0' implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
@ -161,11 +162,9 @@ dependencies {
implementation 'commons-io:commons-io:2.6' implementation 'commons-io:commons-io:2.6'
implementation 'commons-net:commons-net:3.6' implementation 'commons-net:commons-net:3.6'
implementation 'ch.acra:acra:4.9.1' implementation 'ch.acra:acra:4.9.1'
implementation 'io.reactivex:rxjava:1.1.0'
implementation 'com.hannesdorfmann:adapterdelegates3:3.0.1' implementation 'com.hannesdorfmann:adapterdelegates3:3.0.1'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
implementation 'io.reactivex.rxjava3:rxjava:3.0.9'
implementation 'com.fasterxml.jackson.core:jackson-core:2.11.1' implementation 'com.fasterxml.jackson.core:jackson-core:2.11.1'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.1' implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.1'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.1' implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.1'

View File

@ -31,6 +31,13 @@
public *; public *;
} }
# The rxjava library depends on sun.misc.Unsafe, which is unavailable on Android
# The rxjava team is aware of this, and mention in the docs that they only use
# the unsafe functionality if the platform supports it.
# - https://github.com/ReactiveX/RxJava/issues/1415#issuecomment-48390883
# - https://github.com/ReactiveX/RxJava/blob/1.x/src/main/java/rx/internal/util/unsafe/UnsafeAccess.java#L23
-dontwarn rx.internal.util.**
-keepattributes *Annotation*,EnclosingMethod,Signature -keepattributes *Annotation*,EnclosingMethod,Signature
-keepnames class com.fasterxml.jackson.** { *; } -keepnames class com.fasterxml.jackson.** { *; }
-dontwarn com.fasterxml.jackson.databind.ext.** -dontwarn com.fasterxml.jackson.databind.ext.**

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -1,5 +1,6 @@
package org.fdroid.fdroid.nearby; package org.fdroid.fdroid.nearby;
import android.annotation.SuppressLint;
import android.app.Notification; import android.app.Notification;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
@ -12,17 +13,11 @@ import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.IBinder; import android.os.IBinder;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; 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 org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.NotificationHelper; import org.fdroid.fdroid.NotificationHelper;
import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Preferences;
@ -35,6 +30,7 @@ import org.fdroid.fdroid.data.Schema;
import org.fdroid.fdroid.nearby.peers.Peer; import org.fdroid.fdroid.nearby.peers.Peer;
import org.fdroid.fdroid.net.Downloader; import org.fdroid.fdroid.net.Downloader;
import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
@ -45,11 +41,13 @@ import java.util.Set;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
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 cc.mvdan.accesspoint.WifiApControl;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
/** /**
* Central service which manages all of the different moving parts of swap which are required * Central service which manages all of the different moving parts of swap which are required
@ -111,6 +109,46 @@ public class SwapService extends Service {
UpdateService.updateRepoNow(this, peer.getRepoAddress()); UpdateService.updateRepoNow(this, peer.getRepoAddress());
} }
@SuppressLint("StaticFieldLeak")
private void askServerToSwapWithUs(final Repo repo) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... args) {
String swapBackUri = Utils.getLocalRepoUri(FDroidApp.repo).toString();
HttpURLConnection conn = null;
try {
URL url = new URL(repo.address.replace("/fdroid/repo", "/request-swap"));
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.setDoOutput(true);
OutputStream outputStream = conn.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(outputStream);
writer.write("repo=" + swapBackUri);
writer.flush();
writer.close();
outputStream.close();
int responseCode = conn.getResponseCode();
Utils.debugLog(TAG, "Asking server at " + repo.address + " to swap with us in return (by " +
"POSTing to \"/request-swap\" with repo \"" + swapBackUri + "\"): " + responseCode);
} catch (IOException e) {
Log.e(TAG, "Error while asking server to swap with us", e);
Intent intent = new Intent(Downloader.ACTION_INTERRUPTED);
intent.setData(Uri.parse(repo.address));
intent.putExtra(Downloader.EXTRA_ERROR_MESSAGE, e.getLocalizedMessage());
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
} finally {
if (conn != null) {
conn.disconnect();
}
}
return null;
}
}.execute();
}
private Repo ensureRepoExists(@NonNull Peer peer) { private Repo ensureRepoExists(@NonNull Peer peer) {
// TODO: newRepoConfig.getParsedUri() will include a fingerprint, which may not match with // TODO: newRepoConfig.getParsedUri() will include a fingerprint, which may not match with
// the repos address in the database. Not sure on best behaviour in this situation. // the repos address in the database. Not sure on best behaviour in this situation.
@ -304,15 +342,12 @@ public class SwapService extends Service {
@Nullable @Nullable
private Timer timer; private Timer timer;
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
public class Binder extends android.os.Binder { public class Binder extends android.os.Binder {
public SwapService getService() { public SwapService getService() {
return SwapService.this; return SwapService.this;
} }
} }
@Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
startForeground(NOTIFICATION, createNotification()); startForeground(NOTIFICATION, createNotification());
@ -362,45 +397,6 @@ public class SwapService extends Service {
BonjourManager.setVisible(this, getWifiVisibleUserPreference() || getHotspotActivatedUserPreference()); BonjourManager.setVisible(this, getWifiVisibleUserPreference() || getHotspotActivatedUserPreference());
} }
private void askServerToSwapWithUs(final Repo repo) {
compositeDisposable.add(
Completable.fromAction(() -> {
String swapBackUri = Utils.getLocalRepoUri(FDroidApp.repo).toString();
HttpURLConnection conn = null;
try {
URL url = new URL(repo.address.replace("/fdroid/repo", "/request-swap"));
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.setDoOutput(true);
try (OutputStream outputStream = conn.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(outputStream)) {
writer.write("repo=" + swapBackUri);
writer.flush();
}
int responseCode = conn.getResponseCode();
Utils.debugLog(TAG, "Asking server at " + repo.address + " to swap with us in return (by " +
"POSTing to \"/request-swap\" with repo \"" + swapBackUri + "\"): " + responseCode);
} finally {
if (conn != null) {
conn.disconnect();
}
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(e -> {
Intent intent = new Intent(Downloader.ACTION_INTERRUPTED);
intent.setData(Uri.parse(repo.address));
intent.putExtra(Downloader.EXTRA_ERROR_MESSAGE, e.getLocalizedMessage());
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
})
.subscribe()
);
}
/** /**
* This is for setting things up for when the {@code SwapService} was * This is for setting things up for when the {@code SwapService} was
* started by the user clicking on the initial start button. The things * started by the user clicking on the initial start button. The things
@ -424,8 +420,6 @@ public class SwapService extends Service {
@Override @Override
public void onDestroy() { public void onDestroy() {
compositeDisposable.dispose();
Utils.debugLog(TAG, "Destroying service, will disable swapping if required, and unregister listeners."); Utils.debugLog(TAG, "Destroying service, will disable swapping if required, and unregister listeners.");
Preferences.get().unregisterLocalRepoHttpsListeners(httpsEnabledListener); Preferences.get().unregisterLocalRepoHttpsListeners(httpsEnabledListener);
localBroadcastManager.unregisterReceiver(onWifiChange); localBroadcastManager.unregisterReceiver(onWifiChange);

View File

@ -34,18 +34,7 @@ import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.switchmaterial.SwitchMaterial;
import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult; import com.google.zxing.integration.android.IntentResult;
@ -65,6 +54,7 @@ import org.fdroid.fdroid.net.BluetoothDownloader;
import org.fdroid.fdroid.net.Downloader; import org.fdroid.fdroid.net.Downloader;
import org.fdroid.fdroid.net.HttpDownloader; import org.fdroid.fdroid.net.HttpDownloader;
import org.fdroid.fdroid.qr.CameraCharacteristicsChecker; import org.fdroid.fdroid.qr.CameraCharacteristicsChecker;
import org.fdroid.fdroid.qr.QrGenAsyncTask;
import org.fdroid.fdroid.views.main.MainActivity; import org.fdroid.fdroid.views.main.MainActivity;
import java.util.Date; import java.util.Date;
@ -75,8 +65,17 @@ import java.util.Set;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import com.google.android.material.switchmaterial.SwitchMaterial;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import cc.mvdan.accesspoint.WifiApControl; import cc.mvdan.accesspoint.WifiApControl;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import static org.fdroid.fdroid.views.main.MainActivity.ACTION_REQUEST_SWAP; import static org.fdroid.fdroid.views.main.MainActivity.ACTION_REQUEST_SWAP;
@ -119,8 +118,6 @@ public class SwapWorkflowActivity extends AppCompatActivity {
@LayoutRes @LayoutRes
private int currentSwapViewLayoutRes = STEP_INTRO; private int currentSwapViewLayoutRes = STEP_INTRO;
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
public static void requestSwap(Context context, String repo) { public static void requestSwap(Context context, String repo) {
requestSwap(context, Uri.parse(repo)); requestSwap(context, Uri.parse(repo));
} }
@ -238,7 +235,6 @@ public class SwapWorkflowActivity extends AppCompatActivity {
@Override @Override
protected void onDestroy() { protected void onDestroy() {
compositeDisposable.dispose();
localBroadcastManager.unregisterReceiver(downloaderInterruptedReceiver); localBroadcastManager.unregisterReceiver(downloaderInterruptedReceiver);
unbindService(serviceConnection); unbindService(serviceConnection);
super.onDestroy(); super.onDestroy();
@ -933,23 +929,18 @@ public class SwapWorkflowActivity extends AppCompatActivity {
ImageView qrImage = container.findViewById(R.id.wifi_qr_code); ImageView qrImage = container.findViewById(R.id.wifi_qr_code);
if (qrUriString != null && qrImage != null) { if (qrUriString != null && qrImage != null) {
Utils.debugLog(TAG, "Encoded swap URI in QR Code: " + qrUriString); Utils.debugLog(TAG, "Encoded swap URI in QR Code: " + qrUriString);
new QrGenAsyncTask(SwapWorkflowActivity.this, R.id.wifi_qr_code).execute(qrUriString);
compositeDisposable.add(Utils.generateQrBitmap(this, qrUriString) // Replace all blacks with the background blue.
.subscribe(qrBitmap -> { qrImage.setColorFilter(new LightingColorFilter(0xffffffff, ContextCompat.getColor(this,
qrImage.setImageBitmap(qrBitmap); R.color.swap_blue)));
// Replace all blacks with the background blue. final View qrWarningMessage = container.findViewById(R.id.warning_qr_scanner);
qrImage.setColorFilter(new LightingColorFilter(0xffffffff, if (CameraCharacteristicsChecker.getInstance(this).hasAutofocus()) {
ContextCompat.getColor(this, R.color.swap_blue))); qrWarningMessage.setVisibility(View.GONE);
} else {
final View qrWarningMessage = container.findViewById(R.id.warning_qr_scanner); qrWarningMessage.setVisibility(View.VISIBLE);
if (CameraCharacteristicsChecker.getInstance(this).hasAutofocus()) { }
qrWarningMessage.setVisibility(View.GONE);
} else {
qrWarningMessage.setVisibility(View.VISIBLE);
}
})
);
} }
} }

View File

@ -13,10 +13,6 @@ import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.apache.commons.net.util.SubnetUtils; import org.apache.commons.net.util.SubnetUtils;
import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.FDroidApp;
@ -35,8 +31,10 @@ import java.security.cert.Certificate;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Locale; import java.util.Locale;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import cc.mvdan.accesspoint.WifiApControl; import cc.mvdan.accesspoint.WifiApControl;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
/** /**
* Handle state changes to the device's wifi, storing the required bits. * Handle state changes to the device's wifi, storing the required bits.
@ -72,8 +70,6 @@ public class WifiStateChangeService extends IntentService {
private static int previousWifiState = Integer.MIN_VALUE; private static int previousWifiState = Integer.MIN_VALUE;
private static int wifiState; private static int wifiState;
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
public WifiStateChangeService() { public WifiStateChangeService() {
super("WifiStateChangeService"); super("WifiStateChangeService");
} }
@ -86,12 +82,6 @@ public class WifiStateChangeService extends IntentService {
context.startService(intent); context.startService(intent);
} }
@Override
public void onDestroy() {
compositeDisposable.dispose();
super.onDestroy();
}
@Override @Override
protected void onHandleIntent(Intent intent) { protected void onHandleIntent(Intent intent) {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST); android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST);
@ -119,7 +109,7 @@ public class WifiStateChangeService extends IntentService {
} }
if (Build.VERSION.SDK_INT < 21 && wifiState == WifiManager.WIFI_STATE_ENABLED) { if (Build.VERSION.SDK_INT < 21 && wifiState == WifiManager.WIFI_STATE_ENABLED) {
compositeDisposable.add(UpdateService.scheduleIfStillOnWifi(this).subscribe()); UpdateService.scheduleIfStillOnWifi(this);
} }
} }
} }

View File

@ -60,7 +60,7 @@
android:description="@string/app_description" android:description="@string/app_description"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:label="BobStore" android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.App"> android:theme="@style/Theme.App">

View File

@ -24,7 +24,7 @@ class ProgressBufferedInputStream extends BufferedInputStream {
} }
@Override @Override
public synchronized int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException { public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException {
if (progressListener != null) { if (progressListener != null) {
currentBytes += byteCount; currentBytes += byteCount;
/* don't send every change to keep things efficient. 333333 bytes to keep all /* don't send every change to keep things efficient. 333333 bytes to keep all

View File

@ -30,6 +30,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Process; import android.os.Process;
import android.os.SystemClock; import android.os.SystemClock;
@ -50,18 +51,15 @@ import org.fdroid.fdroid.installer.InstallManagerService;
import org.fdroid.fdroid.net.BluetoothDownloader; import org.fdroid.fdroid.net.BluetoothDownloader;
import org.fdroid.fdroid.net.ConnectivityMonitorService; import org.fdroid.fdroid.net.ConnectivityMonitorService;
import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.app.JobIntentService; import androidx.core.app.JobIntentService;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class UpdateService extends JobIntentService { public class UpdateService extends JobIntentService {
@ -217,30 +215,42 @@ public class UpdateService extends JobIntentService {
* unlimited networks over metered networks for index updates and auto * unlimited networks over metered networks for index updates and auto
* downloads of app updates. Starting with {@code android-21}, this uses * downloads of app updates. Starting with {@code android-21}, this uses
* {@link android.app.job.JobScheduler} instead. * {@link android.app.job.JobScheduler} instead.
*
* @return a {@link Completable} that schedules the update. If this process is already running,
* a {@code Completable} that completes immediately is returned.
*/ */
@NonNull public static void scheduleIfStillOnWifi(Context context) {
public static Completable scheduleIfStillOnWifi(Context context) {
if (Build.VERSION.SDK_INT >= 21) { if (Build.VERSION.SDK_INT >= 21) {
throw new IllegalStateException("This should never be used on android-21 or newer!"); throw new IllegalStateException("This should never be used on android-21 or newer!");
} }
if (isScheduleIfStillOnWifiRunning || !Preferences.get().isBackgroundDownloadAllowed()) { if (isScheduleIfStillOnWifiRunning || !Preferences.get().isBackgroundDownloadAllowed()) {
return Completable.complete(); return;
} }
isScheduleIfStillOnWifiRunning = true; isScheduleIfStillOnWifiRunning = true;
new StillOnWifiAsyncTask(context).execute();
}
private static final class StillOnWifiAsyncTask extends AsyncTask<Void, Void, Void> {
private final WeakReference<Context> contextWeakReference;
private StillOnWifiAsyncTask(Context context) {
this.contextWeakReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... voids) {
Context context = contextWeakReference.get();
try {
Thread.sleep(120000);
if (Preferences.get().isBackgroundDownloadAllowed()) {
Utils.debugLog(TAG, "scheduling update because there is good internet");
schedule(context);
}
} catch (Throwable e) { // NOPMD
Utils.debugLog(TAG, e.getMessage());
}
isScheduleIfStillOnWifiRunning = false;
return null;
}
return Completable.timer(2, TimeUnit.MINUTES)
.andThen(Completable.fromAction(() -> {
if (Preferences.get().isBackgroundDownloadAllowed()) {
Utils.debugLog(TAG, "scheduling update because there is good internet");
schedule(context);
}
isScheduleIfStillOnWifiRunning = false;
}))
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread());
} }
public static void stopNow(Context context) { public static void stopNow(Context context) {

View File

@ -27,7 +27,6 @@ import android.content.pm.Signature;
import android.content.res.Resources; import android.content.res.Resources;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
@ -45,15 +44,11 @@ import android.text.style.TypefaceSpan;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.Display;
import android.view.View; import android.view.View;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.Toast; import android.widget.Toast;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.encode.Contents;
import com.google.zxing.encode.QRCodeEncoder;
import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.ImageScaleType; import com.nostra13.universalimageloader.core.assist.ImageScaleType;
@ -100,11 +95,7 @@ import java.util.regex.Pattern;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
public final class Utils { public final class Utils {
@ -995,27 +986,6 @@ public final class Utils {
} }
} }
@NonNull
public static Single<Bitmap> generateQrBitmap(@NonNull final AppCompatActivity activity,
@NonNull final String qrData) {
return Single.fromCallable(() -> {
Display display = activity.getWindowManager().getDefaultDisplay();
Point outSize = new Point();
display.getSize(outSize);
final int x = outSize.x;
final int y = outSize.y;
final int qrCodeDimension = Math.min(x, y);
debugLog(TAG, "generating QRCode Bitmap of " + qrCodeDimension + "x" + qrCodeDimension);
QRCodeEncoder qrCodeEncoder = new QRCodeEncoder(qrData, null,
Contents.Type.TEXT, BarcodeFormat.QR_CODE.toString(), qrCodeDimension);
return qrCodeEncoder.encodeAsBitmap();
})
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(throwable -> Log.e(TAG, "Could not encode QR as bitmap", throwable));
}
/** /**
* Keep an instance of this class as an field in an AppCompatActivity for figuring out whether the on * Keep an instance of this class as an field in an AppCompatActivity for figuring out whether the on
* screen keyboard is currently visible or not. * screen keyboard is currently visible or not.

View File

@ -322,16 +322,16 @@ public class DBHelper extends SQLiteOpenHelper {
/** /**
* Look for additional, initial repositories from the device's filesystem. * Look for additional, initial repositories from the device's filesystem.
* These can be added as part of the ROM ({@code /system} or {@code /product} * These can be added as part of the ROM ({@code /system} or included later
* or included later by vendors/OEMs ({@code /vendor}, {@code /odm}, {@code /oem}). * by vendors/OEMs ({@code /vendor}, {@code /odm}, {@code /oem}). These are
* These are always added at a lower priority than the repos embedded in the APK via * always added at a lower priority than the repos embedded in the APK via
* {@code default_repos.xml}. * {@code default_repos.xml}.
* <p> * <p>
* ROM (System) has the lowest priority, then Product, Vendor, ODM, and OEM. * ROM has the lowest priority, then Vendor, ODM, and OEM.
*/ */
private static List<String> loadAdditionalRepos(String packageName) { private static List<String> loadAdditionalRepos(String packageName) {
List<String> repoItems = new LinkedList<>(); List<String> repoItems = new LinkedList<>();
for (String root : Arrays.asList("/system", "/product", "/vendor", "/odm", "/oem")) { for (String root : Arrays.asList("/system", "/vendor", "/odm", "/oem")) {
File additionalReposFile = new File(root + "/etc/" + packageName + "/additional_repos.xml"); File additionalReposFile = new File(root + "/etc/" + packageName + "/additional_repos.xml");
try { try {
if (additionalReposFile.isFile()) { if (additionalReposFile.isFile()) {

View File

@ -31,9 +31,9 @@ import java.util.concurrent.TimeUnit;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.app.JobIntentService; import androidx.core.app.JobIntentService;
import io.reactivex.rxjava3.disposables.CompositeDisposable; import rx.functions.Action1;
import io.reactivex.rxjava3.schedulers.Schedulers; import rx.schedulers.Schedulers;
import io.reactivex.rxjava3.subjects.PublishSubject; import rx.subjects.PublishSubject;
/** /**
* Handles all updates to {@link InstalledAppProvider}, whether checking the contents * Handles all updates to {@link InstalledAppProvider}, whether checking the contents
@ -65,15 +65,13 @@ public class InstalledAppProviderService extends JobIntentService {
private static final String EXTRA_PACKAGE_INFO = "org.fdroid.fdroid.data.extra.PACKAGE_INFO"; private static final String EXTRA_PACKAGE_INFO = "org.fdroid.fdroid.data.extra.PACKAGE_INFO";
/** /**
* This is for notifying the users of this {@link android.content.ContentProvider} * This is for notifing the users of this {@link android.content.ContentProvider}
* that the contents have changed. Since {@link Intent}s can come in slow * that the contents has changed. Since {@link Intent}s can come in slow
* or fast, and this can trigger a lot of UI updates, the actual * or fast, and this can trigger a lot of UI updates, the actual
* notifications are rate limited to one per second. * notifications are rate limited to one per second.
*/ */
private PublishSubject<String> packageChangeNotifier; private PublishSubject<String> packageChangeNotifier;
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
@ -83,16 +81,17 @@ public class InstalledAppProviderService extends JobIntentService {
// only emit an event to the subscriber after it has not received any new events for one second. // only emit an event to the subscriber after it has not received any new events for one second.
// This ensures that we don't constantly ask our lists of apps to update as we iterate over // This ensures that we don't constantly ask our lists of apps to update as we iterate over
// the list of installed apps and insert them to the database... // the list of installed apps and insert them to the database...
compositeDisposable.add( packageChangeNotifier
packageChangeNotifier .subscribeOn(Schedulers.newThread())
.subscribeOn(Schedulers.newThread()) .debounce(3, TimeUnit.SECONDS)
.debounce(3, TimeUnit.SECONDS) .subscribe(new Action1<String>() {
.subscribe(packageName -> { @Override
Utils.debugLog(TAG, "Notifying content providers to update relevant views."); public void call(String packageName) {
getContentResolver().notifyChange(AppProvider.getContentUri(), null); Utils.debugLog(TAG, "Notifying content providers (so they can update the relevant views).");
getContentResolver().notifyChange(ApkProvider.getContentUri(), null); getContentResolver().notifyChange(AppProvider.getContentUri(), null);
}) getContentResolver().notifyChange(ApkProvider.getContentUri(), null);
); }
});
// ...alternatively, this non-debounced version will instantly emit an event about the // ...alternatively, this non-debounced version will instantly emit an event about the
// particular package being updated. This is required so that our AppDetails view can update // particular package being updated. This is required so that our AppDetails view can update
@ -101,18 +100,14 @@ public class InstalledAppProviderService extends JobIntentService {
// only for changes to specific URIs in the AppProvider. These are triggered when a more // only for changes to specific URIs in the AppProvider. These are triggered when a more
// general notification (e.g. to AppProvider.getContentUri()) is fired, but not when a // general notification (e.g. to AppProvider.getContentUri()) is fired, but not when a
// sibling such as AppProvider.getHighestPriorityMetadataUri() is fired. // sibling such as AppProvider.getHighestPriorityMetadataUri() is fired.
compositeDisposable.add( packageChangeNotifier.subscribeOn(Schedulers.newThread())
packageChangeNotifier .subscribe(new Action1<String>() {
.subscribeOn(Schedulers.newThread()) @Override
.subscribe(packageName -> getContentResolver() public void call(String packageName) {
.notifyChange(AppProvider.getHighestPriorityMetadataUri(packageName), null)) getContentResolver()
); .notifyChange(AppProvider.getHighestPriorityMetadataUri(packageName), null);
} }
});
@Override
public void onDestroy() {
compositeDisposable.dispose();
super.onDestroy();
} }
/** /**
@ -248,7 +243,12 @@ public class InstalledAppProviderService extends JobIntentService {
public static File getPathToInstalledApk(PackageInfo packageInfo) { public static File getPathToInstalledApk(PackageInfo packageInfo) {
File apk = new File(packageInfo.applicationInfo.publicSourceDir); File apk = new File(packageInfo.applicationInfo.publicSourceDir);
if (apk.isDirectory()) { if (apk.isDirectory()) {
FilenameFilter filter = (dir, name) -> name.endsWith(".apk"); FilenameFilter filter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".apk");
}
};
File[] files = apk.listFiles(filter); File[] files = apk.listFiles(filter);
if (files == null) { if (files == null) {
String msg = packageInfo.packageName + " sourceDir has no APKs: " + apk.getAbsolutePath(); String msg = packageInfo.packageName + " sourceDir has no APKs: " + apk.getAbsolutePath();

View File

@ -128,7 +128,6 @@ public class FileInstallerActivity extends FragmentActivity {
@Override @Override
public void onRequestPermissionsResult(int requestCode, public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) { @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) { switch (requestCode) {
case MY_PERMISSIONS_REQUEST_STORAGE: case MY_PERMISSIONS_REQUEST_STORAGE:
// If request is cancelled, the result arrays are empty. // If request is cancelled, the result arrays are empty.

View File

@ -256,7 +256,7 @@ public abstract class Downloader {
} }
@Override @Override
public synchronized void mark(int readlimit) { public void mark(int readlimit) {
toWrap.mark(readlimit); toWrap.mark(readlimit);
} }

View File

@ -0,0 +1,74 @@
package org.fdroid.fdroid.qr;
import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.os.AsyncTask;
import android.util.Log;
import android.view.Display;
import android.widget.ImageView;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.encode.Contents;
import com.google.zxing.encode.QRCodeEncoder;
import org.fdroid.fdroid.Utils;
import androidx.appcompat.app.AppCompatActivity;
public class QrGenAsyncTask extends AsyncTask<String, Void, Void> {
private static final String TAG = "QrGenAsyncTask";
private final AppCompatActivity activity;
private final int viewId;
private Bitmap qrBitmap;
public QrGenAsyncTask(AppCompatActivity activity, int viewId) {
this.activity = activity;
this.viewId = viewId;
}
/*
* The method for getting screen dimens changed, so this uses both the
* deprecated one and the 13+ one, and supports all Android versions.
*/
@SuppressWarnings("deprecation")
@TargetApi(13)
@Override
protected Void doInBackground(String... s) {
String qrData = s[0];
Display display = activity.getWindowManager().getDefaultDisplay();
Point outSize = new Point();
int x, y, qrCodeDimension;
display.getSize(outSize);
x = outSize.x;
y = outSize.y;
if (x < y) {
qrCodeDimension = x;
} else {
qrCodeDimension = y;
}
Utils.debugLog(TAG, "generating QRCode Bitmap of " + qrCodeDimension + "x" + qrCodeDimension);
QRCodeEncoder qrCodeEncoder = new QRCodeEncoder(qrData, null,
Contents.Type.TEXT, BarcodeFormat.QR_CODE.toString(), qrCodeDimension);
try {
qrBitmap = qrCodeEncoder.encodeAsBitmap();
} catch (WriterException e) {
Log.e(TAG, "Could not encode QR as bitmap", e);
}
return null;
}
@Override
protected void onPostExecute(Void v) {
ImageView qrCodeImageView = (ImageView) activity.findViewById(viewId);
// If the generation takes too long for whatever reason, then this view, and indeed the entire
// activity may not be around any more.
if (qrCodeImageView != null) {
qrCodeImageView.setImageBitmap(qrBitmap);
}
}
}

View File

@ -19,6 +19,7 @@
package org.fdroid.fdroid.views; package org.fdroid.fdroid.views;
import android.annotation.SuppressLint;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.ContentResolver; import android.content.ContentResolver;
@ -31,13 +32,13 @@ import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.net.wifi.WifiInfo; import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
@ -49,6 +50,17 @@ import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.NavUtils;
import androidx.core.app.TaskStackBuilder;
import androidx.core.content.ContextCompat;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
@ -75,22 +87,6 @@ import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.NavUtils;
import androidx.core.app.TaskStackBuilder;
import androidx.core.content.ContextCompat;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class ManageReposActivity extends AppCompatActivity public class ManageReposActivity extends AppCompatActivity
implements LoaderManager.LoaderCallbacks<Cursor>, RepoAdapter.EnabledListener { implements LoaderManager.LoaderCallbacks<Cursor>, RepoAdapter.EnabledListener {
private static final String TAG = "ManageReposActivity"; private static final String TAG = "ManageReposActivity";
@ -111,8 +107,6 @@ public class ManageReposActivity extends AppCompatActivity
*/ */
private boolean finishAfterAddingRepo; private boolean finishAfterAddingRepo;
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
FDroidApp fdroidApp = (FDroidApp) getApplication(); FDroidApp fdroidApp = (FDroidApp) getApplication();
@ -160,12 +154,6 @@ public class ManageReposActivity extends AppCompatActivity
}); });
} }
@Override
protected void onDestroy() {
compositeDisposable.dispose();
super.onDestroy();
}
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater(); MenuInflater menuInflater = getMenuInflater();
@ -583,8 +571,10 @@ public class ManageReposActivity extends AppCompatActivity
/** /**
* Adds a new repo to the database. * Adds a new repo to the database.
*/ */
@SuppressLint("StaticFieldLeak")
private void prepareToCreateNewRepo(final String originalAddress, final String fingerprint, private void prepareToCreateNewRepo(final String originalAddress, final String fingerprint,
final String username, final String password) { final String username, final String password) {
final View addRepoForm = addRepoDialog.findViewById(R.id.add_repo_form); final View addRepoForm = addRepoDialog.findViewById(R.id.add_repo_form);
addRepoForm.setVisibility(View.GONE); addRepoForm.setVisibility(View.GONE);
final View positiveButton = addRepoDialog.getButton(AlertDialog.BUTTON_POSITIVE); final View positiveButton = addRepoDialog.getButton(AlertDialog.BUTTON_POSITIVE);
@ -596,117 +586,153 @@ public class ManageReposActivity extends AppCompatActivity
final Button skip = addRepoDialog.getButton(AlertDialog.BUTTON_NEGATIVE); final Button skip = addRepoDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
skip.setText(R.string.skip); skip.setText(R.string.skip);
final int refreshDialog = Integer.MAX_VALUE; final AsyncTask<String, String, String> checker = new AsyncTask<String, String, String>() {
final Disposable disposable = Single.fromCallable(() -> {
int statusCode = -1;
if (fingerprintRepoMap.containsKey(fingerprint)) { private int statusCode = -1;
statusCode = refreshDialog; private static final int REFRESH_DIALOG = Integer.MAX_VALUE;
return Pair.create(statusCode, originalAddress);
}
if (originalAddress.startsWith(ContentResolver.SCHEME_CONTENT) @Override
|| originalAddress.startsWith(ContentResolver.SCHEME_FILE)) { protected String doInBackground(String... params) {
// TODO check whether there is read access final String originalAddress = params[0];
return Pair.create(statusCode, originalAddress);
}
final String[] pathsToCheck = {"", "fdroid/repo", "repo"}; if (fingerprintRepoMap.containsKey(fingerprint)) {
for (final String path : pathsToCheck) { statusCode = REFRESH_DIALOG;
Utils.debugLog(TAG, "Check for repo at " + originalAddress + " with suffix '" + path + "'"); return originalAddress;
Uri.Builder builder = Uri.parse(originalAddress).buildUpon().appendEncodedPath(path);
final String addressWithoutIndex = builder.build().toString();
runOnUiThread(() -> textSearching.setText(getString(R.string.repo_searching_address,
addressWithoutIndex)));
if (urlRepoMap.containsKey(addressWithoutIndex)) {
statusCode = refreshDialog;
return Pair.create(statusCode, addressWithoutIndex);
} }
final Uri uri = builder.appendPath(IndexUpdater.SIGNED_FILE_NAME).build(); if (originalAddress.startsWith(ContentResolver.SCHEME_CONTENT)
|| originalAddress.startsWith(ContentResolver.SCHEME_FILE)) {
// TODO check whether there is read access
return originalAddress;
}
try { final String[] pathsToCheck = {"", "fdroid/repo", "repo"};
final URL url = new URL(uri.toString()); for (final String path : pathsToCheck) {
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("HEAD");
statusCode = connection.getResponseCode(); Utils.debugLog(TAG, "Check for repo at " + originalAddress + " with suffix '" + path + "'");
Uri.Builder builder = Uri.parse(originalAddress).buildUpon().appendEncodedPath(path);
final String addressWithoutIndex = builder.build().toString();
publishProgress(addressWithoutIndex);
if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED if (urlRepoMap.containsKey(addressWithoutIndex)) {
|| statusCode == HttpURLConnection.HTTP_OK) { statusCode = REFRESH_DIALOG;
Utils.debugLog(TAG, "Found F-Droid repo at " + addressWithoutIndex); return addressWithoutIndex;
return Pair.create(statusCode, addressWithoutIndex); }
final Uri uri = builder.appendPath(IndexUpdater.SIGNED_FILE_NAME).build();
try {
if (checkForRepository(uri)) {
Utils.debugLog(TAG, "Found F-Droid repo at " + addressWithoutIndex);
return addressWithoutIndex;
}
} catch (IOException e) {
Log.e(TAG, "Error while searching for repo at " + addressWithoutIndex, e);
return originalAddress;
}
if (isCancelled()) {
Utils.debugLog(TAG, "Not checking more repo addresses, because process was skipped.");
break;
} }
} catch (IOException e) {
Log.e(TAG, "Error while searching for repo at " + addressWithoutIndex, e);
return Pair.create(statusCode, originalAddress);
} }
return originalAddress;
} }
return Pair.create(statusCode, originalAddress);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnDispose(() -> Utils.debugLog(TAG,
"Not checking more repo addresses, because process was skipped."))
.subscribe(codeAddressPair -> {
final int statusCode = codeAddressPair.first;
final String newAddress = codeAddressPair.second;
if (addRepoDialog.isShowing()) { private boolean checkForRepository(Uri indexUri) throws IOException {
if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) { final URL url = new URL(indexUri.toString());
final View view = getLayoutInflater().inflate(R.layout.login, null); final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
final AlertDialog credentialsDialog = new AlertDialog.Builder(context) connection.setRequestMethod("HEAD");
.setView(view).create();
final EditText nameInput = (EditText) view.findViewById(R.id.edit_name);
final EditText passwordInput = (EditText) view.findViewById(R.id.edit_password);
if (username != null) { statusCode = connection.getResponseCode();
nameInput.setText(username);
}
if (password != null) {
passwordInput.setText(password);
}
credentialsDialog.setTitle(R.string.login_title); return statusCode == HttpURLConnection.HTTP_UNAUTHORIZED
credentialsDialog.setButton(DialogInterface.BUTTON_NEGATIVE, || statusCode == HttpURLConnection.HTTP_OK;
getString(R.string.cancel), (dialog, which) -> { }
@Override
protected void onProgressUpdate(String... values) {
String address = values[0];
textSearching.setText(getString(R.string.repo_searching_address, address));
}
@Override
protected void onPostExecute(final String newAddress) {
if (addRepoDialog.isShowing()) {
if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
final View view = getLayoutInflater().inflate(R.layout.login, null);
final AlertDialog credentialsDialog = new AlertDialog.Builder(context)
.setView(view).create();
final EditText nameInput = (EditText) view.findViewById(R.id.edit_name);
final EditText passwordInput = (EditText) view.findViewById(R.id.edit_password);
if (username != null) {
nameInput.setText(username);
}
if (password != null) {
passwordInput.setText(password);
}
credentialsDialog.setTitle(R.string.login_title);
credentialsDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
getString(R.string.cancel),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss(); dialog.dismiss();
// cancel parent dialog, don't add repo // cancel parent dialog, don't add repo
addRepoDialog.cancel(); addRepoDialog.cancel();
}); }
});
credentialsDialog.setButton(DialogInterface.BUTTON_POSITIVE, credentialsDialog.setButton(DialogInterface.BUTTON_POSITIVE,
getString(R.string.ok), getString(R.string.ok),
(dialog, which) -> createNewRepo(newAddress, fingerprint, new DialogInterface.OnClickListener() {
nameInput.getText().toString(), @Override
passwordInput.getText().toString())); public void onClick(DialogInterface dialog, int which) {
createNewRepo(newAddress, fingerprint,
nameInput.getText().toString(),
passwordInput.getText().toString());
}
});
credentialsDialog.show(); credentialsDialog.show();
} else if (statusCode == refreshDialog) {
addRepoForm.setVisibility(View.VISIBLE); } else if (statusCode == REFRESH_DIALOG) {
positiveButton.setVisibility(View.VISIBLE); addRepoForm.setVisibility(View.VISIBLE);
textSearching.setText(""); positiveButton.setVisibility(View.VISIBLE);
skip.setText(R.string.cancel); textSearching.setText("");
skip.setOnClickListener(null); skip.setText(R.string.cancel);
validateRepoDetails(newAddress, fingerprint); skip.setOnClickListener(null);
} else { validateRepoDetails(newAddress, fingerprint);
// create repo without username/password } else {
createNewRepo(newAddress, fingerprint);
} // create repo without username/password
createNewRepo(newAddress, fingerprint);
} }
}); }
compositeDisposable.add(disposable); }
};
skip.setOnClickListener(v -> { skip.setOnClickListener(new View.OnClickListener() {
// Still proceed with adding the repo, just don't bother searching for @Override
// a better alternative than the one provided. public void onClick(View v) {
// The reason for this is that if they are not connected to the internet, // Still proceed with adding the repo, just don't bother searching for
// or their internet is playing up, then you'd have to wait for several // a better alternative than the one provided.
// connection timeouts before being able to proceed. // The reason for this is that if they are not connected to the internet,
createNewRepo(originalAddress, fingerprint); // or their internet is playing up, then you'd have to wait for several
disposable.dispose(); // connection timeouts before being able to proceed.
createNewRepo(originalAddress, fingerprint);
checker.cancel(false);
}
}); });
checker.execute(originalAddress);
} }
/** /**

View File

@ -53,7 +53,6 @@ import org.fdroid.fdroid.work.CleanCacheWorker;
import org.fdroid.fdroid.work.FDroidMetricsWorker; import org.fdroid.fdroid.work.FDroidMetricsWorker;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.preference.CheckBoxPreference; import androidx.preference.CheckBoxPreference;
import androidx.preference.EditTextPreference; import androidx.preference.EditTextPreference;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
@ -292,7 +291,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat
AppCompatActivity activity = (AppCompatActivity) getActivity(); AppCompatActivity activity = (AppCompatActivity) getActivity();
// Theme will be applied upon activity creation // Theme will be applied upon activity creation
if (activity != null) { if (activity != null) {
ActivityCompat.recreate(activity); activity.recreate();
} }
} }
break; break;

View File

@ -23,7 +23,6 @@ import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@ -39,6 +38,7 @@ import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.Schema.RepoTable; import org.fdroid.fdroid.data.Schema.RepoTable;
import org.fdroid.fdroid.qr.QrGenAsyncTask;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
@ -52,7 +52,6 @@ import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import io.reactivex.rxjava3.disposables.Disposable;
public class RepoDetailsActivity extends AppCompatActivity { public class RepoDetailsActivity extends AppCompatActivity {
private static final String TAG = "RepoDetailsActivity"; private static final String TAG = "RepoDetailsActivity";
@ -92,8 +91,6 @@ public class RepoDetailsActivity extends AppCompatActivity {
private MirrorAdapter adapterToNotify; private MirrorAdapter adapterToNotify;
private Disposable disposable;
/** /**
* Help function to make switching between two view states easier. * Help function to make switching between two view states easier.
* Perhaps there is a better way to do this. I recall that using Adobe * Perhaps there is a better way to do this. I recall that using Adobe
@ -144,19 +141,7 @@ public class RepoDetailsActivity extends AppCompatActivity {
Uri uri = Uri.parse(repo.address); Uri uri = Uri.parse(repo.address);
uri = uri.buildUpon().appendQueryParameter("fingerprint", repo.fingerprint).build(); uri = uri.buildUpon().appendQueryParameter("fingerprint", repo.fingerprint).build();
String qrUriString = uri.toString(); String qrUriString = uri.toString();
disposable = Utils.generateQrBitmap(this, qrUriString) new QrGenAsyncTask(this, R.id.qr_code).execute(qrUriString);
.subscribe(bitmap -> {
final ImageView qrCode = findViewById(R.id.qr_code);
if (qrCode != null) {
qrCode.setImageBitmap(bitmap);
}
});
}
@Override
protected void onDestroy() {
disposable.dispose();
super.onDestroy();
} }
@TargetApi(14) @TargetApi(14)

View File

@ -266,7 +266,6 @@ public class AppListActivity extends AppCompatActivity implements LoaderManager.
public void onSearchTermsChanged(@Nullable String category, @NonNull String searchTerms) { public void onSearchTermsChanged(@Nullable String category, @NonNull String searchTerms) {
this.category = category; this.category = category;
this.searchTerms = searchTerms; this.searchTerms = searchTerms;
appView.scrollToPosition(0);
getSupportLoaderManager().restartLoader(0, null, this); getSupportLoaderManager().restartLoader(0, null, this);
if (TextUtils.isEmpty(searchTerms)) { if (TextUtils.isEmpty(searchTerms)) {
removeSavedSearchSettings(this, SEARCH_TERMS_KEY); removeSavedSearchSettings(this, SEARCH_TERMS_KEY);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -34,6 +34,4 @@
<color name="perms_costs_money">#fff4511e</color> <color name="perms_costs_money">#fff4511e</color>
<color name="immersive_sys_ui">#33000000</color>
</resources> </resources>

View File

@ -4,25 +4,88 @@
<string-array name="default_repos"> <string-array name="default_repos">
<!-- name --> <!-- name -->
<item>BoBTV</item> <item>F-Droid Archive</item>
<!-- address --> <!-- address -->
<item>http://vps.k-world.me.uk:8088</item> <item>https://f-droid.org/archive</item>
<!-- description --> <!-- description -->
<item>The BobTV Repo <item>The archive repository of the F-Droid client. This contains older versions of
applications from the main repository.
</item> </item>
<!-- version --> <!-- version -->
<item>1</item> <item>13</item>
<!-- enabled --> <!-- enabled -->
<item>1</item> <item>0</item>
<!-- priority --> <!-- priority -->
<item>1</item> <item>1</item>
<!-- push requests --> <!-- push requests -->
<item>ignore</item> <item>ignore</item>
<!-- pubkey --> <!-- pubkey -->
<item> <item>
308204fb308202e3a0030201020204017b260a300d06092a864886f70d01010b0500302e3110300e060355040b1307462d44726f6964311a3018060355040313117670732e6b2d776f726c642e6d652e756b301e170d3231303632323131323730305a170d3438313130373131323730305a302e3110300e060355040b1307462d44726f6964311a3018060355040313117670732e6b2d776f726c642e6d652e756b30820222300d06092a864886f70d01010105000382020f003082020a0282020100a117a8fa419a4e1e7fc419696744e124e8ceab6e38da0c77820225b3006acaa44c3d8f2f30d66a1389b61b69467576b8a26cded04265aaebb781855a4564f2172857bfe5c6abe00a9e3635e2f5db91c16b619defef5ca28120e111f2a3303b5fab6e652b36ff94f16cf441e28ddcb17d7db71cd1c0cce0c4da1b003c1cca1f45368723cf9b79fbbdcd4d763b7748a499434a633912d22ec2cbf8e73cbc86c2583eea4c6cf6b7819ea4cd8f11551209befd43fc7225e8dc972d15bf0266618224a7ab0110d4c874abc44750d30a8b68624093255f21a72a0f76261167f7b87e03d7ee807677d91dffc91ae1a5b8716f286b9492472351ebe5a4f8b17be875ac60adcd2666d64c68f3eb1dfadc481f990eba379d141472ed51c6962241265f3a77f065c764d89e1283e0cc594e2b3a76bb5364cda080fd5c73d3bf5c3bcd63909cf0dfb489dc67f0752cb312e195b274a9a117ff4e50b0d09f98894136544fd214b2f7cf67f468cbe16966379858f08ba418fc330f578c50acd726ffc3c4904c42722a2ba55b214d78899c479d5cafebc3337ac6c485cc59c4aa162627e6796b081bd6c2ebe30933ed8a9facf13e97961f3368ac407ee92c5ee24fcdc219cb7e36bcd2fa4b4083ded7bf24973295bc6118608d63b4440e9b8c38f44118eb82bbe8d7dc1f48e56a4b5300762634e4d6c3e61a232262fa29c1705d8851fca004340f0203010001a321301f301d0603551d0e041604147ba21d05a77640f662caec346ab8e0c442486711300d06092a864886f70d01010b050003820201001d6e60d7e7d8f077458e1a315c03f3699c29797cac9d27b2e42c21fff2183d0dc95345b9359958696bb1ae298be1ce22359e899e9cef81eb66946a7abfe107cc537d3cb89e81b81886adc79b76f0f38c3d0d69a8741c4458e297cbe5419e1412b14e02f714bdb15cd29d2ba5daf11220d29b5ae7fc147922d3f9b8f31d50117af7bc870aa97fcba569b92799c3638c811413df3d46c4d6c003b3c14472d5690a107440691ccacdb4bc709ee80f548314bafabbe330eabbf9fd63f51c1fc1cdf49f6a7d28b8ada2dd75fd01c6fceae05d1a49467ba4339a4cfbd3dc470d572e84fdaafb96c9fc707f24a96db2695d6b9bfe5318ca0dd6e68f6c3d23734d680006e48deeeb3924d6f9bc1e4b304aa0021572aeee64b643bff40eadba7580845d127f797c501b27f7b1c456bfa36d33e9260f440b3902f59668028187d5df589d47bb3fe5c57f09dfc3a2c987c4b82ddc982633eebfb75cd696e95e46702101cee681579b1165a04aeb5e97c195fee189f629850bf8326473db8dce4429e3abb62275996a1162b6ebe20c36cd4fabd24c94596bb6e71f220e67aa3af28e6fb9ffeaf44c5254f53b5fd297ebcddb553aca9c4c844e1c294c4ad33f965f829ff866d317042182929ed27516ccdd2149861dac0b548db26b9d72a60e04e4b3698ab0427e3e4a77a92a2ff997a5b727ba298570995c00a0644ca48228d44966ab87678b 3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef
</item> </item>
<!-- name -->
<item>F-Droid</item>
<!-- address -->
<item>https://f-droid.org/repo</item>
<!-- description -->
<item>The official F-Droid Free Software repository. Everything in this repository is always built from the source code.
</item>
<!-- version -->
<item>13</item>
<!-- enabled -->
<item>1</item>
<!-- priority -->
<item>2</item>
<!-- push requests -->
<item>ignore</item>
<!-- pubkey -->
<item>
3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef
</item>
<!-- name -->
<item>Guardian Project Archive</item>
<!-- address -->
<item>https://guardianproject.info/fdroid/archive</item>
<!-- description -->
<item>The official repository of The Guardian Project apps for use with F-Droid client. This
contains older versions of applications from the main repository.
</item>
<!-- version -->
<item>13</item>
<!-- enabled -->
<item>0</item>
<!-- priority -->
<item>3</item>
<!-- push requests -->
<item>ignore</item>
<!-- pubkey -->
<item>
308205d8308203c0020900a397b4da7ecda034300d06092a864886f70d01010505003081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f301e170d3134303632363139333931385a170d3431313131303139333931385a3081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f30820222300d06092a864886f70d01010105000382020f003082020a0282020100b3cd79121b9b883843be3c4482e320809106b0a23755f1dd3c7f46f7d315d7bb2e943486d61fc7c811b9294dcc6b5baac4340f8db2b0d5e14749e7f35e1fc211fdbc1071b38b4753db201c314811bef885bd8921ad86facd6cc3b8f74d30a0b6e2e6e576f906e9581ef23d9c03e926e06d1f033f28bd1e21cfa6a0e3ff5c9d8246cf108d82b488b9fdd55d7de7ebb6a7f64b19e0d6b2ab1380a6f9d42361770d1956701a7f80e2de568acd0bb4527324b1e0973e89595d91c8cc102d9248525ae092e2c9b69f7414f724195b81427f28b1d3d09a51acfe354387915fd9521e8c890c125fc41a12bf34d2a1b304067ab7251e0e9ef41833ce109e76963b0b256395b16b886bca21b831f1408f836146019e7908829e716e72b81006610a2af08301de5d067c9e114a1e5759db8a6be6a3cc2806bcfe6fafd41b5bc9ddddb3dc33d6f605b1ca7d8a9e0ecdd6390d38906649e68a90a717bea80fa220170eea0c86fc78a7e10dac7b74b8e62045a3ecca54e035281fdc9fe5920a855fde3c0be522e3aef0c087524f13d973dff3768158b01a5800a060c06b451ec98d627dd052eda804d0556f60dbc490d94e6e9dea62ffcafb5beffbd9fc38fb2f0d7050004fe56b4dda0a27bc47554e1e0a7d764e17622e71f83a475db286bc7862deee1327e2028955d978272ea76bf0b88e70a18621aba59ff0c5993ef5f0e5d6b6b98e68b70203010001300d06092a864886f70d0101050500038202010079c79c8ef408a20d243d8bd8249fb9a48350dc19663b5e0fce67a8dbcb7de296c5ae7bbf72e98a2020fb78f2db29b54b0e24b181aa1c1d333cc0303685d6120b03216a913f96b96eb838f9bff125306ae3120af838c9fc07ebb5100125436bd24ec6d994d0bff5d065221871f8410daf536766757239bf594e61c5432c9817281b985263bada8381292e543a49814061ae11c92a316e7dc100327b59e3da90302c5ada68c6a50201bda1fcce800b53f381059665dbabeeb0b50eb22b2d7d2d9b0aa7488ca70e67ac6c518adb8e78454a466501e89d81a45bf1ebc350896f2c3ae4b6679ecfbf9d32960d4f5b493125c7876ef36158562371193f600bc511000a67bdb7c664d018f99d9e589868d103d7e0994f166b2ba18ff7e67d8c4da749e44dfae1d930ae5397083a51675c409049dfb626a96246c0015ca696e94ebb767a20147834bf78b07fece3f0872b057c1c519ff882501995237d8206b0b3832f78753ebd8dcbd1d3d9f5ba733538113af6b407d960ec4353c50eb38ab29888238da843cd404ed8f4952f59e4bbc0035fc77a54846a9d419179c46af1b4a3b7fc98e4d312aaa29b9b7d79e739703dc0fa41c7280d5587709277ffa11c3620f5fba985b82c238ba19b17ebd027af9424be0941719919f620dd3bb3c3f11638363708aa11f858e153cf3a69bce69978b90e4a273836100aa1e617ba455cd00426847f
</item>
<!-- name -->
<item>Guardian Project</item>
<!-- address -->
<item>https://guardianproject.info/fdroid/repo</item>
<!-- description -->
<item>The official app repository of The Guardian Project. Applications in this repository
are official binaries build by the original application developers and signed by the
same key as the APKs that are released in the Google Play store.
</item>
<!-- version -->
<item>13</item>
<!-- enabled -->
<item>0</item>
<!-- priority -->
<item>4</item>
<!-- push requests -->
<item>ignore</item>
<!-- pubkey -->
<item>
308205d8308203c0020900a397b4da7ecda034300d06092a864886f70d01010505003081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f301e170d3134303632363139333931385a170d3431313131303139333931385a3081ad310b30090603550406130255533111300f06035504080c084e657720596f726b3111300f06035504070c084e657720596f726b31143012060355040b0c0b4644726f6964205265706f31193017060355040a0c10477561726469616e2050726f6a656374311d301b06035504030c14677561726469616e70726f6a6563742e696e666f3128302606092a864886f70d0109011619726f6f7440677561726469616e70726f6a6563742e696e666f30820222300d06092a864886f70d01010105000382020f003082020a0282020100b3cd79121b9b883843be3c4482e320809106b0a23755f1dd3c7f46f7d315d7bb2e943486d61fc7c811b9294dcc6b5baac4340f8db2b0d5e14749e7f35e1fc211fdbc1071b38b4753db201c314811bef885bd8921ad86facd6cc3b8f74d30a0b6e2e6e576f906e9581ef23d9c03e926e06d1f033f28bd1e21cfa6a0e3ff5c9d8246cf108d82b488b9fdd55d7de7ebb6a7f64b19e0d6b2ab1380a6f9d42361770d1956701a7f80e2de568acd0bb4527324b1e0973e89595d91c8cc102d9248525ae092e2c9b69f7414f724195b81427f28b1d3d09a51acfe354387915fd9521e8c890c125fc41a12bf34d2a1b304067ab7251e0e9ef41833ce109e76963b0b256395b16b886bca21b831f1408f836146019e7908829e716e72b81006610a2af08301de5d067c9e114a1e5759db8a6be6a3cc2806bcfe6fafd41b5bc9ddddb3dc33d6f605b1ca7d8a9e0ecdd6390d38906649e68a90a717bea80fa220170eea0c86fc78a7e10dac7b74b8e62045a3ecca54e035281fdc9fe5920a855fde3c0be522e3aef0c087524f13d973dff3768158b01a5800a060c06b451ec98d627dd052eda804d0556f60dbc490d94e6e9dea62ffcafb5beffbd9fc38fb2f0d7050004fe56b4dda0a27bc47554e1e0a7d764e17622e71f83a475db286bc7862deee1327e2028955d978272ea76bf0b88e70a18621aba59ff0c5993ef5f0e5d6b6b98e68b70203010001300d06092a864886f70d0101050500038202010079c79c8ef408a20d243d8bd8249fb9a48350dc19663b5e0fce67a8dbcb7de296c5ae7bbf72e98a2020fb78f2db29b54b0e24b181aa1c1d333cc0303685d6120b03216a913f96b96eb838f9bff125306ae3120af838c9fc07ebb5100125436bd24ec6d994d0bff5d065221871f8410daf536766757239bf594e61c5432c9817281b985263bada8381292e543a49814061ae11c92a316e7dc100327b59e3da90302c5ada68c6a50201bda1fcce800b53f381059665dbabeeb0b50eb22b2d7d2d9b0aa7488ca70e67ac6c518adb8e78454a466501e89d81a45bf1ebc350896f2c3ae4b6679ecfbf9d32960d4f5b493125c7876ef36158562371193f600bc511000a67bdb7c664d018f99d9e589868d103d7e0994f166b2ba18ff7e67d8c4da749e44dfae1d930ae5397083a51675c409049dfb626a96246c0015ca696e94ebb767a20147834bf78b07fece3f0872b057c1c519ff882501995237d8206b0b3832f78753ebd8dcbd1d3d9f5ba733538113af6b407d960ec4353c50eb38ab29888238da843cd404ed8f4952f59e4bbc0035fc77a54846a9d419179c46af1b4a3b7fc98e4d312aaa29b9b7d79e739703dc0fa41c7280d5587709277ffa11c3620f5fba985b82c238ba19b17ebd027af9424be0941719919f620dd3bb3c3f11638363708aa11f858e153cf3a69bce69978b90e4a273836100aa1e617ba455cd00426847f
</item>
</string-array> </string-array>

View File

@ -69,9 +69,6 @@
<item name="bottomNavigationStyle">@style/Widget.MaterialComponents.BottomNavigationView.PrimarySurface</item> <item name="bottomNavigationStyle">@style/Widget.MaterialComponents.BottomNavigationView.PrimarySurface</item>
<item name="screenshotPlaceholderIconColor">#e8e8e8</item> <item name="screenshotPlaceholderIconColor">#e8e8e8</item>
<!--Platform attributes-->
<item name="android:statusBarColor">@color/immersive_sys_ui</item>
</style> </style>
<!-- light app theme with accent colors following roughly android design guidelines --> <!-- light app theme with accent colors following roughly android design guidelines -->

View File

@ -5,7 +5,7 @@ buildscript {
jcenter() // download from jCenter as last resort https://blog.autsoft.hu/a-confusing-dependency jcenter() // download from jCenter as last resort https://blog.autsoft.hu/a-confusing-dependency
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.2.1' classpath 'com.android.tools.build:gradle:3.6.3'
} }
} }
allprojects { allprojects {

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -3,5 +3,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-bin.zip
#distributionSha256Sum=e58cdff0cee6d9b422dcd08ebeb3177bc44eaa09bd9a2e838ff74c408fe1cbcd distributionSha256Sum=e58cdff0cee6d9b422dcd08ebeb3177bc44eaa09bd9a2e838ff74c408fe1cbcd