Clarify that sometimes we don't know which repos apk we are asking for.
Many times in the past, we would ask for an apk based on its package name and its version code. However multiple repositories provide apks with the same package name and version code, and such queries would (seemingly) nondeterministically choose one of these matching apks. This clarifies the wording in the code around when we explicitly ask for a given apk, and when we kind of guess which one we want. Most the time we have an `App` handy, which has a specific repo associated with it. This allows us to be more specific about requesting `Apk`s. The times we are kind of guessing is when we rely on the "suggested version code" of an apk by clicking the misc "Install" or "Upgrade" button in app details. In the future, we'll need to clear this up so that a more specific apk is chosen when touching these buttons.
This commit is contained in:
parent
3a24d21f59
commit
d062af0975
@ -37,10 +37,19 @@ public class ApkProvider extends FDroidProvider {
|
||||
|
||||
public static void update(Context context, Apk apk) {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
Uri uri = getApkFromAnyRepoUri(apk.packageName, apk.versionCode);
|
||||
Uri uri = getApkFromRepoUri(apk);
|
||||
resolver.update(uri, apk.toContentValues(), null, null);
|
||||
}
|
||||
|
||||
public static Uri getApkFromRepoUri(Apk apk) {
|
||||
return getContentUri()
|
||||
.buildUpon()
|
||||
.appendPath(PATH_APK_FROM_REPO)
|
||||
.appendPath(Long.toString(apk.appId))
|
||||
.appendPath(Integer.toString(apk.versionCode))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static List<Apk> cursorToList(Cursor cursor) {
|
||||
int knownApkCount = cursor != null ? cursor.getCount() : 0;
|
||||
List<Apk> apks = new ArrayList<>(knownApkCount);
|
||||
@ -71,7 +80,7 @@ public class ApkProvider extends FDroidProvider {
|
||||
* Find all apks for a particular app, but limit it to those originating from the
|
||||
* specified repo.
|
||||
*/
|
||||
public static List<Apk> find(Context context, Repo repo, List<App> apps, String[] projection) {
|
||||
public static List<Apk> findByUri(Context context, Repo repo, List<App> apps, String[] projection) {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
final Uri uri = getContentUriForApps(repo, apps);
|
||||
Cursor cursor = resolver.query(uri, projection, null, null, null);
|
||||
@ -172,9 +181,12 @@ public class ApkProvider extends FDroidProvider {
|
||||
private static final int CODE_REPO_APPS = CODE_APKS + 1;
|
||||
protected static final int CODE_REPO_APK = CODE_REPO_APPS + 1;
|
||||
private static final int CODE_APK_ROW_ID = CODE_REPO_APK + 1;
|
||||
static final int CODE_APK_FROM_ANY_REPO = CODE_APK_ROW_ID + 1;
|
||||
static final int CODE_APK_FROM_REPO = CODE_APK_FROM_ANY_REPO + 1;
|
||||
|
||||
private static final String PROVIDER_NAME = "ApkProvider";
|
||||
protected static final String PATH_APK = "apk";
|
||||
protected static final String PATH_APK_FROM_ANY_REPO = "apk-any-repo";
|
||||
protected static final String PATH_APK_FROM_REPO = "apk-from-repo";
|
||||
private static final String PATH_APKS = "apks";
|
||||
private static final String PATH_APP = "app";
|
||||
private static final String PATH_REPO = "repo";
|
||||
@ -193,7 +205,8 @@ public class ApkProvider extends FDroidProvider {
|
||||
PACKAGE_FIELDS.put(Cols.Package.PACKAGE_NAME, PackageTable.Cols.PACKAGE_NAME);
|
||||
|
||||
MATCHER.addURI(getAuthority(), PATH_REPO + "/#", CODE_REPO);
|
||||
MATCHER.addURI(getAuthority(), PATH_APK + "/#/*", CODE_SINGLE);
|
||||
MATCHER.addURI(getAuthority(), PATH_APK_FROM_ANY_REPO + "/#/*", CODE_APK_FROM_ANY_REPO);
|
||||
MATCHER.addURI(getAuthority(), PATH_APK_FROM_REPO + "/#/#", CODE_APK_FROM_REPO);
|
||||
MATCHER.addURI(getAuthority(), PATH_APKS + "/*", CODE_APKS);
|
||||
MATCHER.addURI(getAuthority(), PATH_APP + "/*", CODE_PACKAGE);
|
||||
MATCHER.addURI(getAuthority(), PATH_REPO_APPS + "/#/*", CODE_REPO_APPS);
|
||||
@ -240,7 +253,7 @@ public class ApkProvider extends FDroidProvider {
|
||||
public static Uri getApkFromAnyRepoUri(String packageName, int versionCode) {
|
||||
return getContentUri()
|
||||
.buildUpon()
|
||||
.appendPath(PATH_APK)
|
||||
.appendPath(PATH_APK_FROM_ANY_REPO)
|
||||
.appendPath(Integer.toString(versionCode))
|
||||
.appendPath(packageName)
|
||||
.build();
|
||||
@ -397,6 +410,21 @@ public class ApkProvider extends FDroidProvider {
|
||||
return new QuerySelection(selection, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Doesn't prefix column names with table alias. This is so that it can be used in UPDATE
|
||||
* queries. Note that this lack of table alias prefixes means this can't be used for general
|
||||
* constraints in a regular select query within {@link ApkProvider} as the queries specify
|
||||
* aliases for the apk table.
|
||||
*/
|
||||
private QuerySelection querySingleWithAppId(Uri uri) {
|
||||
List<String> path = uri.getPathSegments();
|
||||
String appId = path.get(1);
|
||||
String versionCode = path.get(2);
|
||||
final String selection = Cols.APP_ID + " = ? AND " + Cols.VERSION_CODE + " = ? ";
|
||||
final String[] args = {appId, versionCode};
|
||||
return new QuerySelection(selection, args);
|
||||
}
|
||||
|
||||
protected QuerySelection queryRepo(long repoId) {
|
||||
return queryRepo(repoId, true);
|
||||
}
|
||||
@ -465,7 +493,7 @@ public class ApkProvider extends FDroidProvider {
|
||||
case CODE_LIST:
|
||||
break;
|
||||
|
||||
case CODE_SINGLE:
|
||||
case CODE_APK_FROM_ANY_REPO:
|
||||
query = query.add(querySingleFromAnyRepo(uri));
|
||||
break;
|
||||
|
||||
@ -568,7 +596,7 @@ public class ApkProvider extends FDroidProvider {
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
|
||||
if (MATCHER.match(uri) != CODE_SINGLE) {
|
||||
if (MATCHER.match(uri) != CODE_APK_FROM_REPO) {
|
||||
throw new UnsupportedOperationException("Cannot update anything other than a single apk.");
|
||||
}
|
||||
return performUpdateUnchecked(uri, values, where, whereArgs);
|
||||
@ -579,7 +607,7 @@ public class ApkProvider extends FDroidProvider {
|
||||
removeFieldsFromOtherTables(values);
|
||||
|
||||
QuerySelection query = new QuerySelection(where, whereArgs);
|
||||
query = query.add(querySingleFromAnyRepo(uri, false));
|
||||
query = query.add(querySingleWithAppId(uri));
|
||||
|
||||
int numRows = db().update(getTableName(), values, query.getSelection(), query.getArgs());
|
||||
if (!isApplyingBatch()) {
|
||||
|
@ -258,7 +258,7 @@ public class RepoPersister {
|
||||
@Nullable
|
||||
private ContentProviderOperation deleteOrphanedApks(List<App> apps, Map<String, List<Apk>> packages) {
|
||||
String[] projection = new String[]{Schema.ApkTable.Cols.Package.PACKAGE_NAME, Schema.ApkTable.Cols.VERSION_CODE};
|
||||
List<Apk> existing = ApkProvider.Helper.find(context, repo, apps, projection);
|
||||
List<Apk> existing = ApkProvider.Helper.findByUri(context, repo, apps, projection);
|
||||
List<Apk> toDelete = new ArrayList<>();
|
||||
|
||||
for (Apk existingApk : existing) {
|
||||
|
@ -27,7 +27,8 @@ public class TempApkProvider extends ApkProvider {
|
||||
|
||||
static {
|
||||
MATCHER.addURI(getAuthority(), PATH_INIT, CODE_INIT);
|
||||
MATCHER.addURI(getAuthority(), PATH_APK + "/#/*", CODE_SINGLE);
|
||||
MATCHER.addURI(getAuthority(), PATH_APK_FROM_ANY_REPO + "/#/*", CODE_APK_FROM_ANY_REPO);
|
||||
MATCHER.addURI(getAuthority(), PATH_APK_FROM_REPO + "/#/#", CODE_APK_FROM_REPO);
|
||||
MATCHER.addURI(getAuthority(), PATH_REPO_APK + "/#/*", CODE_REPO_APK);
|
||||
}
|
||||
|
||||
@ -52,9 +53,9 @@ public class TempApkProvider extends ApkProvider {
|
||||
public static Uri getApkUri(Apk apk) {
|
||||
return getContentUri()
|
||||
.buildUpon()
|
||||
.appendPath(PATH_APK)
|
||||
.appendPath(PATH_APK_FROM_REPO)
|
||||
.appendPath(Long.toString(apk.appId))
|
||||
.appendPath(Integer.toString(apk.versionCode))
|
||||
.appendPath(apk.packageName)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -96,7 +97,7 @@ public class TempApkProvider extends ApkProvider {
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
|
||||
if (MATCHER.match(uri) != CODE_SINGLE) {
|
||||
if (MATCHER.match(uri) != CODE_APK_FROM_REPO) {
|
||||
throw new UnsupportedOperationException("Cannot update anything other than a single apk.");
|
||||
}
|
||||
|
||||
|
@ -201,7 +201,7 @@ public class Assert {
|
||||
return AppProvider.Helper.findSpecificApp(context.getContentResolver(), packageName, 1, AppMetadataTable.Cols.ALL);
|
||||
}
|
||||
|
||||
private static App ensureApp(Context context, String packageName) {
|
||||
public static App ensureApp(Context context, String packageName) {
|
||||
App app = AppProvider.Helper.findSpecificApp(context.getContentResolver(), packageName, 1, AppMetadataTable.Cols.ALL);
|
||||
if (app == null) {
|
||||
insertApp(context, packageName, packageName);
|
||||
|
@ -242,41 +242,45 @@ public class ApkProviderTest extends FDroidProviderTest {
|
||||
@Test
|
||||
public void testKnownApks() {
|
||||
|
||||
App fdroid = Assert.ensureApp(context, "org.fdroid.fdroid");
|
||||
for (int i = 0; i < 7; i++) {
|
||||
Assert.insertApk(context, "org.fdroid.fdroid", i);
|
||||
Assert.insertApk(context, fdroid, i);
|
||||
}
|
||||
|
||||
App exampleOrg = Assert.ensureApp(context, "org.example");
|
||||
for (int i = 0; i < 9; i++) {
|
||||
Assert.insertApk(context, "org.example", i);
|
||||
Assert.insertApk(context, exampleOrg, i);
|
||||
}
|
||||
|
||||
App exampleCom = Assert.ensureApp(context, "com.example");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Assert.insertApk(context, "com.example", i);
|
||||
Assert.insertApk(context, exampleCom, i);
|
||||
}
|
||||
|
||||
Assert.insertApk(context, "com.apk.thingo", 1);
|
||||
App thingo = Assert.ensureApp(context, "com.apk.thingo");
|
||||
Assert.insertApk(context, thingo, 1);
|
||||
|
||||
Apk[] known = {
|
||||
new MockApk("org.fdroid.fdroid", 1),
|
||||
new MockApk("org.fdroid.fdroid", 3),
|
||||
new MockApk("org.fdroid.fdroid", 5),
|
||||
new MockApk(fdroid, 1),
|
||||
new MockApk(fdroid, 3),
|
||||
new MockApk(fdroid, 5),
|
||||
|
||||
new MockApk("com.example", 1),
|
||||
new MockApk("com.example", 2),
|
||||
new MockApk(exampleCom, 1),
|
||||
new MockApk(exampleCom, 2),
|
||||
};
|
||||
|
||||
Apk[] unknown = {
|
||||
new MockApk("org.fdroid.fdroid", 7),
|
||||
new MockApk("org.fdroid.fdroid", 9),
|
||||
new MockApk("org.fdroid.fdroid", 11),
|
||||
new MockApk("org.fdroid.fdroid", 13),
|
||||
new MockApk(fdroid, 7),
|
||||
new MockApk(fdroid, 9),
|
||||
new MockApk(fdroid, 11),
|
||||
new MockApk(fdroid, 13),
|
||||
|
||||
new MockApk("com.example", 3),
|
||||
new MockApk("com.example", 4),
|
||||
new MockApk("com.example", 5),
|
||||
new MockApk(exampleCom, 3),
|
||||
new MockApk(exampleCom, 4),
|
||||
new MockApk(exampleCom, 5),
|
||||
|
||||
new MockApk("info.example", 1),
|
||||
new MockApk("info.example", 2),
|
||||
new MockApk(-10, 1),
|
||||
new MockApk(-10, 2),
|
||||
};
|
||||
|
||||
List<Apk> apksToCheck = new ArrayList<>(known.length + unknown.length);
|
||||
@ -285,6 +289,7 @@ public class ApkProviderTest extends FDroidProviderTest {
|
||||
|
||||
String[] projection = {
|
||||
Cols.Package.PACKAGE_NAME,
|
||||
Cols.APP_ID,
|
||||
Cols.VERSION_CODE,
|
||||
};
|
||||
|
||||
|
@ -135,9 +135,9 @@ public class ProviderUriTests {
|
||||
|
||||
assertValidUri(resolver, ApkProvider.getContentUri(), "content://org.fdroid.fdroid.data.ApkProvider", projection);
|
||||
assertValidUri(resolver, ApkProvider.getAppUri("org.fdroid.fdroid"), "content://org.fdroid.fdroid.data.ApkProvider/app/org.fdroid.fdroid", projection);
|
||||
assertValidUri(resolver, ApkProvider.getApkFromAnyRepoUri(new MockApk("org.fdroid.fdroid", 100)), "content://org.fdroid.fdroid.data.ApkProvider/apk/100/org.fdroid.fdroid", projection);
|
||||
assertValidUri(resolver, ApkProvider.getApkFromAnyRepoUri(new MockApk("org.fdroid.fdroid", 100)), "content://org.fdroid.fdroid.data.ApkProvider/apk-any-repo/100/org.fdroid.fdroid", projection);
|
||||
assertValidUri(resolver, ApkProvider.getContentUri(apks), projection);
|
||||
assertValidUri(resolver, ApkProvider.getApkFromAnyRepoUri("org.fdroid.fdroid", 100), "content://org.fdroid.fdroid.data.ApkProvider/apk/100/org.fdroid.fdroid", projection);
|
||||
assertValidUri(resolver, ApkProvider.getApkFromAnyRepoUri("org.fdroid.fdroid", 100), "content://org.fdroid.fdroid.data.ApkProvider/apk-any-repo/100/org.fdroid.fdroid", projection);
|
||||
assertValidUri(resolver, ApkProvider.getRepoUri(1000), "content://org.fdroid.fdroid.data.ApkProvider/repo/1000", projection);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.fdroid.fdroid.mock;
|
||||
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
|
||||
public class MockApk extends Apk {
|
||||
|
||||
@ -9,4 +10,14 @@ public class MockApk extends Apk {
|
||||
this.versionCode = versionCode;
|
||||
}
|
||||
|
||||
public MockApk(App app, int versionCode) {
|
||||
this.appId = app.getId();
|
||||
this.versionCode = versionCode;
|
||||
}
|
||||
|
||||
public MockApk(long appId, int versionCode) {
|
||||
this.appId = appId;
|
||||
this.versionCode = versionCode;
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user