package org.fdroid.fdroid; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.FeatureInfo; 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; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; // Call getIncompatibleReasons(apk) on an instance of this class to // find reasons why an apk may be incompatible with the user's device. public class CompatibilityChecker { private 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 ignoreTouchscreen; public CompatibilityChecker(Context ctx) { context = ctx.getApplicationContext(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); ignoreTouchscreen = prefs.getBoolean(Preferences.PREF_IGN_TOUCH, false); PackageManager pm = ctx.getPackageManager(); features = new HashSet<>(); 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); } } } 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) { if (nativecode == null) { return true; } for (final String cpuAbi : cpuAbis) { for (String code : nativecode) { if (code.equals(cpuAbi)) { return true; } } } return false; } public List<String> getIncompatibleReasons(final Apk apk) { List<String> incompatibleReasons = new ArrayList<>(); if (Build.VERSION.SDK_INT < apk.minSdkVersion) { incompatibleReasons.add(context.getString( R.string.minsdk_or_later, Utils.getAndroidVersionName(apk.minSdkVersion))); } else if (Build.VERSION.SDK_INT > apk.maxSdkVersion) { incompatibleReasons.add(context.getString( R.string.up_to_maxsdk, Utils.getAndroidVersionName(apk.maxSdkVersion))); } if (apk.features != null) { for (final String feat : apk.features) { if (ignoreTouchscreen && "android.hardware.touchscreen".equals(feat)) { continue; } 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; } }