Merge branch '1.2.1-polish' into 'master'
1.2.1 polish Closes #577 and #440 See merge request fdroid/fdroidclient!674
This commit is contained in:
commit
9a33a751a1
@ -7,8 +7,6 @@ import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.fdroid.fdroid.compat.SupportedArchitectures;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
|
||||
@ -22,12 +20,11 @@ import java.util.Set;
|
||||
// find reasons why an apk may be incompatible with the user's device.
|
||||
public class CompatibilityChecker {
|
||||
|
||||
private static final String TAG = "Compatibility";
|
||||
public static final String TAG = "Compatibility";
|
||||
|
||||
private final Context context;
|
||||
private final Set<String> features;
|
||||
private final String[] cpuAbis;
|
||||
private final String cpuAbisDesc;
|
||||
private final boolean forceTouchApps;
|
||||
|
||||
public CompatibilityChecker(Context ctx) {
|
||||
@ -43,13 +40,6 @@ public class CompatibilityChecker {
|
||||
if (pm != null) {
|
||||
final FeatureInfo[] featureArray = pm.getSystemAvailableFeatures();
|
||||
if (featureArray != null) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
StringBuilder logMsg = new StringBuilder("Available device features:");
|
||||
for (FeatureInfo fi : pm.getSystemAvailableFeatures()) {
|
||||
logMsg.append('\n').append(fi.name);
|
||||
}
|
||||
Utils.debugLog(TAG, logMsg.toString());
|
||||
}
|
||||
for (FeatureInfo fi : pm.getSystemAvailableFeatures()) {
|
||||
features.add(fi.name);
|
||||
}
|
||||
@ -57,18 +47,6 @@ public class CompatibilityChecker {
|
||||
}
|
||||
|
||||
cpuAbis = SupportedArchitectures.getAbis();
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
boolean first = true;
|
||||
for (final String abi : cpuAbis) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
builder.append(", ");
|
||||
}
|
||||
builder.append(abi);
|
||||
}
|
||||
cpuAbisDesc = builder.toString();
|
||||
}
|
||||
|
||||
private boolean compatibleApi(@Nullable String[] nativecode) {
|
||||
@ -107,16 +85,11 @@ public class CompatibilityChecker {
|
||||
}
|
||||
if (!features.contains(feat)) {
|
||||
Collections.addAll(incompatibleReasons, feat.split(","));
|
||||
Utils.debugLog(TAG, apk.packageName + " vercode " + apk.versionCode
|
||||
+ " is incompatible based on lack of " + feat);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!compatibleApi(apk.nativecode)) {
|
||||
Collections.addAll(incompatibleReasons, apk.nativecode);
|
||||
Utils.debugLog(TAG, apk.packageName + " vercode " + apk.versionCode
|
||||
+ " only supports " + TextUtils.join(", ", apk.nativecode)
|
||||
+ " while your architectures are " + cpuAbisDesc);
|
||||
}
|
||||
|
||||
return incompatibleReasons;
|
||||
|
@ -36,6 +36,7 @@ import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.StrictMode;
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
@ -113,7 +114,9 @@ public class FDroidApp extends Application {
|
||||
|
||||
public static volatile int networkState = ConnectivityMonitorService.FLAG_NET_UNAVAILABLE;
|
||||
|
||||
private static volatile String lastWorkingMirror = null;
|
||||
public static final SubnetUtils.SubnetInfo UNSET_SUBNET_INFO = new SubnetUtils("0.0.0.0/32").getInfo();
|
||||
|
||||
private static volatile LongSparseArray<String> lastWorkingMirrorArray = new LongSparseArray<>(1);
|
||||
private static volatile int numTries = Integer.MAX_VALUE;
|
||||
private static volatile int timeout = 10000;
|
||||
|
||||
@ -230,7 +233,7 @@ public class FDroidApp extends Application {
|
||||
public static void initWifiSettings() {
|
||||
port = 8888;
|
||||
ipAddressString = null;
|
||||
subnetInfo = new SubnetUtils("0.0.0.0/32").getInfo();
|
||||
subnetInfo = UNSET_SUBNET_INFO;
|
||||
ssid = "";
|
||||
bssid = "";
|
||||
repo = new Repo();
|
||||
@ -242,6 +245,7 @@ public class FDroidApp extends Application {
|
||||
|
||||
public static String getMirror(String urlString, Repo repo2) throws IOException {
|
||||
if (repo2.hasMirrors()) {
|
||||
String lastWorkingMirror = lastWorkingMirrorArray.get(repo2.getId());
|
||||
if (lastWorkingMirror == null) {
|
||||
lastWorkingMirror = repo2.address;
|
||||
}
|
||||
@ -264,7 +268,7 @@ public class FDroidApp extends Application {
|
||||
String newUrl = urlString.replace(lastWorkingMirror, mirror);
|
||||
Utils.debugLog(TAG, "Trying mirror " + mirror + " after " + lastWorkingMirror + " failed," +
|
||||
" timeout=" + timeout / 1000 + "s");
|
||||
lastWorkingMirror = mirror;
|
||||
lastWorkingMirrorArray.put(repo2.getId(), mirror);
|
||||
numTries--;
|
||||
return newUrl;
|
||||
} else {
|
||||
@ -278,7 +282,9 @@ public class FDroidApp extends Application {
|
||||
|
||||
public static void resetMirrorVars() {
|
||||
// Reset last working mirror, numtries, and timeout
|
||||
lastWorkingMirror = null;
|
||||
for (int i = 0; i < lastWorkingMirrorArray.size(); i++) {
|
||||
lastWorkingMirrorArray.removeAt(i);
|
||||
}
|
||||
numTries = Integer.MAX_VALUE;
|
||||
timeout = 10000;
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import org.fdroid.fdroid.data.Schema.InstalledAppTable;
|
||||
import org.fdroid.fdroid.data.Schema.InstalledAppTable.Cols;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
public class InstalledAppProvider extends FDroidProvider {
|
||||
@ -81,6 +82,20 @@ public class InstalledAppProvider extends FDroidProvider {
|
||||
|
||||
private static final UriMatcher MATCHER = new UriMatcher(-1);
|
||||
|
||||
/**
|
||||
* Built-in apps that are signed by the various Android ROM keys.
|
||||
*
|
||||
* @see <a href="https://source.android.com/devices/tech/ota/sign_builds#certificates-keys">Certificates and private keys</a>
|
||||
*/
|
||||
private static final String[] SYSTEM_PACKAGES = {
|
||||
"android", // platform key
|
||||
"com.android.email", // test/release key
|
||||
"com.android.contacts", // shared key
|
||||
"com.android.providers.downloads", // media key
|
||||
};
|
||||
|
||||
private static String[] systemSignatures;
|
||||
|
||||
static {
|
||||
MATCHER.addURI(getAuthority(), null, CODE_LIST);
|
||||
MATCHER.addURI(getAuthority(), PATH_SEARCH + "/*", CODE_SEARCH);
|
||||
@ -117,6 +132,36 @@ public class InstalledAppProvider extends FDroidProvider {
|
||||
return packageName; // all else fails, return packageName
|
||||
}
|
||||
|
||||
/**
|
||||
* Add SQL selection statement to exclude {@link InstalledApp}s that were
|
||||
* signed by the platform/shared/media/testkey keys.
|
||||
*
|
||||
* @see <a href="https://source.android.com/devices/tech/ota/sign_builds#certificates-keys">Certificates and private keys</a>
|
||||
*/
|
||||
private QuerySelection selectNotSystemSignature(QuerySelection selection) {
|
||||
if (systemSignatures == null) {
|
||||
Log.i(TAG, "selectNotSystemSignature: systemSignature == null, querying for it");
|
||||
HashSet<String> signatures = new HashSet<>();
|
||||
for (String packageName : SYSTEM_PACKAGES) {
|
||||
Cursor cursor = query(InstalledAppProvider.getAppUri(packageName), new String[]{Cols.SIGNATURE},
|
||||
null, null, null);
|
||||
if (cursor != null) {
|
||||
if (cursor.moveToFirst()) {
|
||||
signatures.add(cursor.getString(cursor.getColumnIndex(Cols.SIGNATURE)));
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
systemSignatures = signatures.toArray(new String[signatures.size()]);
|
||||
}
|
||||
|
||||
Log.i(TAG, "excluding InstalledApps signed by system signatures");
|
||||
for (String systemSignature : systemSignatures) {
|
||||
selection = selection.add("NOT " + Cols.SIGNATURE + " IN (?)", new String[]{systemSignature});
|
||||
}
|
||||
return selection;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTableName() {
|
||||
return InstalledAppTable.NAME;
|
||||
@ -185,6 +230,7 @@ public class InstalledAppProvider extends FDroidProvider {
|
||||
QuerySelection selection = new QuerySelection(customSelection, selectionArgs);
|
||||
switch (MATCHER.match(uri)) {
|
||||
case CODE_LIST:
|
||||
selection = selectNotSystemSignature(selection);
|
||||
break;
|
||||
|
||||
case CODE_SINGLE:
|
||||
|
@ -23,6 +23,8 @@ import rx.subjects.PublishSubject;
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -146,7 +148,10 @@ public class InstalledAppProviderService extends IntentService {
|
||||
* Make sure that {@link InstalledAppProvider}, our database of installed apps,
|
||||
* is in sync with what the {@link PackageManager} tells us is installed. Once
|
||||
* completed, the relevant {@link android.content.ContentProvider}s will be
|
||||
* notified of any changes to installed statuses.
|
||||
* notified of any changes to installed statuses. The packages are processed
|
||||
* in alphabetically order so that "{@code android}" is processed first. That
|
||||
* is always present and signed by the system key, so it is the source of the
|
||||
* system key for comparing all packages.
|
||||
* <p>
|
||||
* The installed app cache could get out of sync, e.g. if F-Droid crashed/ or
|
||||
* ran out of battery half way through responding to {@link Intent#ACTION_PACKAGE_ADDED}.
|
||||
@ -169,6 +174,12 @@ public class InstalledAppProviderService extends IntentService {
|
||||
|
||||
List<PackageInfo> packageInfoList = context.getPackageManager()
|
||||
.getInstalledPackages(PackageManager.GET_SIGNATURES);
|
||||
Collections.sort(packageInfoList, new Comparator<PackageInfo>() {
|
||||
@Override
|
||||
public int compare(PackageInfo o1, PackageInfo o2) {
|
||||
return o1.packageName.compareTo(o2.packageName);
|
||||
}
|
||||
});
|
||||
for (PackageInfo packageInfo : packageInfoList) {
|
||||
if (cachedInfo.containsKey(packageInfo.packageName)) {
|
||||
if (packageInfo.lastUpdateTime < 1262300400000L // 2010-01-01 00:00
|
||||
@ -314,6 +325,11 @@ public class InstalledAppProviderService extends IntentService {
|
||||
context.getContentResolver().delete(uri, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fingerprint used to represent an APK signing key in F-Droid.
|
||||
* This is a custom fingerprint algorithm that was kind of accidentally
|
||||
* created, but is still in use.
|
||||
*/
|
||||
private static String getPackageSig(PackageInfo info) {
|
||||
if (info == null || info.signatures == null || info.signatures.length < 1) {
|
||||
return "";
|
||||
|
@ -481,6 +481,8 @@ public class SwapService extends Service {
|
||||
Utils.debugLog(TAG, "Creating swap service.");
|
||||
startForeground(NOTIFICATION, createNotification());
|
||||
|
||||
deleteAllSwapRepos();
|
||||
|
||||
CacheSwapAppsService.startCaching(this);
|
||||
|
||||
swapPreferences = getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE);
|
||||
@ -537,11 +539,11 @@ public class SwapService extends Service {
|
||||
Preferences.get().unregisterLocalRepoHttpsListeners(httpsEnabledListener);
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(onWifiChange);
|
||||
|
||||
if (!SwapService.wasBluetoothEnabledBeforeSwap()) {
|
||||
if (bluetoothAdapter != null && !wasBluetoothEnabledBeforeSwap()) {
|
||||
bluetoothAdapter.disable();
|
||||
}
|
||||
|
||||
if (!SwapService.wasWifiEnabledBeforeSwap()) {
|
||||
if (wifiManager != null && !wasWifiEnabledBeforeSwap()) {
|
||||
wifiManager.setWifiEnabled(false);
|
||||
}
|
||||
|
||||
@ -553,6 +555,8 @@ public class SwapService extends Service {
|
||||
}
|
||||
stopForeground(true);
|
||||
|
||||
deleteAllSwapRepos();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@ -568,7 +572,26 @@ public class SwapService extends Service {
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* For now, swap repos are only trusted as long as swapping is active. They
|
||||
* should have a long lived trust based on the signing key, but that requires
|
||||
* that the repos are stored in the database by fingerprint, not by URL address.
|
||||
*
|
||||
* @see <a href="https://gitlab.com/fdroid/fdroidclient/issues/295">TOFU in swap</a>
|
||||
* @see <a href="https://gitlab.com/fdroid/fdroidclient/issues/703">
|
||||
* signing key fingerprint should be sole ID for repos in the database</a>
|
||||
*/
|
||||
private void deleteAllSwapRepos() {
|
||||
for (Repo repo : RepoProvider.Helper.all(this)) {
|
||||
if (repo.isSwap) {
|
||||
Utils.debugLog(TAG, "Removing stale swap repo: " + repo.address + " - " + repo.fingerprint);
|
||||
RepoProvider.Helper.remove(this, repo.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initTimer() {
|
||||
// TODO replace by Android scheduler
|
||||
if (timer != null) {
|
||||
Utils.debugLog(TAG, "Cancelling existing timeout timer so timeout can be reset.");
|
||||
timer.cancel();
|
||||
|
@ -41,6 +41,10 @@ import java.util.Locale;
|
||||
* the current state because it means that something about the wifi has
|
||||
* changed. Having the {@code Thread} also makes it easy to kill work
|
||||
* that is in progress.
|
||||
* <p>
|
||||
* Some devices send multiple copies of given events, like a Moto G often
|
||||
* sends three {@code CONNECTED} events. So they have to be debounced to
|
||||
* keep the {@link #BROADCAST} useful.
|
||||
*/
|
||||
@SuppressWarnings("LineLength")
|
||||
public class WifiStateChangeService extends IntentService {
|
||||
@ -50,6 +54,7 @@ public class WifiStateChangeService extends IntentService {
|
||||
|
||||
private WifiManager wifiManager;
|
||||
private static WifiInfoThread wifiInfoThread;
|
||||
private static int previousWifiState = Integer.MIN_VALUE;
|
||||
|
||||
public WifiStateChangeService() {
|
||||
super("WifiStateChangeService");
|
||||
@ -70,16 +75,17 @@ public class WifiStateChangeService extends IntentService {
|
||||
Utils.debugLog(TAG, "received null Intent, ignoring");
|
||||
return;
|
||||
}
|
||||
Utils.debugLog(TAG, "WiFi change service started, clearing info about wifi state until we have figured it out again.");
|
||||
Utils.debugLog(TAG, "WiFi change service started.");
|
||||
NetworkInfo ni = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
|
||||
wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
|
||||
int wifiState = wifiManager.getWifiState();
|
||||
if (ni == null || ni.isConnected()) {
|
||||
Utils.debugLog(TAG, "ni == " + ni + " wifiState == " + printWifiState(wifiState));
|
||||
if (wifiState == WifiManager.WIFI_STATE_ENABLED
|
||||
if (previousWifiState != wifiState &&
|
||||
(wifiState == WifiManager.WIFI_STATE_ENABLED
|
||||
|| wifiState == WifiManager.WIFI_STATE_DISABLING // might be switching to hotspot
|
||||
|| wifiState == WifiManager.WIFI_STATE_DISABLED // might be hotspot
|
||||
|| wifiState == WifiManager.WIFI_STATE_UNKNOWN) { // might be hotspot
|
||||
|| wifiState == WifiManager.WIFI_STATE_UNKNOWN)) { // might be hotspot
|
||||
if (wifiInfoThread != null) {
|
||||
wifiInfoThread.interrupt();
|
||||
}
|
||||
@ -121,6 +127,10 @@ public class WifiStateChangeService extends IntentService {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (FDroidApp.ipAddressString == null
|
||||
|| FDroidApp.subnetInfo == FDroidApp.UNSET_SUBNET_INFO) {
|
||||
setIpInfoFromNetworkInterface();
|
||||
}
|
||||
} else if (wifiState == WifiManager.WIFI_STATE_DISABLED
|
||||
|| wifiState == WifiManager.WIFI_STATE_DISABLING) {
|
||||
// try once to see if its a hotspot
|
||||
@ -210,6 +220,16 @@ public class WifiStateChangeService extends IntentService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for known Wi-Fi, Hotspot, and local network interfaces and get
|
||||
* the IP Address info from it. This is necessary because network
|
||||
* interfaces in Hotspot/AP mode do not show up in the regular
|
||||
* {@link WifiManager} queries, and also on
|
||||
* {@link android.os.Build.VERSION_CODES#LOLLIPOP Android 5.0} and newer,
|
||||
* {@link WifiManager#getDhcpInfo()} returns an invalid netmask.
|
||||
*
|
||||
* @see <a href="https://issuetracker.google.com/issues/37015180">netmask of WifiManager.getDhcpInfo() is always zero on Android 5.0</a>
|
||||
*/
|
||||
private void setIpInfoFromNetworkInterface() {
|
||||
try {
|
||||
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
|
||||
@ -273,7 +293,10 @@ public class WifiStateChangeService extends IntentService {
|
||||
return "WIFI_STATE_ENABLED";
|
||||
case WifiManager.WIFI_STATE_UNKNOWN:
|
||||
return "WIFI_STATE_UNKNOWN";
|
||||
}
|
||||
return null;
|
||||
case Integer.MIN_VALUE:
|
||||
return "previous value unset";
|
||||
default:
|
||||
return "~not mapped~";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -425,7 +425,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
||||
})
|
||||
.create().show();
|
||||
} else {
|
||||
showSelectApps();
|
||||
showWifiQr();
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user