Store installed app signature in cache

This means we can fetch the signatures only once instead of every time we need
them. Start by using the cache in AppDetails.
This commit is contained in:
Daniel Martí 2015-10-22 19:42:39 +02:00
parent bb9426763c
commit eefbee969e
9 changed files with 62 additions and 51 deletions

View File

@ -30,7 +30,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@ -96,7 +95,6 @@ import org.fdroid.fdroid.net.AsyncDownloaderFromAndroid;
import org.fdroid.fdroid.net.Downloader;
import java.io.File;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.List;
@ -104,8 +102,6 @@ interface AppDetailsData {
App getApp();
AppDetails.ApkListAdapter getApks();
String getInstalledSigHash();
}
/**
@ -188,8 +184,8 @@ public class AppDetails extends AppCompatActivity implements ProgressListener, A
return getString(R.string.app_not_installed);
}
// Definitely installed this version.
if (mInstalledSigID != null && apk.sig != null
&& apk.sig.equals(mInstalledSigID)) {
if (app.installedSig != null && apk.sig != null
&& apk.sig.equals(app.installedSig)) {
return getString(R.string.app_installed);
}
// Installed the same version, but from someplace else.
@ -447,9 +443,6 @@ public class AppDetails extends AppCompatActivity implements ProgressListener, A
}
// The signature of the installed version.
private String mInstalledSigID;
@Override
protected void onStart() {
super.onStart();
@ -601,7 +594,6 @@ public class AppDetails extends AppCompatActivity implements ProgressListener, A
* like that) and then finish the activity.
*/
private void setApp(App newApp) {
if (newApp == null) {
Toast.makeText(this, R.string.no_such_app, Toast.LENGTH_LONG).show();
finish();
@ -612,21 +604,6 @@ public class AppDetails extends AppCompatActivity implements ProgressListener, A
startingIgnoreAll = app.ignoreAllUpdates;
startingIgnoreThis = app.ignoreThisUpdate;
// Get the signature of the installed package...
mInstalledSigID = null;
if (app.isInstalled()) {
try {
PackageInfo pi = mPm.getPackageInfo(app.id, PackageManager.GET_SIGNATURES);
Hasher hash = new Hasher("MD5", pi.signatures[0].toCharsString().getBytes());
mInstalledSigID = hash.getHash();
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Failed to get installed signature");
} catch (NoSuchAlgorithmException e) {
Log.w(TAG, "Failed to calculate signature MD5 sum");
}
}
}
private void refreshApkList() {
@ -853,8 +830,8 @@ public class AppDetails extends AppCompatActivity implements ProgressListener, A
alert.show();
return;
}
if (mInstalledSigID != null && apk.sig != null
&& !apk.sig.equals(mInstalledSigID)) {
if (app.installedSig != null && apk.sig != null
&& !apk.sig.equals(app.installedSig)) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.SignatureMismatch).setPositiveButton(
R.string.ok,
@ -1044,11 +1021,6 @@ public class AppDetails extends AppCompatActivity implements ProgressListener, A
return adapter;
}
@Override
public String getInstalledSigHash() {
return mInstalledSigID;
}
public static class AppDetailsSummaryFragment extends Fragment {
protected final Preferences prefs;
@ -1092,10 +1064,6 @@ public class AppDetails extends AppCompatActivity implements ProgressListener, A
return data.getApks();
}
protected String getInstalledSigHash() {
return data.getInstalledSigHash();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
@ -1408,11 +1376,11 @@ public class AppDetails extends AppCompatActivity implements ProgressListener, A
return;
}
App app = getApp();
TextView signatureView = (TextView) view.findViewById(R.id.signature);
String sig = getInstalledSigHash();
if (prefs.expertMode() && !TextUtils.isEmpty(sig)) {
if (prefs.expertMode() && !TextUtils.isEmpty(app.installedSig)) {
signatureView.setVisibility(View.VISIBLE);
signatureView.setText("Signed: " + sig);
signatureView.setText("Signed: " + app.installedSig);
} else {
signatureView.setVisibility(View.GONE);
}

View File

@ -100,6 +100,8 @@ public class App extends ValueObject implements Comparable<App> {
public Apk installedApk; // might be null if not installed
public String installedSig;
public boolean system;
public boolean updatedSystemApp;
@ -209,6 +211,9 @@ public class App extends ValueObject implements Comparable<App> {
case AppProvider.DataColumns.InstalledApp.VERSION_NAME:
installedVersionName = cursor.getString(i);
break;
case AppProvider.DataColumns.InstalledApp.SIGNATURE:
installedSig = cursor.getString(i);
break;
case "_id":
break;
default:

View File

@ -209,6 +209,7 @@ public class AppProvider extends FDroidProvider {
interface InstalledApp {
String VERSION_CODE = "installedVersionCode";
String VERSION_NAME = "installedVersionName";
String SIGNATURE = "installedSig";
}
String[] ALL = {
@ -220,6 +221,7 @@ public class AppProvider extends FDroidProvider {
IGNORE_THISUPDATE, ICON_URL, ICON_URL_LARGE,
SUGGESTED_VERSION_CODE, SuggestedApk.VERSION,
InstalledApp.VERSION_CODE, InstalledApp.VERSION_NAME,
InstalledApp.SIGNATURE,
};
}
@ -354,6 +356,9 @@ public class AppProvider extends FDroidProvider {
case DataColumns.InstalledApp.VERSION_CODE:
addInstalledAppVersionCode();
break;
case DataColumns.InstalledApp.SIGNATURE:
addInstalledSig();
break;
case DataColumns._COUNT:
appendCountField();
break;
@ -402,6 +407,13 @@ public class AppProvider extends FDroidProvider {
);
}
private void addInstalledSig() {
addInstalledAppField(
InstalledAppProvider.DataColumns.SIGNATURE,
DataColumns.InstalledApp.SIGNATURE
);
}
private void addInstalledAppField(String fieldName, String alias) {
leftJoinToInstalledTable();
appendField(fieldName, "installed", alias);

View File

@ -97,10 +97,12 @@ public class DBHelper extends SQLiteOpenHelper {
+ InstalledAppProvider.DataColumns.APP_ID + " TEXT NOT NULL PRIMARY KEY, "
+ InstalledAppProvider.DataColumns.VERSION_CODE + " INT NOT NULL, "
+ InstalledAppProvider.DataColumns.VERSION_NAME + " TEXT NOT NULL, "
+ InstalledAppProvider.DataColumns.APPLICATION_LABEL + " TEXT NOT NULL "
+ InstalledAppProvider.DataColumns.APPLICATION_LABEL + " TEXT NOT NULL, "
+ InstalledAppProvider.DataColumns.SIGNATURE + " TEXT NOT NULL "
+ " );";
private static final String DROP_TABLE_INSTALLED_APP = "DROP TABLE " + TABLE_INSTALLED_APP + ";";
private static final int DB_VERSION = 50;
private static final int DB_VERSION = 51;
private final Context context;
@ -278,11 +280,11 @@ public class DBHelper extends SQLiteOpenHelper {
renameRepoId(db, oldVersion);
populateRepoNames(db, oldVersion);
if (oldVersion < 43) createInstalledApp(db);
addAppLabelToInstalledCache(db, oldVersion);
addIsSwapToRepo(db, oldVersion);
addChangelogToApp(db, oldVersion);
addIconUrlLargeToApp(db, oldVersion);
updateIconUrlLarge(db, oldVersion);
recreateInstalledCache(db, oldVersion);
}
/**
@ -478,13 +480,11 @@ public class DBHelper extends SQLiteOpenHelper {
db.execSQL(CREATE_TABLE_INSTALLED_APP);
}
private void addAppLabelToInstalledCache(SQLiteDatabase db, int oldVersion) {
if (oldVersion < 45) {
Utils.debugLog(TAG, "Adding applicationLabel to installed app table. " +
"Turns out we will need to repopulate the cache after doing this, " +
"so just dropping and recreating the table (instead of altering and adding a column). " +
"This will force the entire cache to be rebuilt, including app names.");
db.execSQL("DROP TABLE fdroid_installedApp;");
// If any column was added or removed, just drop the table, create it
// again and let the cache be filled from scratch again.
private void recreateInstalledCache(SQLiteDatabase db, int oldVersion) {
if (oldVersion < 51) {
db.execSQL(DROP_TABLE_INSTALLED_APP);
createInstalledApp(db);
}
}

View File

@ -4,6 +4,7 @@ import android.content.ContentProviderOperation;
import android.content.Context;
import android.content.OperationApplicationException;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.RemoteException;
@ -110,7 +111,8 @@ public class InstalledAppCacheUpdater {
Map<String, Integer> cachedInfo = InstalledAppProvider.Helper.all(context);
List<PackageInfo> installedPackages = context.getPackageManager().getInstalledPackages(0);
List<PackageInfo> installedPackages = context.getPackageManager()
.getInstalledPackages(PackageManager.GET_SIGNATURES);
for (PackageInfo appInfo : installedPackages) {
toInsert.add(appInfo);
if (cachedInfo.containsKey(appInfo.packageName)) {
@ -137,6 +139,8 @@ public class InstalledAppCacheUpdater {
.withValue(InstalledAppProvider.DataColumns.VERSION_NAME, info.versionName)
.withValue(InstalledAppProvider.DataColumns.APPLICATION_LABEL,
InstalledAppProvider.getApplicationLabel(context, info.packageName))
.withValue(InstalledAppProvider.DataColumns.SIGNATURE,
InstalledAppProvider.getPackageSig(info))
.build();
ops.add(op);
}

View File

@ -4,15 +4,19 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Hasher;
import org.fdroid.fdroid.Utils;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
@ -59,9 +63,11 @@ public class InstalledAppProvider extends FDroidProvider {
String VERSION_CODE = "versionCode";
String VERSION_NAME = "versionName";
String APPLICATION_LABEL = "applicationLabel";
String SIGNATURE = "sig";
String[] ALL = {
_ID, APP_ID, VERSION_CODE, VERSION_NAME, APPLICATION_LABEL,
SIGNATURE,
};
}
@ -106,6 +112,18 @@ public class InstalledAppProvider extends FDroidProvider {
return packageName; // all else fails, return id
}
public static String getPackageSig(PackageInfo info) {
Signature sig = info.signatures[0];
String sigHash = "";
try {
Hasher hash = new Hasher("MD5", sig.toCharsString().getBytes());
sigHash = hash.getHash();
} catch (NoSuchAlgorithmException e) {
// ignore
}
return sigHash;
}
@Override
protected String getTableName() {
return DBHelper.TABLE_INSTALLED_APP;

View File

@ -57,6 +57,8 @@ public class PackageAddedReceiver extends PackageReceiver {
values.put(InstalledAppProvider.DataColumns.VERSION_NAME, info.versionName);
values.put(InstalledAppProvider.DataColumns.APPLICATION_LABEL,
InstalledAppProvider.getApplicationLabel(context, appId));
values.put(InstalledAppProvider.DataColumns.SIGNATURE,
InstalledAppProvider.getPackageSig(info));
context.getContentResolver().insert(uri, values);
}

View File

@ -40,7 +40,7 @@ abstract class PackageReceiver extends BroadcastReceiver {
protected PackageInfo getPackageInfo(Context context, String appId) {
PackageInfo info = null;
try {
info = context.getPackageManager().getPackageInfo(appId, 0);
info = context.getPackageManager().getPackageInfo(appId, PackageManager.GET_SIGNATURES);
} catch (PackageManager.NameNotFoundException e) {
// ignore
}

View File

@ -59,6 +59,8 @@ public class PackageUpgradedReceiver extends PackageReceiver {
values.put(InstalledAppProvider.DataColumns.VERSION_NAME, info.versionName);
values.put(InstalledAppProvider.DataColumns.APPLICATION_LABEL,
InstalledAppProvider.getApplicationLabel(context, appId));
values.put(InstalledAppProvider.DataColumns.SIGNATURE,
InstalledAppProvider.getPackageSig(info));
context.getContentResolver().insert(uri, values);
}