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:
Peter Serwylo 2016-09-30 00:06:08 +10:00
parent 3a24d21f59
commit d062af0975
7 changed files with 79 additions and 34 deletions

View File

@ -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()) {

View File

@ -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) {

View File

@ -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.");
}

View File

@ -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);

View File

@ -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,
};

View File

@ -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);
}

View File

@ -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;
}
}