Compare commits
24 Commits
1.13-alpha
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c5fa250331 | ||
![]() |
a51588ddd2 | ||
![]() |
b066c40176 | ||
![]() |
bbc2c6b35a | ||
![]() |
42b0b576d1 | ||
![]() |
39f680bb24 | ||
![]() |
19ffd2b491 | ||
![]() |
a8efa1ac07 | ||
![]() |
240b7af585 | ||
![]() |
345915cdf4 | ||
![]() |
d7fb1e0044 | ||
![]() |
0d8018b10e | ||
![]() |
d9b443429d | ||
![]() |
e698f4f8a3 | ||
![]() |
c27e1a697e | ||
![]() |
d549fb905d | ||
![]() |
c758cb60d9 | ||
![]() |
eab5ef59b9 | ||
![]() |
c0a699e21e | ||
![]() |
242662d02a | ||
![]() |
e1ca1552f7 | ||
![]() |
93a160b40d | ||
![]() |
5fad229dbe | ||
![]() |
43a809490c |
@ -21,16 +21,16 @@ def basicApplicationId = "org.fdroid.basic"
|
|||||||
def privilegedExtensionApplicationId = '"org.fdroid.fdroid.privileged"'
|
def privilegedExtensionApplicationId = '"org.fdroid.fdroid.privileged"'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 30
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
versionCode 1013001
|
versionCode 1013001
|
||||||
versionName getVersionName()
|
versionName getVersionName()
|
||||||
|
|
||||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||||
minSdkVersion 22
|
minSdkVersion 24
|
||||||
//noinspection ExpiredTargetSdkVersion
|
//noinspection ExpiredTargetSdkVersion
|
||||||
targetSdkVersion 25
|
targetSdkVersion 28
|
||||||
/*
|
/*
|
||||||
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,10 +142,9 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation 'androidx.appcompat:appcompat:1.3.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'
|
||||||
@ -162,9 +161,11 @@ 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'
|
||||||
|
7
app/proguard-rules.pro
vendored
@ -31,13 +31,6 @@
|
|||||||
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.**
|
||||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 65 KiB |
@ -1,6 +1,5 @@
|
|||||||
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;
|
||||||
@ -13,11 +12,17 @@ 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;
|
||||||
@ -30,7 +35,6 @@ 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;
|
||||||
@ -41,13 +45,11 @@ 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
|
||||||
@ -109,46 +111,6 @@ 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.
|
||||||
@ -342,12 +304,15 @@ 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());
|
||||||
@ -397,6 +362,45 @@ 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
|
||||||
@ -420,6 +424,8 @@ 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);
|
||||||
|
@ -34,7 +34,18 @@ 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;
|
||||||
|
|
||||||
@ -54,7 +65,6 @@ 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;
|
||||||
@ -65,17 +75,8 @@ 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;
|
||||||
|
|
||||||
@ -118,6 +119,8 @@ 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));
|
||||||
}
|
}
|
||||||
@ -235,6 +238,7 @@ 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();
|
||||||
@ -929,18 +933,23 @@ 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);
|
|
||||||
|
|
||||||
// Replace all blacks with the background blue.
|
compositeDisposable.add(Utils.generateQrBitmap(this, qrUriString)
|
||||||
qrImage.setColorFilter(new LightingColorFilter(0xffffffff, ContextCompat.getColor(this,
|
.subscribe(qrBitmap -> {
|
||||||
R.color.swap_blue)));
|
qrImage.setImageBitmap(qrBitmap);
|
||||||
|
|
||||||
final View qrWarningMessage = container.findViewById(R.id.warning_qr_scanner);
|
// Replace all blacks with the background blue.
|
||||||
if (CameraCharacteristicsChecker.getInstance(this).hasAutofocus()) {
|
qrImage.setColorFilter(new LightingColorFilter(0xffffffff,
|
||||||
qrWarningMessage.setVisibility(View.GONE);
|
ContextCompat.getColor(this, R.color.swap_blue)));
|
||||||
} else {
|
|
||||||
qrWarningMessage.setVisibility(View.VISIBLE);
|
final View qrWarningMessage = container.findViewById(R.id.warning_qr_scanner);
|
||||||
}
|
if (CameraCharacteristicsChecker.getInstance(this).hasAutofocus()) {
|
||||||
|
qrWarningMessage.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
qrWarningMessage.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,10 @@ 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;
|
||||||
@ -31,10 +35,8 @@ 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.
|
||||||
@ -70,6 +72,8 @@ 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");
|
||||||
}
|
}
|
||||||
@ -82,6 +86,12 @@ 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);
|
||||||
@ -109,7 +119,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) {
|
||||||
UpdateService.scheduleIfStillOnWifi(this);
|
compositeDisposable.add(UpdateService.scheduleIfStillOnWifi(this).subscribe());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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="@string/app_name"
|
android:label="BobStore"
|
||||||
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">
|
||||||
|
@ -24,7 +24,7 @@ class ProgressBufferedInputStream extends BufferedInputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException {
|
public synchronized 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
|
||||||
|
@ -30,7 +30,6 @@ 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;
|
||||||
@ -51,15 +50,18 @@ 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 {
|
||||||
|
|
||||||
@ -215,42 +217,30 @@ 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.
|
||||||
*/
|
*/
|
||||||
public static void scheduleIfStillOnWifi(Context context) {
|
@NonNull
|
||||||
|
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;
|
return Completable.complete();
|
||||||
}
|
}
|
||||||
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) {
|
||||||
|
@ -27,6 +27,7 @@ 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;
|
||||||
@ -44,11 +45,15 @@ 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;
|
||||||
@ -95,7 +100,11 @@ 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 {
|
||||||
|
|
||||||
@ -986,6 +995,27 @@ 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.
|
||||||
|
@ -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 included later
|
* These can be added as part of the ROM ({@code /system} or {@code /product}
|
||||||
* by vendors/OEMs ({@code /vendor}, {@code /odm}, {@code /oem}). These are
|
* or included later by vendors/OEMs ({@code /vendor}, {@code /odm}, {@code /oem}).
|
||||||
* always added at a lower priority than the repos embedded in the APK via
|
* These are 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 has the lowest priority, then Vendor, ODM, and OEM.
|
* ROM (System) has the lowest priority, then Product, 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", "/vendor", "/odm", "/oem")) {
|
for (String root : Arrays.asList("/system", "/product", "/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()) {
|
||||||
|
@ -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 rx.functions.Action1;
|
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||||
import rx.schedulers.Schedulers;
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
import rx.subjects.PublishSubject;
|
import io.reactivex.rxjava3.subjects.PublishSubject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles all updates to {@link InstalledAppProvider}, whether checking the contents
|
* Handles all updates to {@link InstalledAppProvider}, whether checking the contents
|
||||||
@ -65,13 +65,15 @@ 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 notifing the users of this {@link android.content.ContentProvider}
|
* This is for notifying the users of this {@link android.content.ContentProvider}
|
||||||
* that the contents has changed. Since {@link Intent}s can come in slow
|
* that the contents have 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();
|
||||||
@ -81,17 +83,16 @@ 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...
|
||||||
packageChangeNotifier
|
compositeDisposable.add(
|
||||||
.subscribeOn(Schedulers.newThread())
|
packageChangeNotifier
|
||||||
.debounce(3, TimeUnit.SECONDS)
|
.subscribeOn(Schedulers.newThread())
|
||||||
.subscribe(new Action1<String>() {
|
.debounce(3, TimeUnit.SECONDS)
|
||||||
@Override
|
.subscribe(packageName -> {
|
||||||
public void call(String packageName) {
|
Utils.debugLog(TAG, "Notifying content providers to update relevant views.");
|
||||||
Utils.debugLog(TAG, "Notifying content providers (so they can update the relevant views).");
|
getContentResolver().notifyChange(AppProvider.getContentUri(), null);
|
||||||
getContentResolver().notifyChange(AppProvider.getContentUri(), null);
|
getContentResolver().notifyChange(ApkProvider.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
|
||||||
@ -100,14 +101,18 @@ 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.
|
||||||
packageChangeNotifier.subscribeOn(Schedulers.newThread())
|
compositeDisposable.add(
|
||||||
.subscribe(new Action1<String>() {
|
packageChangeNotifier
|
||||||
@Override
|
.subscribeOn(Schedulers.newThread())
|
||||||
public void call(String packageName) {
|
.subscribe(packageName -> getContentResolver()
|
||||||
getContentResolver()
|
.notifyChange(AppProvider.getHighestPriorityMetadataUri(packageName), null))
|
||||||
.notifyChange(AppProvider.getHighestPriorityMetadataUri(packageName), null);
|
);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
compositeDisposable.dispose();
|
||||||
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -243,12 +248,7 @@ 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 = new FilenameFilter() {
|
FilenameFilter filter = (dir, name) -> name.endsWith(".apk");
|
||||||
@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();
|
||||||
|
@ -128,6 +128,7 @@ 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.
|
||||||
|
@ -256,7 +256,7 @@ public abstract class Downloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mark(int readlimit) {
|
public synchronized void mark(int readlimit) {
|
||||||
toWrap.mark(readlimit);
|
toWrap.mark(readlimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
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;
|
||||||
@ -32,13 +31,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;
|
||||||
@ -50,17 +49,6 @@ 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;
|
||||||
@ -87,6 +75,22 @@ 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";
|
||||||
@ -107,6 +111,8 @@ 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();
|
||||||
@ -154,6 +160,12 @@ 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();
|
||||||
@ -571,10 +583,8 @@ 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);
|
||||||
@ -586,153 +596,117 @@ 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 AsyncTask<String, String, String> checker = new AsyncTask<String, String, String>() {
|
final int refreshDialog = Integer.MAX_VALUE;
|
||||||
|
final Disposable disposable = Single.fromCallable(() -> {
|
||||||
private int statusCode = -1;
|
int statusCode = -1;
|
||||||
private static final int REFRESH_DIALOG = Integer.MAX_VALUE;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String doInBackground(String... params) {
|
|
||||||
final String originalAddress = params[0];
|
|
||||||
|
|
||||||
if (fingerprintRepoMap.containsKey(fingerprint)) {
|
|
||||||
statusCode = REFRESH_DIALOG;
|
|
||||||
return originalAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (originalAddress.startsWith(ContentResolver.SCHEME_CONTENT)
|
|
||||||
|| originalAddress.startsWith(ContentResolver.SCHEME_FILE)) {
|
|
||||||
// TODO check whether there is read access
|
|
||||||
return originalAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
final String[] pathsToCheck = {"", "fdroid/repo", "repo"};
|
|
||||||
for (final String path : pathsToCheck) {
|
|
||||||
|
|
||||||
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 (urlRepoMap.containsKey(addressWithoutIndex)) {
|
|
||||||
statusCode = REFRESH_DIALOG;
|
|
||||||
return 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return originalAddress;
|
|
||||||
|
|
||||||
|
if (fingerprintRepoMap.containsKey(fingerprint)) {
|
||||||
|
statusCode = refreshDialog;
|
||||||
|
return Pair.create(statusCode, originalAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkForRepository(Uri indexUri) throws IOException {
|
if (originalAddress.startsWith(ContentResolver.SCHEME_CONTENT)
|
||||||
final URL url = new URL(indexUri.toString());
|
|| originalAddress.startsWith(ContentResolver.SCHEME_FILE)) {
|
||||||
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
// TODO check whether there is read access
|
||||||
connection.setRequestMethod("HEAD");
|
return Pair.create(statusCode, originalAddress);
|
||||||
|
|
||||||
statusCode = connection.getResponseCode();
|
|
||||||
|
|
||||||
return statusCode == HttpURLConnection.HTTP_UNAUTHORIZED
|
|
||||||
|| statusCode == HttpURLConnection.HTTP_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
final String[] pathsToCheck = {"", "fdroid/repo", "repo"};
|
||||||
protected void onProgressUpdate(String... values) {
|
for (final String path : pathsToCheck) {
|
||||||
String address = values[0];
|
Utils.debugLog(TAG, "Check for repo at " + originalAddress + " with suffix '" + path + "'");
|
||||||
textSearching.setText(getString(R.string.repo_searching_address, address));
|
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();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final URL url = new URL(uri.toString());
|
||||||
|
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
|
connection.setRequestMethod("HEAD");
|
||||||
|
|
||||||
|
statusCode = connection.getResponseCode();
|
||||||
|
|
||||||
|
if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED
|
||||||
|
|| statusCode == HttpURLConnection.HTTP_OK) {
|
||||||
|
Utils.debugLog(TAG, "Found F-Droid repo at " + addressWithoutIndex);
|
||||||
|
return Pair.create(statusCode, addressWithoutIndex);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Error while searching for repo at " + addressWithoutIndex, e);
|
||||||
|
return Pair.create(statusCode, 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;
|
||||||
|
|
||||||
@Override
|
if (addRepoDialog.isShowing()) {
|
||||||
protected void onPostExecute(final String newAddress) {
|
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 (addRepoDialog.isShowing()) {
|
if (username != null) {
|
||||||
|
nameInput.setText(username);
|
||||||
|
}
|
||||||
|
if (password != null) {
|
||||||
|
passwordInput.setText(password);
|
||||||
|
}
|
||||||
|
|
||||||
if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
|
credentialsDialog.setTitle(R.string.login_title);
|
||||||
|
credentialsDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
|
||||||
final View view = getLayoutInflater().inflate(R.layout.login, null);
|
getString(R.string.cancel), (dialog, which) -> {
|
||||||
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),
|
||||||
new DialogInterface.OnClickListener() {
|
(dialog, which) -> createNewRepo(newAddress, fingerprint,
|
||||||
@Override
|
nameInput.getText().toString(),
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
passwordInput.getText().toString()));
|
||||||
createNewRepo(newAddress, fingerprint,
|
|
||||||
nameInput.getText().toString(),
|
|
||||||
passwordInput.getText().toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
credentialsDialog.show();
|
credentialsDialog.show();
|
||||||
|
} else if (statusCode == refreshDialog) {
|
||||||
} else if (statusCode == REFRESH_DIALOG) {
|
addRepoForm.setVisibility(View.VISIBLE);
|
||||||
addRepoForm.setVisibility(View.VISIBLE);
|
positiveButton.setVisibility(View.VISIBLE);
|
||||||
positiveButton.setVisibility(View.VISIBLE);
|
textSearching.setText("");
|
||||||
textSearching.setText("");
|
skip.setText(R.string.cancel);
|
||||||
skip.setText(R.string.cancel);
|
skip.setOnClickListener(null);
|
||||||
skip.setOnClickListener(null);
|
validateRepoDetails(newAddress, fingerprint);
|
||||||
validateRepoDetails(newAddress, fingerprint);
|
} else {
|
||||||
} else {
|
// create repo without username/password
|
||||||
|
createNewRepo(newAddress, fingerprint);
|
||||||
// create repo without username/password
|
}
|
||||||
createNewRepo(newAddress, fingerprint);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
compositeDisposable.add(disposable);
|
||||||
};
|
|
||||||
|
|
||||||
skip.setOnClickListener(new View.OnClickListener() {
|
skip.setOnClickListener(v -> {
|
||||||
@Override
|
// Still proceed with adding the repo, just don't bother searching for
|
||||||
public void onClick(View v) {
|
// a better alternative than the one provided.
|
||||||
// Still proceed with adding the repo, just don't bother searching for
|
// The reason for this is that if they are not connected to the internet,
|
||||||
// a better alternative than the one provided.
|
// or their internet is playing up, then you'd have to wait for several
|
||||||
// The reason for this is that if they are not connected to the internet,
|
// connection timeouts before being able to proceed.
|
||||||
// or their internet is playing up, then you'd have to wait for several
|
createNewRepo(originalAddress, fingerprint);
|
||||||
// connection timeouts before being able to proceed.
|
disposable.dispose();
|
||||||
|
|
||||||
createNewRepo(originalAddress, fingerprint);
|
|
||||||
checker.cancel(false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
checker.execute(originalAddress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,6 +53,7 @@ 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;
|
||||||
@ -291,7 +292,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) {
|
||||||
activity.recreate();
|
ActivityCompat.recreate(activity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -23,6 +23,7 @@ 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;
|
||||||
|
|
||||||
@ -38,7 +39,6 @@ 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,6 +52,7 @@ 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";
|
||||||
@ -91,6 +92,8 @@ 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
|
||||||
@ -141,7 +144,19 @@ 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();
|
||||||
new QrGenAsyncTask(this, R.id.qr_code).execute(qrUriString);
|
disposable = Utils.generateQrBitmap(this, 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)
|
||||||
|
@ -266,6 +266,7 @@ 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);
|
||||||
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 65 KiB |
@ -34,4 +34,6 @@
|
|||||||
|
|
||||||
<color name="perms_costs_money">#fff4511e</color>
|
<color name="perms_costs_money">#fff4511e</color>
|
||||||
|
|
||||||
|
<color name="immersive_sys_ui">#33000000</color>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -4,88 +4,25 @@
|
|||||||
<string-array name="default_repos">
|
<string-array name="default_repos">
|
||||||
|
|
||||||
<!-- name -->
|
<!-- name -->
|
||||||
<item>F-Droid Archive</item>
|
<item>BoBTV</item>
|
||||||
<!-- address -->
|
<!-- address -->
|
||||||
<item>https://f-droid.org/archive</item>
|
<item>http://vps.k-world.me.uk:8088</item>
|
||||||
<!-- description -->
|
<!-- description -->
|
||||||
<item>The archive repository of the F-Droid client. This contains older versions of
|
<item>The BobTV Repo
|
||||||
applications from the main repository.
|
|
||||||
</item>
|
</item>
|
||||||
<!-- version -->
|
<!-- version -->
|
||||||
<item>13</item>
|
<item>1</item>
|
||||||
<!-- enabled -->
|
<!-- enabled -->
|
||||||
<item>0</item>
|
<item>1</item>
|
||||||
<!-- priority -->
|
<!-- priority -->
|
||||||
<item>1</item>
|
<item>1</item>
|
||||||
<!-- push requests -->
|
<!-- push requests -->
|
||||||
<item>ignore</item>
|
<item>ignore</item>
|
||||||
<!-- pubkey -->
|
<!-- pubkey -->
|
||||||
<item>
|
<item>
|
||||||
3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef
|
308204fb308202e3a0030201020204017b260a300d06092a864886f70d01010b0500302e3110300e060355040b1307462d44726f6964311a3018060355040313117670732e6b2d776f726c642e6d652e756b301e170d3231303632323131323730305a170d3438313130373131323730305a302e3110300e060355040b1307462d44726f6964311a3018060355040313117670732e6b2d776f726c642e6d652e756b30820222300d06092a864886f70d01010105000382020f003082020a0282020100a117a8fa419a4e1e7fc419696744e124e8ceab6e38da0c77820225b3006acaa44c3d8f2f30d66a1389b61b69467576b8a26cded04265aaebb781855a4564f2172857bfe5c6abe00a9e3635e2f5db91c16b619defef5ca28120e111f2a3303b5fab6e652b36ff94f16cf441e28ddcb17d7db71cd1c0cce0c4da1b003c1cca1f45368723cf9b79fbbdcd4d763b7748a499434a633912d22ec2cbf8e73cbc86c2583eea4c6cf6b7819ea4cd8f11551209befd43fc7225e8dc972d15bf0266618224a7ab0110d4c874abc44750d30a8b68624093255f21a72a0f76261167f7b87e03d7ee807677d91dffc91ae1a5b8716f286b9492472351ebe5a4f8b17be875ac60adcd2666d64c68f3eb1dfadc481f990eba379d141472ed51c6962241265f3a77f065c764d89e1283e0cc594e2b3a76bb5364cda080fd5c73d3bf5c3bcd63909cf0dfb489dc67f0752cb312e195b274a9a117ff4e50b0d09f98894136544fd214b2f7cf67f468cbe16966379858f08ba418fc330f578c50acd726ffc3c4904c42722a2ba55b214d78899c479d5cafebc3337ac6c485cc59c4aa162627e6796b081bd6c2ebe30933ed8a9facf13e97961f3368ac407ee92c5ee24fcdc219cb7e36bcd2fa4b4083ded7bf24973295bc6118608d63b4440e9b8c38f44118eb82bbe8d7dc1f48e56a4b5300762634e4d6c3e61a232262fa29c1705d8851fca004340f0203010001a321301f301d0603551d0e041604147ba21d05a77640f662caec346ab8e0c442486711300d06092a864886f70d01010b050003820201001d6e60d7e7d8f077458e1a315c03f3699c29797cac9d27b2e42c21fff2183d0dc95345b9359958696bb1ae298be1ce22359e899e9cef81eb66946a7abfe107cc537d3cb89e81b81886adc79b76f0f38c3d0d69a8741c4458e297cbe5419e1412b14e02f714bdb15cd29d2ba5daf11220d29b5ae7fc147922d3f9b8f31d50117af7bc870aa97fcba569b92799c3638c811413df3d46c4d6c003b3c14472d5690a107440691ccacdb4bc709ee80f548314bafabbe330eabbf9fd63f51c1fc1cdf49f6a7d28b8ada2dd75fd01c6fceae05d1a49467ba4339a4cfbd3dc470d572e84fdaafb96c9fc707f24a96db2695d6b9bfe5318ca0dd6e68f6c3d23734d680006e48deeeb3924d6f9bc1e4b304aa0021572aeee64b643bff40eadba7580845d127f797c501b27f7b1c456bfa36d33e9260f440b3902f59668028187d5df589d47bb3fe5c57f09dfc3a2c987c4b82ddc982633eebfb75cd696e95e46702101cee681579b1165a04aeb5e97c195fee189f629850bf8326473db8dce4429e3abb62275996a1162b6ebe20c36cd4fabd24c94596bb6e71f220e67aa3af28e6fb9ffeaf44c5254f53b5fd297ebcddb553aca9c4c844e1c294c4ad33f965f829ff866d317042182929ed27516ccdd2149861dac0b548db26b9d72a60e04e4b3698ab0427e3e4a77a92a2ff997a5b727ba298570995c00a0644ca48228d44966ab87678b
|
||||||
</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>
|
||||||
|
|
||||||
|
@ -69,6 +69,9 @@
|
|||||||
<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 -->
|
||||||
|
@ -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:3.6.3'
|
classpath 'com.android.tools.build:gradle:4.2.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allprojects {
|
allprojects {
|
||||||
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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.4.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
|
||||||
distributionSha256Sum=e58cdff0cee6d9b422dcd08ebeb3177bc44eaa09bd9a2e838ff74c408fe1cbcd
|
#distributionSha256Sum=e58cdff0cee6d9b422dcd08ebeb3177bc44eaa09bd9a2e838ff74c408fe1cbcd
|
||||||
|