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
	 Hans-Christoph Steiner
						Hans-Christoph Steiner