diff --git a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java index 5ea9e1a6e..47733d7f2 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/AppProvider.java @@ -250,7 +250,7 @@ public class AppProvider extends FDroidProvider { join( InstalledAppTable.NAME, "installed", - "installed." + InstalledAppTable.Cols.PACKAGE_NAME + " = " + PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME); + "installed." + InstalledAppTable.Cols.PACKAGE_ID + " = " + PackageTable.NAME + "." + PackageTable.Cols.ROW_ID); requiresInstalledTable = true; } } @@ -270,7 +270,7 @@ public class AppProvider extends FDroidProvider { leftJoin( InstalledAppTable.NAME, "installed", - "installed." + InstalledAppTable.Cols.PACKAGE_NAME + " = " + PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME); + "installed." + InstalledAppTable.Cols.PACKAGE_ID + " = " + PackageTable.NAME + "." + PackageTable.Cols.ROW_ID); requiresInstalledTable = true; } } @@ -959,8 +959,9 @@ public class AppProvider extends FDroidProvider { * @see #updateSuggestedFromLatest() */ private void updateSuggestedFromUpstream() { - Utils.debugLog(TAG, "Calculating suggested versions for all apps which specify an upstream version code."); + Utils.debugLog(TAG, "Calculating suggested versions for all NON-INSTALLED apps which specify an upstream version code."); + Utils.Profiler profiler = new Utils.Profiler("UpdateSuggestedApks"); final String apk = getApkTableName(); final String app = getTableName(); @@ -986,6 +987,7 @@ public class AppProvider extends FDroidProvider { " WHERE " + Cols.UPSTREAM_VERSION_CODE + " > 0 "; LoggingQuery.execSQL(db(), updateSql); + profiler.log("Done"); } /** @@ -1029,7 +1031,7 @@ public class AppProvider extends FDroidProvider { " FROM " + InstalledAppTable.NAME + " AS installed " + " JOIN " + PackageTable.NAME + " AS pkg ON " + " (pkg." + PackageTable.Cols.ROW_ID + " = " + appTable + "." + Cols.PACKAGE_ID + " AND " + - " installed." + InstalledAppTable.Cols.PACKAGE_NAME + " = pkg." + PackageTable.Cols.PACKAGE_NAME + ") " + + " installed." + InstalledAppTable.Cols.PACKAGE_ID + " = pkg." + PackageTable.Cols.ROW_ID + ") " + ")"; // Ideally, the check below would actually be written as: diff --git a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java index 6bca3d3d8..555d39f90 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java +++ b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java @@ -182,7 +182,7 @@ class DBHelper extends SQLiteOpenHelper { private static final String CREATE_TABLE_INSTALLED_APP = "CREATE TABLE " + InstalledAppTable.NAME + " ( " - + InstalledAppTable.Cols.PACKAGE_NAME + " TEXT NOT NULL PRIMARY KEY, " + + InstalledAppTable.Cols.PACKAGE_ID + " INT NOT NULL UNIQUE, " + InstalledAppTable.Cols.VERSION_CODE + " INT NOT NULL, " + InstalledAppTable.Cols.VERSION_NAME + " TEXT NOT NULL, " + InstalledAppTable.Cols.APPLICATION_LABEL + " TEXT NOT NULL, " @@ -192,7 +192,7 @@ class DBHelper extends SQLiteOpenHelper { + InstalledAppTable.Cols.HASH + " TEXT NOT NULL" + " );"; - protected static final int DB_VERSION = 70; + protected static final int DB_VERSION = 71; private final Context context; @@ -277,6 +277,28 @@ class DBHelper extends SQLiteOpenHelper { recalculatePreferredMetadata(db, oldVersion); addWhatsNewAndVideo(db, oldVersion); dropApkPrimaryKey(db, oldVersion); + addIntegerPrimaryKeyToInstalledApps(db, oldVersion); + } + + private void addIntegerPrimaryKeyToInstalledApps(SQLiteDatabase db, int oldVersion) { + if (oldVersion >= 71) { + return; + } + + Log.i(TAG, "Replacing primary key on installed app table with integer for performance."); + + db.beginTransaction(); + try { + if (tableExists(db, Schema.InstalledAppTable.NAME)) { + db.execSQL("DROP TABLE " + Schema.InstalledAppTable.NAME); + } + + db.execSQL(CREATE_TABLE_INSTALLED_APP); + ensureIndexes(db); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } } private void dropApkPrimaryKey(SQLiteDatabase db, int oldVersion) { @@ -1061,9 +1083,11 @@ class DBHelper extends SQLiteOpenHelper { AppPrefsTable.Cols.IGNORE_THIS_UPDATE + ");"); } - Utils.debugLog(TAG, "Ensuring indexes exist for " + InstalledAppTable.NAME); - db.execSQL("CREATE INDEX IF NOT EXISTS installedApp_appId_vercode on " + InstalledAppTable.NAME + " (" + - InstalledAppTable.Cols.PACKAGE_NAME + ", " + InstalledAppTable.Cols.VERSION_CODE + ");"); + if (columnExists(db, InstalledAppTable.NAME, InstalledAppTable.Cols.PACKAGE_ID)) { + Utils.debugLog(TAG, "Ensuring indexes exist for " + InstalledAppTable.NAME); + db.execSQL("CREATE INDEX IF NOT EXISTS installedApp_packageId_vercode on " + InstalledAppTable.NAME + " (" + + InstalledAppTable.Cols.PACKAGE_ID + ", " + InstalledAppTable.Cols.VERSION_CODE + ");"); + } Utils.debugLog(TAG, "Ensuring indexes exist for " + RepoTable.NAME); db.execSQL("CREATE INDEX IF NOT EXISTS repo_id_isSwap on " + RepoTable.NAME + " (" + diff --git a/app/src/main/java/org/fdroid/fdroid/data/InstalledApp.java b/app/src/main/java/org/fdroid/fdroid/data/InstalledApp.java index ec9aec9fd..20edcd367 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/InstalledApp.java +++ b/app/src/main/java/org/fdroid/fdroid/data/InstalledApp.java @@ -24,7 +24,7 @@ public class InstalledApp extends ValueObject { case Schema.InstalledAppTable.Cols._ID: id = cursor.getLong(i); break; - case Schema.InstalledAppTable.Cols.PACKAGE_NAME: + case Schema.InstalledAppTable.Cols.Package.NAME: packageName = cursor.getString(i); break; case Schema.InstalledAppTable.Cols.VERSION_CODE: diff --git a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProvider.java b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProvider.java index f7ee7ec66..5b30b2549 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProvider.java @@ -10,6 +10,7 @@ import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; import android.support.annotation.Nullable; +import android.text.TextUtils; import android.util.Log; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; @@ -41,7 +42,7 @@ public class InstalledAppProvider extends FDroidProvider { cursor.moveToFirst(); while (!cursor.isAfterLast()) { cachedInfo.put( - cursor.getString(cursor.getColumnIndex(Cols.PACKAGE_NAME)), + cursor.getString(cursor.getColumnIndex(Cols.Package.NAME)), cursor.getLong(cursor.getColumnIndex(Cols.LAST_UPDATE_TIME)) ); cursor.moveToNext(); @@ -136,7 +137,17 @@ public class InstalledAppProvider extends FDroidProvider { } private QuerySelection queryApp(String packageName) { - return new QuerySelection(Cols.PACKAGE_NAME + " = ?", new String[]{packageName}); + return new QuerySelection(Cols.Package.NAME + " = ?", new String[]{packageName}); + } + + private QuerySelection queryAppSubQuery(String packageName) { + String pkg = Schema.PackageTable.NAME; + String subQuery = "(" + + " SELECT " + pkg + "." + Schema.PackageTable.Cols.ROW_ID + + " FROM " + pkg + + " WHERE " + pkg + "." + Schema.PackageTable.Cols.PACKAGE_NAME + " = ?)"; + String query = Cols.PACKAGE_ID + " = " + subQuery; + return new QuerySelection(query, new String[]{packageName}); } private QuerySelection querySearch(String query) { @@ -144,6 +155,26 @@ public class InstalledAppProvider extends FDroidProvider { new String[]{"%" + query + "%"}); } + private static class QueryBuilder extends org.fdroid.fdroid.data.QueryBuilder { + @Override + protected String getRequiredTables() { + String pkg = Schema.PackageTable.NAME; + String installed = InstalledAppTable.NAME; + return installed + " JOIN " + pkg + + " ON (" + pkg + "." + Schema.PackageTable.Cols.ROW_ID + " = " + + installed + "." + Cols.PACKAGE_ID + ")"; + } + + @Override + public void addField(String field) { + if (TextUtils.equals(field, Cols.Package.NAME)) { + appendField(Schema.PackageTable.Cols.PACKAGE_NAME, Schema.PackageTable.NAME, field); + } else { + appendField(field, InstalledAppTable.NAME); + } + } + } + @Override public Cursor query(Uri uri, String[] projection, String customSelection, String[] selectionArgs, String sortOrder) { @@ -170,8 +201,15 @@ public class InstalledAppProvider extends FDroidProvider { throw new UnsupportedOperationException(message); } - Cursor cursor = db().query(getTableName(), projection, - selection.getSelection(), selection.getArgs(), null, null, sortOrder); + QueryBuilder query = new QueryBuilder(); + query.addFields(projection); + if (projection.length == 0) { + query.addField(Cols._ID); + } + query.addSelection(selection); + query.addOrderBy(sortOrder); + + Cursor cursor = db().rawQuery(query.toString(), selection.getArgs()); cursor.setNotificationUri(getContext().getContentResolver(), uri); return cursor; } @@ -184,7 +222,7 @@ public class InstalledAppProvider extends FDroidProvider { } QuerySelection query = new QuerySelection(where, whereArgs); - query = query.add(queryApp(uri.getLastPathSegment())); + query = query.add(queryAppSubQuery(uri.getLastPathSegment())); return db().delete(getTableName(), query.getSelection(), query.getArgs()); } @@ -196,9 +234,16 @@ public class InstalledAppProvider extends FDroidProvider { throw new UnsupportedOperationException("Insert not supported for " + uri + "."); } + if (values.containsKey(Cols.Package.NAME)) { + String packageName = values.getAsString(Cols.Package.NAME); + long packageId = PackageProvider.Helper.ensureExists(getContext(), packageName); + values.remove(Cols.Package.NAME); + values.put(Cols.PACKAGE_ID, packageId); + } + verifyVersionNameNotNull(values); db().replaceOrThrow(getTableName(), null, values); - return getAppUri(values.getAsString(Cols.PACKAGE_NAME)); + return getAppUri(values.getAsString(Cols.Package.NAME)); } /** diff --git a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java index aa71c8bc1..d319b4033 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java +++ b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java @@ -288,7 +288,7 @@ public class InstalledAppProviderService extends IntentService { static void insertAppIntoDb(Context context, PackageInfo packageInfo, String hashType, String hash) { Uri uri = InstalledAppProvider.getContentUri(); ContentValues contentValues = new ContentValues(); - contentValues.put(InstalledAppTable.Cols.PACKAGE_NAME, packageInfo.packageName); + contentValues.put(InstalledAppTable.Cols.Package.NAME, packageInfo.packageName); contentValues.put(InstalledAppTable.Cols.VERSION_CODE, packageInfo.versionCode); contentValues.put(InstalledAppTable.Cols.VERSION_NAME, packageInfo.versionName); contentValues.put(InstalledAppTable.Cols.APPLICATION_LABEL, diff --git a/app/src/main/java/org/fdroid/fdroid/data/Schema.java b/app/src/main/java/org/fdroid/fdroid/data/Schema.java index 7ae47d8c5..cb02a85d9 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/Schema.java +++ b/app/src/main/java/org/fdroid/fdroid/data/Schema.java @@ -329,7 +329,7 @@ public interface Schema { interface Cols { String _ID = "rowid as _id"; // Required for CursorLoaders - String PACKAGE_NAME = "appId"; + String PACKAGE_ID = "packageId"; String VERSION_CODE = "versionCode"; String VERSION_NAME = "versionName"; String APPLICATION_LABEL = "applicationLabel"; @@ -338,8 +338,12 @@ public interface Schema { String HASH_TYPE = "hashType"; String HASH = "hash"; + interface Package { + String NAME = "packageName"; + } + String[] ALL = { - _ID, PACKAGE_NAME, VERSION_CODE, VERSION_NAME, APPLICATION_LABEL, + _ID, PACKAGE_ID, Package.NAME, VERSION_CODE, VERSION_NAME, APPLICATION_LABEL, SIGNATURE, LAST_UPDATE_TIME, HASH_TYPE, HASH, }; } diff --git a/app/src/main/java/org/fdroid/fdroid/views/swap/SelectAppsView.java b/app/src/main/java/org/fdroid/fdroid/views/swap/SelectAppsView.java index 77daa73bd..5f0d7a496 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/swap/SelectAppsView.java +++ b/app/src/main/java/org/fdroid/fdroid/views/swap/SelectAppsView.java @@ -142,7 +142,7 @@ public class SelectAppsView extends ListView implements private void toggleAppSelected(int position) { Cursor c = (Cursor) adapter.getItem(position); - String packageName = c.getString(c.getColumnIndex(InstalledAppTable.Cols.PACKAGE_NAME)); + String packageName = c.getString(c.getColumnIndex(InstalledAppTable.Cols.Package.NAME)); if (getState().hasSelectedPackage(packageName)) { getState().deselectPackage(packageName); adapter.updateCheckedIndicatorView(position, false); @@ -176,7 +176,7 @@ public class SelectAppsView extends ListView implements for (int i = 0; i < getCount(); i++) { Cursor c = (Cursor) getItemAtPosition(i); - String packageName = c.getString(c.getColumnIndex(InstalledAppTable.Cols.PACKAGE_NAME)); + String packageName = c.getString(c.getColumnIndex(InstalledAppTable.Cols.Package.NAME)); getState().ensureFDroidSelected(); for (String selected : getState().getAppsToSwap()) { if (TextUtils.equals(packageName, selected)) { @@ -257,7 +257,7 @@ public class SelectAppsView extends ListView implements TextView labelView = (TextView) view.findViewById(R.id.application_label); ImageView iconView = (ImageView) view.findViewById(android.R.id.icon); - String packageName = cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.PACKAGE_NAME)); + String packageName = cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.Package.NAME)); String appLabel = cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.APPLICATION_LABEL)); Drawable icon; diff --git a/app/src/test/java/org/fdroid/fdroid/Assert.java b/app/src/test/java/org/fdroid/fdroid/Assert.java index 741be9814..a9db602e7 100644 --- a/app/src/test/java/org/fdroid/fdroid/Assert.java +++ b/app/src/test/java/org/fdroid/fdroid/Assert.java @@ -158,7 +158,7 @@ public class Assert { Uri uri = InstalledAppProvider.getAppUri(appId); String[] projection = { - InstalledAppTable.Cols.PACKAGE_NAME, + InstalledAppTable.Cols.Package.NAME, InstalledAppTable.Cols.VERSION_CODE, InstalledAppTable.Cols.VERSION_NAME, InstalledAppTable.Cols.APPLICATION_LABEL, @@ -171,7 +171,7 @@ public class Assert { cursor.moveToFirst(); - assertEquals(appId, cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.PACKAGE_NAME))); + assertEquals(appId, cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.Package.NAME))); assertEquals(versionCode, cursor.getInt(cursor.getColumnIndex(InstalledAppTable.Cols.VERSION_CODE))); assertEquals(versionName, cursor.getString(cursor.getColumnIndex(InstalledAppTable.Cols.VERSION_NAME))); cursor.close(); diff --git a/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java index 23872b54d..cd5a3e38a 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java @@ -37,7 +37,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest { assertEquals(foundBefore.size(), 0); ContentValues values = new ContentValues(); - values.put(Cols.PACKAGE_NAME, "org.example.test-app"); + values.put(Cols.Package.NAME, "org.example.test-app"); values.put(Cols.APPLICATION_LABEL, "Test App"); values.put(Cols.VERSION_CODE, 1021); values.put(Cols.VERSION_NAME, "Longhorn"); @@ -56,7 +56,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest { assertEquals(cursor.getCount(), 1); cursor.moveToFirst(); - assertEquals("org.example.test-app", cursor.getString(cursor.getColumnIndex(Cols.PACKAGE_NAME))); + assertEquals("org.example.test-app", cursor.getString(cursor.getColumnIndex(Cols.Package.NAME))); assertEquals("Test App", cursor.getString(cursor.getColumnIndex(Cols.APPLICATION_LABEL))); assertEquals(1021, cursor.getInt(cursor.getColumnIndex(Cols.VERSION_CODE))); assertEquals("Longhorn", cursor.getString(cursor.getColumnIndex(Cols.VERSION_NAME))); @@ -125,7 +125,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest { Uri uri = InstalledAppProvider.getAppUri(packageName); String[] projection = { - Cols.PACKAGE_NAME, + Cols.Package.NAME, Cols.LAST_UPDATE_TIME, }; @@ -133,7 +133,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest { assertNotNull(cursor); assertEquals("App \"" + packageName + "\" not installed", 1, cursor.getCount()); cursor.moveToFirst(); - assertEquals(packageName, cursor.getString(cursor.getColumnIndex(Cols.PACKAGE_NAME))); + assertEquals(packageName, cursor.getString(cursor.getColumnIndex(Cols.Package.NAME))); long lastUpdateTime = cursor.getLong(cursor.getColumnIndex(Cols.LAST_UPDATE_TIME)); assertTrue(lastUpdateTime > 0); assertTrue(lastUpdateTime < System.currentTimeMillis()); @@ -170,7 +170,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest { private ContentValues createContentValues(String appId, int versionCode, String versionNumber) { ContentValues values = new ContentValues(3); if (appId != null) { - values.put(Cols.PACKAGE_NAME, appId); + values.put(Cols.Package.NAME, appId); } values.put(Cols.APPLICATION_LABEL, "Mock app: " + appId); values.put(Cols.VERSION_CODE, versionCode);