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:
Hans-Christoph Steiner 2018-04-18 17:57:09 +00:00
commit 9a33a751a1
7 changed files with 129 additions and 42 deletions

View File

@ -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;

View File

@ -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;
}

View File

@ -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:

View File

@ -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 "";

View File

@ -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();

View File

@ -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";
case Integer.MIN_VALUE:
return "previous value unset";
default:
return "~not mapped~";
}
return null;
}
}

View File

@ -425,7 +425,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
})
.create().show();
} else {
showSelectApps();
showWifiQr();
}
}