WIP: Making metadata table work. Requires significant refactorings throughout :(
This commit is contained in:
		
							parent
							
								
									05a64084d7
								
							
						
					
					
						commit
						be9690228f
					
				@ -100,6 +100,11 @@
 | 
				
			|||||||
            android:name="org.fdroid.fdroid.data.AppPrefsProvider"
 | 
					            android:name="org.fdroid.fdroid.data.AppPrefsProvider"
 | 
				
			||||||
            android:exported="false"/>
 | 
					            android:exported="false"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <provider
 | 
				
			||||||
 | 
					            android:authorities="org.fdroid.fdroid.data.PackageProvider"
 | 
				
			||||||
 | 
					            android:name="org.fdroid.fdroid.data.PackageProvider"
 | 
				
			||||||
 | 
					            android:exported="false"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <provider
 | 
					        <provider
 | 
				
			||||||
            android:name="org.fdroid.fdroid.installer.ApkFileProvider"
 | 
					            android:name="org.fdroid.fdroid.installer.ApkFileProvider"
 | 
				
			||||||
            android:authorities="org.fdroid.fdroid.installer.ApkFileProvider"
 | 
					            android:authorities="org.fdroid.fdroid.installer.ApkFileProvider"
 | 
				
			||||||
 | 
				
			|||||||
@ -425,7 +425,7 @@ public class AppDetails extends AppCompatActivity {
 | 
				
			|||||||
        // register observer to know when install status changes
 | 
					        // register observer to know when install status changes
 | 
				
			||||||
        myAppObserver = new AppObserver(new Handler());
 | 
					        myAppObserver = new AppObserver(new Handler());
 | 
				
			||||||
        getContentResolver().registerContentObserver(
 | 
					        getContentResolver().registerContentObserver(
 | 
				
			||||||
                AppProvider.getContentUri(app.packageName),
 | 
					                AppProvider.getAppUri(app),
 | 
				
			||||||
                true,
 | 
					                true,
 | 
				
			||||||
                myAppObserver);
 | 
					                myAppObserver);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -667,7 +667,7 @@ public class AppDetails extends AppCompatActivity {
 | 
				
			|||||||
        calcActiveDownloadUrlString(packageName);
 | 
					        calcActiveDownloadUrlString(packageName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!TextUtils.isEmpty(packageName)) {
 | 
					        if (!TextUtils.isEmpty(packageName)) {
 | 
				
			||||||
            newApp = AppProvider.Helper.findByPackageName(getContentResolver(), packageName);
 | 
					            newApp = AppProvider.Helper.findHighestPriorityMetadata(getContentResolver(), packageName);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setApp(newApp);
 | 
					        setApp(newApp);
 | 
				
			||||||
 | 
				
			|||||||
@ -458,7 +458,12 @@ public class RepoUpdater {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            if (RepoPushRequest.INSTALL.equals(repoPushRequest.request)) {
 | 
					            if (RepoPushRequest.INSTALL.equals(repoPushRequest.request)) {
 | 
				
			||||||
                ContentResolver cr = context.getContentResolver();
 | 
					                ContentResolver cr = context.getContentResolver();
 | 
				
			||||||
                App app = AppProvider.Helper.findByPackageName(cr, packageName);
 | 
					
 | 
				
			||||||
 | 
					                // TODO: In the future, this needs to be able to specify which repository to get
 | 
				
			||||||
 | 
					                // the package from. Better yet, we should be able to specify the hash of a package
 | 
				
			||||||
 | 
					                // to install (especially when we move to using hashes more as identifiers than we
 | 
				
			||||||
 | 
					                // do righ tnow).
 | 
				
			||||||
 | 
					                App app = AppProvider.Helper.findHighestPriorityMetadata(cr, packageName);
 | 
				
			||||||
                if (app == null) {
 | 
					                if (app == null) {
 | 
				
			||||||
                    Utils.debugLog(TAG, packageName + " not in local database, ignoring request to"
 | 
					                    Utils.debugLog(TAG, packageName + " not in local database, ignoring request to"
 | 
				
			||||||
                            + repoPushRequest.request);
 | 
					                            + repoPushRequest.request);
 | 
				
			||||||
 | 
				
			|||||||
@ -280,6 +280,7 @@ public class RepoXMLHandler extends DefaultHandler {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        } else if ("application".equals(localName) && curapp == null) {
 | 
					        } else if ("application".equals(localName) && curapp == null) {
 | 
				
			||||||
            curapp = new App();
 | 
					            curapp = new App();
 | 
				
			||||||
 | 
					            curapp.repoId = repo.getId();
 | 
				
			||||||
            curapp.packageName = attributes.getValue("", "id");
 | 
					            curapp.packageName = attributes.getValue("", "id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // To appease the NON NULL constraint in the DB. Usually there is a description, and it
 | 
					            // To appease the NON NULL constraint in the DB. Usually there is a description, and it
 | 
				
			||||||
 | 
				
			|||||||
@ -63,7 +63,7 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
 | 
				
			|||||||
    public String[] incompatibleReasons;
 | 
					    public String[] incompatibleReasons;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The numeric primary key of the App table, which is used to join apks.
 | 
					     * The numeric primary key of the Package table, which is used to join apks.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public long appId;
 | 
					    public long appId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -91,7 +91,7 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
 | 
				
			|||||||
                case Cols.FEATURES:
 | 
					                case Cols.FEATURES:
 | 
				
			||||||
                    features = Utils.parseCommaSeparatedString(cursor.getString(i));
 | 
					                    features = Utils.parseCommaSeparatedString(cursor.getString(i));
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                case Cols.App.PACKAGE_NAME:
 | 
					                case Cols.Package.PACKAGE_NAME:
 | 
				
			||||||
                    packageName = cursor.getString(i);
 | 
					                    packageName = cursor.getString(i);
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                case Cols.IS_COMPATIBLE:
 | 
					                case Cols.IS_COMPATIBLE:
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,7 @@ import android.util.Log;
 | 
				
			|||||||
import org.fdroid.fdroid.data.Schema.ApkTable;
 | 
					import org.fdroid.fdroid.data.Schema.ApkTable;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.ApkTable.Cols;
 | 
					import org.fdroid.fdroid.data.Schema.ApkTable.Cols;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.AppMetadataTable;
 | 
					import org.fdroid.fdroid.data.Schema.AppMetadataTable;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.Schema.PackageTable;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.RepoTable;
 | 
					import org.fdroid.fdroid.data.Schema.RepoTable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
@ -189,7 +190,7 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
    static {
 | 
					    static {
 | 
				
			||||||
        REPO_FIELDS.put(Cols.Repo.VERSION, RepoTable.Cols.VERSION);
 | 
					        REPO_FIELDS.put(Cols.Repo.VERSION, RepoTable.Cols.VERSION);
 | 
				
			||||||
        REPO_FIELDS.put(Cols.Repo.ADDRESS, RepoTable.Cols.ADDRESS);
 | 
					        REPO_FIELDS.put(Cols.Repo.ADDRESS, RepoTable.Cols.ADDRESS);
 | 
				
			||||||
        PACKAGE_FIELDS.put(Cols.App.PACKAGE_NAME, AppMetadataTable.Cols.PACKAGE_NAME);
 | 
					        PACKAGE_FIELDS.put(Cols.Package.PACKAGE_NAME, PackageTable.Cols.PACKAGE_NAME);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        MATCHER.addURI(getAuthority(), PATH_REPO + "/#", CODE_REPO);
 | 
					        MATCHER.addURI(getAuthority(), PATH_REPO + "/#", CODE_REPO);
 | 
				
			||||||
        MATCHER.addURI(getAuthority(), PATH_APK + "/#/*", CODE_SINGLE);
 | 
					        MATCHER.addURI(getAuthority(), PATH_APK + "/#/*", CODE_SINGLE);
 | 
				
			||||||
@ -317,9 +318,11 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
        protected String getRequiredTables() {
 | 
					        protected String getRequiredTables() {
 | 
				
			||||||
            final String apk = getTableName();
 | 
					            final String apk = getTableName();
 | 
				
			||||||
            final String app = getAppTableName();
 | 
					            final String app = getAppTableName();
 | 
				
			||||||
 | 
					            final String pkg = PackageTable.NAME;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return apk + " AS apk " +
 | 
					            return apk + " AS apk " +
 | 
				
			||||||
                " LEFT JOIN " + app + " AS app ON (app." + AppMetadataTable.Cols.ROW_ID + " = apk." + Cols.APP_ID + ")";
 | 
					                " LEFT JOIN " + app + " AS app ON (app." + AppMetadataTable.Cols.ROW_ID + " = apk." + Cols.APP_ID + ")" +
 | 
				
			||||||
 | 
					                " LEFT JOIN " + pkg + " AS pkg ON (pkg." + PackageTable.Cols.ROW_ID + " = app." + AppMetadataTable.Cols.PACKAGE_ID + ")";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
@ -340,7 +343,7 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void addPackageField(String field, String alias) {
 | 
					        private void addPackageField(String field, String alias) {
 | 
				
			||||||
            appendField(field, "app", alias);
 | 
					            appendField(field, "pkg", alias);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void addRepoField(String field, String alias) {
 | 
					        private void addRepoField(String field, String alias) {
 | 
				
			||||||
@ -354,12 +357,7 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private QuerySelection queryPackage(String packageName) {
 | 
					    private QuerySelection queryPackage(String packageName) {
 | 
				
			||||||
        return queryPackage(packageName, true);
 | 
					        final String selection = "pkg." + PackageTable.Cols.PACKAGE_NAME + " = ?";
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private QuerySelection queryPackage(String packageName, boolean includeTableAlias) {
 | 
					 | 
				
			||||||
        String alias = includeTableAlias ? "apk." : "";
 | 
					 | 
				
			||||||
        final String selection = alias + Cols.APP_ID + " = (" + getAppIdFromPackageNameQuery() + ")";
 | 
					 | 
				
			||||||
        final String[] args = {packageName};
 | 
					        final String[] args = {packageName};
 | 
				
			||||||
        return new QuerySelection(selection, args);
 | 
					        return new QuerySelection(selection, args);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -370,7 +368,9 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private QuerySelection querySingleFromAnyRepo(Uri uri, boolean includeAlias) {
 | 
					    private QuerySelection querySingleFromAnyRepo(Uri uri, boolean includeAlias) {
 | 
				
			||||||
        String alias = includeAlias ? "apk." : "";
 | 
					        String alias = includeAlias ? "apk." : "";
 | 
				
			||||||
        final String selection = alias + Cols.VERSION_CODE + " = ? and " + alias + Cols.APP_ID + " = (" + getAppIdFromPackageNameQuery() + ")";
 | 
					
 | 
				
			||||||
 | 
					        // TODO: Change the = to an IN to deal with multiple apps?
 | 
				
			||||||
 | 
					        final String selection = alias + Cols.VERSION_CODE + " = ? and " + alias + Cols.APP_ID + " = (" + getMetadataIdFromPackageNameQuery() + ")";
 | 
				
			||||||
        final String[] args = {
 | 
					        final String[] args = {
 | 
				
			||||||
            // First (0th) path segment is the word "apk",
 | 
					            // First (0th) path segment is the word "apk",
 | 
				
			||||||
            // and we are not interested in it.
 | 
					            // and we are not interested in it.
 | 
				
			||||||
@ -403,7 +403,7 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private QuerySelection queryRepoApps(long repoId, String packageNames) {
 | 
					    private QuerySelection queryRepoApps(long repoId, String packageNames) {
 | 
				
			||||||
        return queryRepo(repoId).add(AppProvider.queryPackageNames(packageNames, "app." + AppMetadataTable.Cols.PACKAGE_NAME));
 | 
					        return queryRepo(repoId).add(AppProvider.queryPackageNames(packageNames, "pkg." + PackageTable.Cols.PACKAGE_NAME));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected QuerySelection queryApks(String apkKeys) {
 | 
					    protected QuerySelection queryApks(String apkKeys) {
 | 
				
			||||||
@ -418,6 +418,7 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
                "You tried to query " + apkDetails.length);
 | 
					                "You tried to query " + apkDetails.length);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        String alias = includeAlias ? "apk." : "";
 | 
					        String alias = includeAlias ? "apk." : "";
 | 
				
			||||||
 | 
					        String metadataAlias = includeAlias ? "app." : "";
 | 
				
			||||||
        final String[] args = new String[apkDetails.length * 2];
 | 
					        final String[] args = new String[apkDetails.length * 2];
 | 
				
			||||||
        StringBuilder sb = new StringBuilder();
 | 
					        StringBuilder sb = new StringBuilder();
 | 
				
			||||||
        for (int i = 0; i < apkDetails.length; i++) {
 | 
					        for (int i = 0; i < apkDetails.length; i++) {
 | 
				
			||||||
@ -430,10 +431,10 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
                sb.append(" OR ");
 | 
					                sb.append(" OR ");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            sb.append(" ( ")
 | 
					            sb.append(" ( ")
 | 
				
			||||||
                    .append(alias)
 | 
					                    .append(metadataAlias)
 | 
				
			||||||
                    .append(Cols.APP_ID)
 | 
					                    .append(AppMetadataTable.Cols.PACKAGE_ID)
 | 
				
			||||||
                    .append(" = (")
 | 
					                    .append(" = (")
 | 
				
			||||||
                    .append(getAppIdFromPackageNameQuery())
 | 
					                    .append(getPackageIdFromPackageNameQuery())
 | 
				
			||||||
                    .append(") AND ")
 | 
					                    .append(") AND ")
 | 
				
			||||||
                    .append(alias)
 | 
					                    .append(alias)
 | 
				
			||||||
                    .append(Cols.VERSION_CODE)
 | 
					                    .append(Cols.VERSION_CODE)
 | 
				
			||||||
@ -442,8 +443,13 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
        return new QuerySelection(sb.toString(), args);
 | 
					        return new QuerySelection(sb.toString(), args);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String getAppIdFromPackageNameQuery() {
 | 
					    // TODO: This could return many rows of app metadata
 | 
				
			||||||
        return "SELECT " + AppMetadataTable.Cols.ROW_ID + " FROM " + getAppTableName() + " WHERE " + AppMetadataTable.Cols.PACKAGE_NAME + " = ?";
 | 
					    private String getMetadataIdFromPackageNameQuery() {
 | 
				
			||||||
 | 
					        return "SELECT app." + AppMetadataTable.Cols.ROW_ID + " " +
 | 
				
			||||||
 | 
					                "FROM " + AppMetadataTable.NAME + " AS app " +
 | 
				
			||||||
 | 
					                "JOIN " + PackageTable.NAME + " AS pkg ON ( " +
 | 
				
			||||||
 | 
					                "  app." + AppMetadataTable.Cols.PACKAGE_ID + " = pkg." + PackageTable.Cols.ROW_ID + " ) " +
 | 
				
			||||||
 | 
					                "WHERE pkg." + PackageTable.Cols.PACKAGE_NAME + " = ?";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
@ -535,10 +541,6 @@ public class ApkProvider extends FDroidProvider {
 | 
				
			|||||||
                query = query.add(queryRepo(Long.parseLong(uri.getLastPathSegment()), false));
 | 
					                query = query.add(queryRepo(Long.parseLong(uri.getLastPathSegment()), false));
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            case CODE_PACKAGE:
 | 
					 | 
				
			||||||
                query = query.add(queryPackage(uri.getLastPathSegment(), false));
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            case CODE_APKS:
 | 
					            case CODE_APKS:
 | 
				
			||||||
                query = query.add(queryApks(uri.getLastPathSegment(), false));
 | 
					                query = query.add(queryApks(uri.getLastPathSegment(), false));
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
				
			|||||||
@ -46,6 +46,7 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public String packageName = "unknown";
 | 
					    public String packageName = "unknown";
 | 
				
			||||||
    public String name = "Unknown";
 | 
					    public String name = "Unknown";
 | 
				
			||||||
 | 
					    public long repoId;
 | 
				
			||||||
    public String summary = "Unknown application";
 | 
					    public String summary = "Unknown application";
 | 
				
			||||||
    public String icon;
 | 
					    public String icon;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -147,10 +148,13 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
 | 
				
			|||||||
                case Cols.ROW_ID:
 | 
					                case Cols.ROW_ID:
 | 
				
			||||||
                    id = cursor.getLong(i);
 | 
					                    id = cursor.getLong(i);
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
 | 
					                case Cols.REPO_ID:
 | 
				
			||||||
 | 
					                    repoId = cursor.getLong(i);
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
                case Cols.IS_COMPATIBLE:
 | 
					                case Cols.IS_COMPATIBLE:
 | 
				
			||||||
                    compatible = cursor.getInt(i) == 1;
 | 
					                    compatible = cursor.getInt(i) == 1;
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                case Cols.PACKAGE_NAME:
 | 
					                case Cols.Package.PACKAGE_NAME:
 | 
				
			||||||
                    packageName = cursor.getString(i);
 | 
					                    packageName = cursor.getString(i);
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
                case Cols.NAME:
 | 
					                case Cols.NAME:
 | 
				
			||||||
@ -430,8 +434,9 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
 | 
				
			|||||||
        final ContentValues values = new ContentValues();
 | 
					        final ContentValues values = new ContentValues();
 | 
				
			||||||
        // Intentionally don't put "ROW_ID" in here, because we don't ever want to change that
 | 
					        // Intentionally don't put "ROW_ID" in here, because we don't ever want to change that
 | 
				
			||||||
        // primary key generated by sqlite.
 | 
					        // primary key generated by sqlite.
 | 
				
			||||||
        values.put(Cols.PACKAGE_NAME, packageName);
 | 
					        values.put(Cols.Package.PACKAGE_NAME, packageName);
 | 
				
			||||||
        values.put(Cols.NAME, name);
 | 
					        values.put(Cols.NAME, name);
 | 
				
			||||||
 | 
					        values.put(Cols.REPO_ID, repoId);
 | 
				
			||||||
        values.put(Cols.SUMMARY, summary);
 | 
					        values.put(Cols.SUMMARY, summary);
 | 
				
			||||||
        values.put(Cols.ICON, icon);
 | 
					        values.put(Cols.ICON, icon);
 | 
				
			||||||
        values.put(Cols.ICON_URL, iconUrl);
 | 
					        values.put(Cols.ICON_URL, iconUrl);
 | 
				
			||||||
@ -560,6 +565,7 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
 | 
				
			|||||||
        dest.writeByte(this.compatible ? (byte) 1 : (byte) 0);
 | 
					        dest.writeByte(this.compatible ? (byte) 1 : (byte) 0);
 | 
				
			||||||
        dest.writeString(this.packageName);
 | 
					        dest.writeString(this.packageName);
 | 
				
			||||||
        dest.writeString(this.name);
 | 
					        dest.writeString(this.name);
 | 
				
			||||||
 | 
					        dest.writeLong(this.repoId);
 | 
				
			||||||
        dest.writeString(this.summary);
 | 
					        dest.writeString(this.summary);
 | 
				
			||||||
        dest.writeString(this.icon);
 | 
					        dest.writeString(this.icon);
 | 
				
			||||||
        dest.writeString(this.description);
 | 
					        dest.writeString(this.description);
 | 
				
			||||||
@ -596,6 +602,7 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
 | 
				
			|||||||
        this.compatible = in.readByte() != 0;
 | 
					        this.compatible = in.readByte() != 0;
 | 
				
			||||||
        this.packageName = in.readString();
 | 
					        this.packageName = in.readString();
 | 
				
			||||||
        this.name = in.readString();
 | 
					        this.name = in.readString();
 | 
				
			||||||
 | 
					        this.repoId = in.readLong();
 | 
				
			||||||
        this.summary = in.readString();
 | 
					        this.summary = in.readString();
 | 
				
			||||||
        this.icon = in.readString();
 | 
					        this.icon = in.readString();
 | 
				
			||||||
        this.description = in.readString();
 | 
					        this.description = in.readString();
 | 
				
			||||||
 | 
				
			|||||||
@ -18,6 +18,7 @@ import org.fdroid.fdroid.data.Schema.AppPrefsTable;
 | 
				
			|||||||
import org.fdroid.fdroid.data.Schema.AppMetadataTable;
 | 
					import org.fdroid.fdroid.data.Schema.AppMetadataTable;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
 | 
					import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.InstalledAppTable;
 | 
					import org.fdroid.fdroid.data.Schema.InstalledAppTable;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.Schema.PackageTable;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.RepoTable;
 | 
					import org.fdroid.fdroid.data.Schema.RepoTable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
@ -120,13 +121,17 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
            return categories;
 | 
					            return categories;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static App findByPackageName(ContentResolver resolver, String packageName) {
 | 
					        public static App findHighestPriorityMetadata(ContentResolver resolver, String packageName) {
 | 
				
			||||||
            return findByPackageName(resolver, packageName, Cols.ALL);
 | 
					            throw new UnsupportedOperationException("TODO: Pull back the metadata with the highest priority for packageName");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static App findByPackageName(ContentResolver resolver, String packageName,
 | 
					        public static App findByPackageName(ContentResolver resolver, String packageName, long repoId) {
 | 
				
			||||||
 | 
					            return findByPackageName(resolver, packageName, repoId, Cols.ALL);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static App findByPackageName(ContentResolver resolver, String packageName, long repoId,
 | 
				
			||||||
                                            String[] projection) {
 | 
					                                            String[] projection) {
 | 
				
			||||||
            final Uri uri = getContentUri(packageName);
 | 
					            final Uri uri = getAppUri(packageName, repoId);
 | 
				
			||||||
            return cursorToApp(resolver.query(uri, projection, null, null, null));
 | 
					            return cursorToApp(resolver.query(uri, projection, null, null, null));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -254,11 +259,13 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        protected String getRequiredTables() {
 | 
					        protected String getRequiredTables() {
 | 
				
			||||||
 | 
					            final String pkg  = PackageTable.NAME;
 | 
				
			||||||
            final String app  = getTableName();
 | 
					            final String app  = getTableName();
 | 
				
			||||||
            final String apk  = getApkTableName();
 | 
					            final String apk  = getApkTableName();
 | 
				
			||||||
            final String repo = RepoTable.NAME;
 | 
					            final String repo = RepoTable.NAME;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return app +
 | 
					            return pkg +
 | 
				
			||||||
 | 
					                " JOIN " + app + " ON (" + app + "." + Cols.PACKAGE_ID + " = " + pkg + "." + PackageTable.Cols.ROW_ID + ") " +
 | 
				
			||||||
                " LEFT JOIN " + apk + " ON (" + apk + "." + ApkTable.Cols.APP_ID + " = " + app + "." + Cols.ROW_ID + ") " +
 | 
					                " LEFT JOIN " + apk + " ON (" + apk + "." + ApkTable.Cols.APP_ID + " = " + app + "." + Cols.ROW_ID + ") " +
 | 
				
			||||||
                " LEFT JOIN " + repo + " ON (" + apk + "." + ApkTable.Cols.REPO_ID + " = " + repo + "." + RepoTable.Cols._ID + ") ";
 | 
					                " LEFT JOIN " + repo + " ON (" + apk + "." + ApkTable.Cols.REPO_ID + " = " + repo + "." + RepoTable.Cols._ID + ") ";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -291,7 +298,7 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
                join(
 | 
					                join(
 | 
				
			||||||
                        InstalledAppTable.NAME,
 | 
					                        InstalledAppTable.NAME,
 | 
				
			||||||
                        "installed",
 | 
					                        "installed",
 | 
				
			||||||
                        "installed." + InstalledAppTable.Cols.PACKAGE_NAME + " = " + getTableName() + "." + Cols.PACKAGE_NAME);
 | 
					                        "installed." + InstalledAppTable.Cols.PACKAGE_NAME + " = " + PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME);
 | 
				
			||||||
                requiresInstalledTable = true;
 | 
					                requiresInstalledTable = true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -301,7 +308,7 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
                leftJoin(
 | 
					                leftJoin(
 | 
				
			||||||
                        AppPrefsTable.NAME,
 | 
					                        AppPrefsTable.NAME,
 | 
				
			||||||
                        "prefs",
 | 
					                        "prefs",
 | 
				
			||||||
                        "prefs." + AppPrefsTable.Cols.PACKAGE_NAME + " = " + getTableName() + "." + Cols.PACKAGE_NAME);
 | 
					                        "prefs." + AppPrefsTable.Cols.PACKAGE_NAME + " = " + PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME);
 | 
				
			||||||
                requiresLeftJoinToPrefs = true;
 | 
					                requiresLeftJoinToPrefs = true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -311,7 +318,7 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
                leftJoin(
 | 
					                leftJoin(
 | 
				
			||||||
                        InstalledAppTable.NAME,
 | 
					                        InstalledAppTable.NAME,
 | 
				
			||||||
                        "installed",
 | 
					                        "installed",
 | 
				
			||||||
                        "installed." + InstalledAppTable.Cols.PACKAGE_NAME + " = " + getTableName() + "." + Cols.PACKAGE_NAME);
 | 
					                        "installed." + InstalledAppTable.Cols.PACKAGE_NAME + " = " + PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME);
 | 
				
			||||||
                requiresInstalledTable = true;
 | 
					                requiresInstalledTable = true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -319,6 +326,9 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
        @Override
 | 
					        @Override
 | 
				
			||||||
        public void addField(String field) {
 | 
					        public void addField(String field) {
 | 
				
			||||||
            switch (field) {
 | 
					            switch (field) {
 | 
				
			||||||
 | 
					                case Cols.Package.PACKAGE_NAME:
 | 
				
			||||||
 | 
					                    appendField(PackageTable.Cols.PACKAGE_NAME, PackageTable.NAME, Cols.Package.PACKAGE_NAME);
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
                case Cols.SuggestedApk.VERSION_NAME:
 | 
					                case Cols.SuggestedApk.VERSION_NAME:
 | 
				
			||||||
                    addSuggestedApkVersionField();
 | 
					                    addSuggestedApkVersionField();
 | 
				
			||||||
                    break;
 | 
					                    break;
 | 
				
			||||||
@ -404,6 +414,7 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
    private static final String PATH_SEARCH_REPO = "searchRepo";
 | 
					    private static final String PATH_SEARCH_REPO = "searchRepo";
 | 
				
			||||||
    private static final String PATH_NO_APKS = "noApks";
 | 
					    private static final String PATH_NO_APKS = "noApks";
 | 
				
			||||||
    protected static final String PATH_APPS = "apps";
 | 
					    protected static final String PATH_APPS = "apps";
 | 
				
			||||||
 | 
					    protected static final String PATH_APP = "app";
 | 
				
			||||||
    private static final String PATH_RECENTLY_UPDATED = "recentlyUpdated";
 | 
					    private static final String PATH_RECENTLY_UPDATED = "recentlyUpdated";
 | 
				
			||||||
    private static final String PATH_NEWLY_ADDED = "newlyAdded";
 | 
					    private static final String PATH_NEWLY_ADDED = "newlyAdded";
 | 
				
			||||||
    private static final String PATH_CATEGORY = "category";
 | 
					    private static final String PATH_CATEGORY = "category";
 | 
				
			||||||
@ -437,7 +448,7 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
        MATCHER.addURI(getAuthority(), PATH_CAN_UPDATE, CAN_UPDATE);
 | 
					        MATCHER.addURI(getAuthority(), PATH_CAN_UPDATE, CAN_UPDATE);
 | 
				
			||||||
        MATCHER.addURI(getAuthority(), PATH_INSTALLED, INSTALLED);
 | 
					        MATCHER.addURI(getAuthority(), PATH_INSTALLED, INSTALLED);
 | 
				
			||||||
        MATCHER.addURI(getAuthority(), PATH_NO_APKS, NO_APKS);
 | 
					        MATCHER.addURI(getAuthority(), PATH_NO_APKS, NO_APKS);
 | 
				
			||||||
        MATCHER.addURI(getAuthority(), "*", CODE_SINGLE);
 | 
					        MATCHER.addURI(getAuthority(), PATH_APP + "/#/*", CODE_SINGLE);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static Uri getContentUri() {
 | 
					    public static Uri getContentUri() {
 | 
				
			||||||
@ -486,6 +497,19 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
        return getContentUri(app.packageName);
 | 
					        return getContentUri(app.packageName);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static Uri getAppUri(App app) {
 | 
				
			||||||
 | 
					        return getAppUri(app.packageName, app.repoId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static Uri getAppUri(String packageName, long repoId) {
 | 
				
			||||||
 | 
					        return getContentUri()
 | 
				
			||||||
 | 
					                .buildUpon()
 | 
				
			||||||
 | 
					                .appendPath(PATH_APP)
 | 
				
			||||||
 | 
					                .appendPath(Long.toString(repoId))
 | 
				
			||||||
 | 
					                .appendPath(packageName)
 | 
				
			||||||
 | 
					                .build();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static Uri getContentUri(String packageName) {
 | 
					    public static Uri getContentUri(String packageName) {
 | 
				
			||||||
        return Uri.withAppendedPath(getContentUri(), packageName);
 | 
					        return Uri.withAppendedPath(getContentUri(), packageName);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -590,7 +614,7 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        final String app = getTableName();
 | 
					        final String app = getTableName();
 | 
				
			||||||
        final String[] columns = {
 | 
					        final String[] columns = {
 | 
				
			||||||
                app + "." + Cols.PACKAGE_NAME,
 | 
					                PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME,
 | 
				
			||||||
                app + "." + Cols.NAME,
 | 
					                app + "." + Cols.NAME,
 | 
				
			||||||
                app + "." + Cols.SUMMARY,
 | 
					                app + "." + Cols.SUMMARY,
 | 
				
			||||||
                app + "." + Cols.DESCRIPTION,
 | 
					                app + "." + Cols.DESCRIPTION,
 | 
				
			||||||
@ -624,12 +648,25 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
        return new AppQuerySelection(selection.toString(), selectionKeywords);
 | 
					        return new AppQuerySelection(selection.toString(), selectionKeywords);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected AppQuerySelection querySingle(String packageName) {
 | 
					    protected AppQuerySelection querySingle(String packageName, long repoId) {
 | 
				
			||||||
        final String selection = getTableName() + "." + Cols.PACKAGE_NAME + " = ?";
 | 
					        final String selection = PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME + " = ?";
 | 
				
			||||||
        final String[] args = {packageName};
 | 
					        final String[] args = {packageName};
 | 
				
			||||||
        return new AppQuerySelection(selection, args);
 | 
					        return new AppQuerySelection(selection, args);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Same as {@link AppProvider#querySingle(String, long)} except it is used for the purpose
 | 
				
			||||||
 | 
					     * of an UPDATE query rather than a SELECT query. This means that it must use a subquery to get
 | 
				
			||||||
 | 
					     * the {@link Cols.Package#PACKAGE_ID} rather than the join which is already in place for that
 | 
				
			||||||
 | 
					     * table.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected AppQuerySelection querySingleForUpdate(String packageName, long repoId) {
 | 
				
			||||||
 | 
					        final String selection = Cols.PACKAGE_ID + " = (" + getPackageIdFromPackageNameQuery() +
 | 
				
			||||||
 | 
					                ") AND " + Cols.REPO_ID + " = ? ";
 | 
				
			||||||
 | 
					        final String[] args = {packageName, Long.toString(repoId)};
 | 
				
			||||||
 | 
					        return new AppQuerySelection(selection, args);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private AppQuerySelection queryExcludeSwap() {
 | 
					    private AppQuerySelection queryExcludeSwap() {
 | 
				
			||||||
        // fdroid_repo will have null fields if the LEFT JOIN didn't resolve, e.g. due to there
 | 
					        // fdroid_repo will have null fields if the LEFT JOIN didn't resolve, e.g. due to there
 | 
				
			||||||
        // being no apks for the app in the result set. In that case, we can't tell if it is from
 | 
					        // being no apks for the app in the result set. In that case, we can't tell if it is from
 | 
				
			||||||
@ -697,7 +734,10 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            case CODE_SINGLE:
 | 
					            case CODE_SINGLE:
 | 
				
			||||||
                selection = selection.add(querySingle(uri.getLastPathSegment()));
 | 
					                List<String> pathParts = uri.getPathSegments();
 | 
				
			||||||
 | 
					                long repoId = Long.parseLong(pathParts.get(1));
 | 
				
			||||||
 | 
					                String packageName = pathParts.get(2);
 | 
				
			||||||
 | 
					                selection = selection.add(querySingle(packageName, repoId));
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            case CAN_UPDATE:
 | 
					            case CAN_UPDATE:
 | 
				
			||||||
@ -799,11 +839,15 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public Uri insert(Uri uri, ContentValues values) {
 | 
					    public Uri insert(Uri uri, ContentValues values) {
 | 
				
			||||||
 | 
					        long packageId = PackageProvider.Helper.ensureExists(getContext(), values.getAsString(Cols.Package.PACKAGE_NAME));
 | 
				
			||||||
 | 
					        values.remove(Cols.Package.PACKAGE_NAME);
 | 
				
			||||||
 | 
					        values.put(Cols.PACKAGE_ID, packageId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        db().insertOrThrow(getTableName(), null, values);
 | 
					        db().insertOrThrow(getTableName(), null, values);
 | 
				
			||||||
        if (!isApplyingBatch()) {
 | 
					        if (!isApplyingBatch()) {
 | 
				
			||||||
            getContext().getContentResolver().notifyChange(uri, null);
 | 
					            getContext().getContentResolver().notifyChange(uri, null);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return getContentUri(values.getAsString(Cols.PACKAGE_NAME));
 | 
					        return getAppUri(values.getAsString(PackageTable.Cols.PACKAGE_NAME), values.getAsLong(Cols.REPO_ID));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
@ -816,7 +860,10 @@ public class AppProvider extends FDroidProvider {
 | 
				
			|||||||
                return 0;
 | 
					                return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            case CODE_SINGLE:
 | 
					            case CODE_SINGLE:
 | 
				
			||||||
                query = query.add(querySingle(uri.getLastPathSegment()));
 | 
					                List<String> pathParts = uri.getPathSegments();
 | 
				
			||||||
 | 
					                long repoId = Long.parseLong(pathParts.get(1));
 | 
				
			||||||
 | 
					                String packageName = pathParts.get(2);
 | 
				
			||||||
 | 
					                query = query.add(querySingleForUpdate(packageName, repoId));
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            default:
 | 
					            default:
 | 
				
			||||||
 | 
				
			|||||||
@ -103,7 +103,8 @@ class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    static final String CREATE_TABLE_APP_METADATA = "CREATE TABLE " + AppMetadataTable.NAME
 | 
					    static final String CREATE_TABLE_APP_METADATA = "CREATE TABLE " + AppMetadataTable.NAME
 | 
				
			||||||
            + " ( "
 | 
					            + " ( "
 | 
				
			||||||
            + AppMetadataTable.Cols.PACKAGE_NAME + " text not null, "
 | 
					            + AppMetadataTable.Cols.PACKAGE_ID + " integer not null, "
 | 
				
			||||||
 | 
					            + AppMetadataTable.Cols.REPO_ID + " integer not null, "
 | 
				
			||||||
            + AppMetadataTable.Cols.NAME + " text not null, "
 | 
					            + AppMetadataTable.Cols.NAME + " text not null, "
 | 
				
			||||||
            + AppMetadataTable.Cols.SUMMARY + " text not null, "
 | 
					            + AppMetadataTable.Cols.SUMMARY + " text not null, "
 | 
				
			||||||
            + AppMetadataTable.Cols.ICON + " text, "
 | 
					            + AppMetadataTable.Cols.ICON + " text, "
 | 
				
			||||||
@ -130,7 +131,7 @@ class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
            + AppMetadataTable.Cols.IS_COMPATIBLE + " int not null,"
 | 
					            + AppMetadataTable.Cols.IS_COMPATIBLE + " int not null,"
 | 
				
			||||||
            + AppMetadataTable.Cols.ICON_URL + " text, "
 | 
					            + AppMetadataTable.Cols.ICON_URL + " text, "
 | 
				
			||||||
            + AppMetadataTable.Cols.ICON_URL_LARGE + " text, "
 | 
					            + AppMetadataTable.Cols.ICON_URL_LARGE + " text, "
 | 
				
			||||||
            + "primary key(" + AppMetadataTable.Cols.PACKAGE_NAME + "));";
 | 
					            + "primary key(" + AppMetadataTable.Cols.PACKAGE_ID + ", " + AppMetadataTable.Cols.REPO_ID + "));";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static final String CREATE_TABLE_APP_PREFS = "CREATE TABLE " + AppPrefsTable.NAME
 | 
					    private static final String CREATE_TABLE_APP_PREFS = "CREATE TABLE " + AppPrefsTable.NAME
 | 
				
			||||||
            + " ( "
 | 
					            + " ( "
 | 
				
			||||||
@ -434,7 +435,7 @@ class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
                + AppPrefsTable.Cols.IGNORE_THIS_UPDATE + ", "
 | 
					                + AppPrefsTable.Cols.IGNORE_THIS_UPDATE + ", "
 | 
				
			||||||
                + AppPrefsTable.Cols.IGNORE_ALL_UPDATES
 | 
					                + AppPrefsTable.Cols.IGNORE_ALL_UPDATES
 | 
				
			||||||
                + ") SELECT "
 | 
					                + ") SELECT "
 | 
				
			||||||
                + AppMetadataTable.Cols.PACKAGE_NAME + ", "
 | 
					                + "id, "
 | 
				
			||||||
                + "ignoreThisUpdate, "
 | 
					                + "ignoreThisUpdate, "
 | 
				
			||||||
                + "ignoreAllUpdates "
 | 
					                + "ignoreAllUpdates "
 | 
				
			||||||
                + "FROM " + AppMetadataTable.NAME + " "
 | 
					                + "FROM " + AppMetadataTable.NAME + " "
 | 
				
			||||||
@ -543,7 +544,7 @@ class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
                final String update = "UPDATE " + ApkTable.NAME + " SET " + ApkTable.Cols.APP_ID + " = ( " +
 | 
					                final String update = "UPDATE " + ApkTable.NAME + " SET " + ApkTable.Cols.APP_ID + " = ( " +
 | 
				
			||||||
                        "SELECT app." + AppMetadataTable.Cols.ROW_ID + " " +
 | 
					                        "SELECT app." + AppMetadataTable.Cols.ROW_ID + " " +
 | 
				
			||||||
                        "FROM " + AppMetadataTable.NAME + " AS app " +
 | 
					                        "FROM " + AppMetadataTable.NAME + " AS app " +
 | 
				
			||||||
                        "WHERE " + ApkTable.NAME + ".id = app." + AppMetadataTable.Cols.PACKAGE_NAME + ")";
 | 
					                        "WHERE " + ApkTable.NAME + ".id = app.id)";
 | 
				
			||||||
                Log.i(TAG, "Updating foreign key from " + ApkTable.NAME + " to " + AppMetadataTable.NAME + " to use numeric foreign key.");
 | 
					                Log.i(TAG, "Updating foreign key from " + ApkTable.NAME + " to " + AppMetadataTable.NAME + " to use numeric foreign key.");
 | 
				
			||||||
                Utils.debugLog(TAG, update);
 | 
					                Utils.debugLog(TAG, update);
 | 
				
			||||||
                db.execSQL(update);
 | 
					                db.execSQL(update);
 | 
				
			||||||
@ -840,10 +841,17 @@ class DBHelper extends SQLiteOpenHelper {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Utils.debugLog(TAG, "Ensuring indexes exist for " + AppMetadataTable.NAME);
 | 
					        Utils.debugLog(TAG, "Ensuring indexes exist for " + AppMetadataTable.NAME);
 | 
				
			||||||
        db.execSQL("CREATE INDEX IF NOT EXISTS app_id on " + AppMetadataTable.NAME + " (" + AppMetadataTable.Cols.PACKAGE_NAME + ");");
 | 
					 | 
				
			||||||
        db.execSQL("CREATE INDEX IF NOT EXISTS name on " + AppMetadataTable.NAME + " (" + AppMetadataTable.Cols.NAME + ");"); // Used for sorting most lists
 | 
					        db.execSQL("CREATE INDEX IF NOT EXISTS name on " + AppMetadataTable.NAME + " (" + AppMetadataTable.Cols.NAME + ");"); // Used for sorting most lists
 | 
				
			||||||
        db.execSQL("CREATE INDEX IF NOT EXISTS added on " + AppMetadataTable.NAME + " (" + AppMetadataTable.Cols.ADDED + ");"); // Used for sorting "newly added"
 | 
					        db.execSQL("CREATE INDEX IF NOT EXISTS added on " + AppMetadataTable.NAME + " (" + AppMetadataTable.Cols.ADDED + ");"); // Used for sorting "newly added"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.PACKAGE_ID)) {
 | 
				
			||||||
 | 
					            db.execSQL("CREATE INDEX IF NOT EXISTS metadata_packageId ON " + AppMetadataTable.NAME + " (" + AppMetadataTable.Cols.PACKAGE_ID + ");");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (columnExists(db, AppMetadataTable.NAME, AppMetadataTable.Cols.REPO_ID)) {
 | 
				
			||||||
 | 
					            db.execSQL("CREATE INDEX IF NOT EXISTS metadata_repoId ON " + AppMetadataTable.NAME + " (" + AppMetadataTable.Cols.REPO_ID + ");");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Utils.debugLog(TAG, "Ensuring indexes exist for " + ApkTable.NAME);
 | 
					        Utils.debugLog(TAG, "Ensuring indexes exist for " + ApkTable.NAME);
 | 
				
			||||||
        db.execSQL("CREATE INDEX IF NOT EXISTS apk_vercode on " + ApkTable.NAME + " (" + ApkTable.Cols.VERSION_CODE + ");");
 | 
					        db.execSQL("CREATE INDEX IF NOT EXISTS apk_vercode on " + ApkTable.NAME + " (" + ApkTable.Cols.VERSION_CODE + ");");
 | 
				
			||||||
        db.execSQL("CREATE INDEX IF NOT EXISTS apk_appId on " + ApkTable.NAME + " (" + ApkTable.Cols.APP_ID + ");");
 | 
					        db.execSQL("CREATE INDEX IF NOT EXISTS apk_appId on " + ApkTable.NAME + " (" + ApkTable.Cols.APP_ID + ");");
 | 
				
			||||||
 | 
				
			|||||||
@ -157,4 +157,8 @@ public abstract class FDroidProvider extends ContentProvider {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected String getPackageIdFromPackageNameQuery() {
 | 
				
			||||||
 | 
					        return "SELECT " + Schema.PackageTable.Cols.ROW_ID + " FROM " + Schema.PackageTable.NAME + " WHERE " + Schema.PackageTable.Cols.PACKAGE_NAME + " = ?";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										173
									
								
								app/src/main/java/org/fdroid/fdroid/data/PackageProvider.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								app/src/main/java/org/fdroid/fdroid/data/PackageProvider.java
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,173 @@
 | 
				
			|||||||
 | 
					package org.fdroid.fdroid.data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.ContentValues;
 | 
				
			||||||
 | 
					import android.content.Context;
 | 
				
			||||||
 | 
					import android.content.UriMatcher;
 | 
				
			||||||
 | 
					import android.database.Cursor;
 | 
				
			||||||
 | 
					import android.net.Uri;
 | 
				
			||||||
 | 
					import android.util.Log;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.Schema.PackageTable;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.Schema.PackageTable.Cols;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class PackageProvider extends FDroidProvider {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final String TAG = "PackageProvider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static final class Helper {
 | 
				
			||||||
 | 
					        private Helper() { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static long ensureExists(Context context, String packageName) {
 | 
				
			||||||
 | 
					            long id = getPackageId(context, packageName);
 | 
				
			||||||
 | 
					            if (id <= 0) {
 | 
				
			||||||
 | 
					                ContentValues values = new ContentValues(1);
 | 
				
			||||||
 | 
					                values.put(Cols.PACKAGE_NAME, packageName);
 | 
				
			||||||
 | 
					                Uri uri = context.getContentResolver().insert(getContentUri(), values);
 | 
				
			||||||
 | 
					                id = Long.parseLong(uri.getLastPathSegment());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return id;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public static long getPackageId(Context context, String packageName) {
 | 
				
			||||||
 | 
					            Cursor cursor = context.getContentResolver().query(getPackageUri(packageName), Cols.ALL, null, null, null);
 | 
				
			||||||
 | 
					            if (cursor == null) {
 | 
				
			||||||
 | 
					                return 0;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                if (cursor.getCount() == 0) {
 | 
				
			||||||
 | 
					                    return 0;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    cursor.moveToFirst();
 | 
				
			||||||
 | 
					                    return cursor.getLong(cursor.getColumnIndexOrThrow(Cols.PACKAGE_NAME));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } finally {
 | 
				
			||||||
 | 
					                cursor.close();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private class Query extends QueryBuilder {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        protected String getRequiredTables() {
 | 
				
			||||||
 | 
					            return PackageTable.NAME;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public void addField(String field) {
 | 
				
			||||||
 | 
					            appendField(field, getTableName());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final String PROVIDER_NAME = "PackageProvider";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final UriMatcher MATCHER = new UriMatcher(-1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final String PATH_PACKAGE_NAME = "packageName";
 | 
				
			||||||
 | 
					    private static final String PATH_PACKAGE_ID = "packageId";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static {
 | 
				
			||||||
 | 
					        MATCHER.addURI(getAuthority(), PATH_PACKAGE_NAME + "/*", CODE_SINGLE);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static Uri getContentUri() {
 | 
				
			||||||
 | 
					        return Uri.parse("content://" + getAuthority());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static Uri getPackageUri(String packageName) {
 | 
				
			||||||
 | 
					        return getContentUri()
 | 
				
			||||||
 | 
					                .buildUpon()
 | 
				
			||||||
 | 
					                .appendPath(PATH_PACKAGE_NAME)
 | 
				
			||||||
 | 
					                .appendPath(packageName)
 | 
				
			||||||
 | 
					                .build();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Not actually used as part of the external API to this content provider.
 | 
				
			||||||
 | 
					     * Rather, used as a mechanism for returning the ID of a newly inserted row after calling
 | 
				
			||||||
 | 
					     * {@link android.content.ContentProvider#insert(Uri, ContentValues)}, as that is only able
 | 
				
			||||||
 | 
					     * to return a {@link Uri}. The {@link Uri#getLastPathSegment()} of this URI contains a
 | 
				
			||||||
 | 
					     * {@link Long} which is the {@link PackageTable.Cols#ROW_ID} of the newly inserted row.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private static Uri getPackageIdUri(long packageId) {
 | 
				
			||||||
 | 
					        return getContentUri()
 | 
				
			||||||
 | 
					                .buildUpon()
 | 
				
			||||||
 | 
					                .appendPath(PATH_PACKAGE_ID)
 | 
				
			||||||
 | 
					                .appendPath(Long.toString(packageId))
 | 
				
			||||||
 | 
					                .build();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected String getTableName() {
 | 
				
			||||||
 | 
					        return PackageTable.NAME;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected String getProviderName() {
 | 
				
			||||||
 | 
					        return "PackageProvider";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static String getAuthority() {
 | 
				
			||||||
 | 
					        return AUTHORITY + "." + PROVIDER_NAME;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    protected UriMatcher getMatcher() {
 | 
				
			||||||
 | 
					        return MATCHER;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected QuerySelection querySingle(String packageName) {
 | 
				
			||||||
 | 
					        final String selection = getTableName() + "." + Cols.PACKAGE_NAME + " = ?";
 | 
				
			||||||
 | 
					        final String[] args = {packageName};
 | 
				
			||||||
 | 
					        return new QuerySelection(selection, args);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public Cursor query(Uri uri, String[] projection, String customSelection, String[] selectionArgs, String sortOrder) {
 | 
				
			||||||
 | 
					        QuerySelection selection = new QuerySelection(customSelection, selectionArgs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (MATCHER.match(uri)) {
 | 
				
			||||||
 | 
					            case CODE_SINGLE:
 | 
				
			||||||
 | 
					                selection = selection.add(querySingle(uri.getLastPathSegment()));
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                Log.e(TAG, "Invalid URI for content provider: " + uri);
 | 
				
			||||||
 | 
					                throw new UnsupportedOperationException("Invalid URI for content provider: " + uri);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Query query = new Query();
 | 
				
			||||||
 | 
					        query.addSelection(selection);
 | 
				
			||||||
 | 
					        query.addFields(projection);
 | 
				
			||||||
 | 
					        query.addOrderBy(sortOrder);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Cursor cursor = LoggingQuery.query(db(), query.toString(), query.getArgs());
 | 
				
			||||||
 | 
					        cursor.setNotificationUri(getContext().getContentResolver(), uri);
 | 
				
			||||||
 | 
					        return cursor;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Deleting of packages is not required.
 | 
				
			||||||
 | 
					     * It doesn't matter if we have a package name in the database after the package is no longer
 | 
				
			||||||
 | 
					     * present in the repo any more. They wont take up much space, and it is the presence of rows
 | 
				
			||||||
 | 
					     * in the {@link Schema.MetadataTable} which decides whether something is available in the
 | 
				
			||||||
 | 
					     * F-Droid client or not.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public int delete(Uri uri, String where, String[] whereArgs) {
 | 
				
			||||||
 | 
					        throw new UnsupportedOperationException("Delete not supported for " + uri + ".");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public Uri insert(Uri uri, ContentValues values) {
 | 
				
			||||||
 | 
					        long rowId = db().insertOrThrow(getTableName(), null, values);
 | 
				
			||||||
 | 
					        getContext().getContentResolver().notifyChange(AppProvider.getCanUpdateUri(), null);
 | 
				
			||||||
 | 
					        return getPackageIdUri(rowId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
 | 
				
			||||||
 | 
					        throw new UnsupportedOperationException("Update not supported for " + uri + ".");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -143,7 +143,7 @@ public class RepoPersister {
 | 
				
			|||||||
        for (App app : apps) {
 | 
					        for (App app : apps) {
 | 
				
			||||||
            packageNames.add(app.packageName);
 | 
					            packageNames.add(app.packageName);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        String[] projection = {Schema.AppMetadataTable.Cols.ROW_ID, Schema.AppMetadataTable.Cols.PACKAGE_NAME};
 | 
					        String[] projection = {Schema.AppMetadataTable.Cols.ROW_ID, Schema.AppMetadataTable.Cols.Package.PACKAGE_NAME};
 | 
				
			||||||
        List<App> fromDb = TempAppProvider.Helper.findByPackageNames(context, packageNames, projection);
 | 
					        List<App> fromDb = TempAppProvider.Helper.findByPackageNames(context, packageNames, projection);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Map<String, Long> ids = new HashMap<>(fromDb.size());
 | 
					        Map<String, Long> ids = new HashMap<>(fromDb.size());
 | 
				
			||||||
@ -175,7 +175,7 @@ public class RepoPersister {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    private ArrayList<ContentProviderOperation> insertOrUpdateApks(List<Apk> packages) {
 | 
					    private ArrayList<ContentProviderOperation> insertOrUpdateApks(List<Apk> packages) {
 | 
				
			||||||
        String[] projection = new String[]{
 | 
					        String[] projection = new String[]{
 | 
				
			||||||
                Schema.ApkTable.Cols.App.PACKAGE_NAME,
 | 
					                Schema.ApkTable.Cols.Package.PACKAGE_NAME,
 | 
				
			||||||
                Schema.ApkTable.Cols.VERSION_CODE,
 | 
					                Schema.ApkTable.Cols.VERSION_CODE,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        List<Apk> existingApks = ApkProvider.Helper.knownApks(context, packages, projection);
 | 
					        List<Apk> existingApks = ApkProvider.Helper.knownApks(context, packages, projection);
 | 
				
			||||||
@ -204,7 +204,7 @@ public class RepoPersister {
 | 
				
			|||||||
     * <strong>Does not do any checks to see if the app already exists or not.</strong>
 | 
					     * <strong>Does not do any checks to see if the app already exists or not.</strong>
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private ContentProviderOperation updateExistingApp(App app) {
 | 
					    private ContentProviderOperation updateExistingApp(App app) {
 | 
				
			||||||
        Uri uri = TempAppProvider.getAppUri(app);
 | 
					        Uri uri = TempAppProvider.getAppUri(app.packageName, app.repoId);
 | 
				
			||||||
        return ContentProviderOperation.newUpdate(uri).withValues(app.toContentValues()).build();
 | 
					        return ContentProviderOperation.newUpdate(uri).withValues(app.toContentValues()).build();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -224,8 +224,8 @@ public class RepoPersister {
 | 
				
			|||||||
     * array.
 | 
					     * array.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private boolean isAppInDatabase(App app) {
 | 
					    private boolean isAppInDatabase(App app) {
 | 
				
			||||||
        String[] fields = {Schema.AppMetadataTable.Cols.PACKAGE_NAME};
 | 
					        String[] fields = {Schema.AppMetadataTable.Cols.Package.PACKAGE_NAME};
 | 
				
			||||||
        App found = AppProvider.Helper.findByPackageName(context.getContentResolver(), app.packageName, fields);
 | 
					        App found = AppProvider.Helper.findByPackageName(context.getContentResolver(), app.packageName, repo.id, fields);
 | 
				
			||||||
        return found != null;
 | 
					        return found != null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -255,7 +255,7 @@ public class RepoPersister {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    @Nullable
 | 
					    @Nullable
 | 
				
			||||||
    private ContentProviderOperation deleteOrphanedApks(List<App> apps, Map<String, List<Apk>> packages) {
 | 
					    private ContentProviderOperation deleteOrphanedApks(List<App> apps, Map<String, List<Apk>> packages) {
 | 
				
			||||||
        String[] projection = new String[]{Schema.ApkTable.Cols.App.PACKAGE_NAME, Schema.ApkTable.Cols.VERSION_CODE};
 | 
					        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.find(context, repo, apps, projection);
 | 
				
			||||||
        List<Apk> toDelete = new ArrayList<>();
 | 
					        List<Apk> toDelete = new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -53,7 +53,8 @@ public interface Schema {
 | 
				
			|||||||
            String ROW_ID = "rowid";
 | 
					            String ROW_ID = "rowid";
 | 
				
			||||||
            String _COUNT = "_count";
 | 
					            String _COUNT = "_count";
 | 
				
			||||||
            String IS_COMPATIBLE = "compatible";
 | 
					            String IS_COMPATIBLE = "compatible";
 | 
				
			||||||
            String PACKAGE_NAME = "id";
 | 
					            String PACKAGE_ID = "packageId";
 | 
				
			||||||
 | 
					            String REPO_ID = "repoId";
 | 
				
			||||||
            String NAME = "name";
 | 
					            String NAME = "name";
 | 
				
			||||||
            String SUMMARY = "summary";
 | 
					            String SUMMARY = "summary";
 | 
				
			||||||
            String ICON = "icon";
 | 
					            String ICON = "icon";
 | 
				
			||||||
@ -90,13 +91,17 @@ public interface Schema {
 | 
				
			|||||||
                String SIGNATURE = "installedSig";
 | 
					                String SIGNATURE = "installedSig";
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            interface Package {
 | 
				
			||||||
 | 
					                String PACKAGE_NAME = "package_packageName";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            /**
 | 
					            /**
 | 
				
			||||||
             * Each of the physical columns in the sqlite table. Differs from {@link Cols#ALL} in
 | 
					             * Each of the physical columns in the sqlite table. Differs from {@link Cols#ALL} in
 | 
				
			||||||
             * that it doesn't include fields which are aliases of other fields (e.g. {@link Cols#_ID}
 | 
					             * that it doesn't include fields which are aliases of other fields (e.g. {@link Cols#_ID}
 | 
				
			||||||
             * or which are from other related tables (e.g. {@link Cols.SuggestedApk#VERSION_NAME}).
 | 
					             * or which are from other related tables (e.g. {@link Cols.SuggestedApk#VERSION_NAME}).
 | 
				
			||||||
             */
 | 
					             */
 | 
				
			||||||
            String[] ALL_COLS = {
 | 
					            String[] ALL_COLS = {
 | 
				
			||||||
                    ROW_ID, IS_COMPATIBLE, PACKAGE_NAME, NAME, SUMMARY, ICON, DESCRIPTION,
 | 
					                    ROW_ID, PACKAGE_ID, IS_COMPATIBLE, NAME, SUMMARY, ICON, DESCRIPTION,
 | 
				
			||||||
                    LICENSE, AUTHOR, EMAIL, WEB_URL, TRACKER_URL, SOURCE_URL,
 | 
					                    LICENSE, AUTHOR, EMAIL, WEB_URL, TRACKER_URL, SOURCE_URL,
 | 
				
			||||||
                    CHANGELOG_URL, DONATE_URL, BITCOIN_ADDR, LITECOIN_ADDR, FLATTR_ID,
 | 
					                    CHANGELOG_URL, DONATE_URL, BITCOIN_ADDR, LITECOIN_ADDR, FLATTR_ID,
 | 
				
			||||||
                    UPSTREAM_VERSION_NAME, UPSTREAM_VERSION_CODE, ADDED, LAST_UPDATED,
 | 
					                    UPSTREAM_VERSION_NAME, UPSTREAM_VERSION_CODE, ADDED, LAST_UPDATED,
 | 
				
			||||||
@ -110,14 +115,14 @@ public interface Schema {
 | 
				
			|||||||
             * @see Cols#ALL_COLS
 | 
					             * @see Cols#ALL_COLS
 | 
				
			||||||
             */
 | 
					             */
 | 
				
			||||||
            String[] ALL = {
 | 
					            String[] ALL = {
 | 
				
			||||||
                    _ID, ROW_ID, IS_COMPATIBLE, PACKAGE_NAME, NAME, SUMMARY, ICON, DESCRIPTION,
 | 
					                    _ID, ROW_ID, IS_COMPATIBLE, NAME, SUMMARY, ICON, DESCRIPTION,
 | 
				
			||||||
                    LICENSE, AUTHOR, EMAIL, WEB_URL, TRACKER_URL, SOURCE_URL,
 | 
					                    LICENSE, AUTHOR, EMAIL, WEB_URL, TRACKER_URL, SOURCE_URL,
 | 
				
			||||||
                    CHANGELOG_URL, DONATE_URL, BITCOIN_ADDR, LITECOIN_ADDR, FLATTR_ID,
 | 
					                    CHANGELOG_URL, DONATE_URL, BITCOIN_ADDR, LITECOIN_ADDR, FLATTR_ID,
 | 
				
			||||||
                    UPSTREAM_VERSION_NAME, UPSTREAM_VERSION_CODE, ADDED, LAST_UPDATED,
 | 
					                    UPSTREAM_VERSION_NAME, UPSTREAM_VERSION_CODE, ADDED, LAST_UPDATED,
 | 
				
			||||||
                    CATEGORIES, ANTI_FEATURES, REQUIREMENTS, ICON_URL, ICON_URL_LARGE,
 | 
					                    CATEGORIES, ANTI_FEATURES, REQUIREMENTS, ICON_URL, ICON_URL_LARGE,
 | 
				
			||||||
                    SUGGESTED_VERSION_CODE, SuggestedApk.VERSION_NAME,
 | 
					                    SUGGESTED_VERSION_CODE, SuggestedApk.VERSION_NAME,
 | 
				
			||||||
                    InstalledApp.VERSION_CODE, InstalledApp.VERSION_NAME,
 | 
					                    InstalledApp.VERSION_CODE, InstalledApp.VERSION_NAME,
 | 
				
			||||||
                    InstalledApp.SIGNATURE,
 | 
					                    InstalledApp.SIGNATURE, Package.PACKAGE_NAME
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -163,8 +168,8 @@ public interface Schema {
 | 
				
			|||||||
                String ADDRESS = "repoAddress";
 | 
					                String ADDRESS = "repoAddress";
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            interface App {
 | 
					            interface Package {
 | 
				
			||||||
                String PACKAGE_NAME = "appPackageName";
 | 
					                String PACKAGE_NAME = "package_packageName";
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            /**
 | 
					            /**
 | 
				
			||||||
@ -181,7 +186,7 @@ public interface Schema {
 | 
				
			|||||||
             * @see AppMetadataTable.Cols#ALL
 | 
					             * @see AppMetadataTable.Cols#ALL
 | 
				
			||||||
             */
 | 
					             */
 | 
				
			||||||
            String[] ALL = {
 | 
					            String[] ALL = {
 | 
				
			||||||
                    _ID, APP_ID, App.PACKAGE_NAME, VERSION_NAME, REPO_ID, HASH, VERSION_CODE, NAME,
 | 
					                    _ID, APP_ID, Package.PACKAGE_NAME, VERSION_NAME, REPO_ID, HASH, VERSION_CODE, NAME,
 | 
				
			||||||
                    SIZE, SIGNATURE, SOURCE_NAME, MIN_SDK_VERSION, TARGET_SDK_VERSION, MAX_SDK_VERSION,
 | 
					                    SIZE, SIGNATURE, SOURCE_NAME, MIN_SDK_VERSION, TARGET_SDK_VERSION, MAX_SDK_VERSION,
 | 
				
			||||||
                    PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE,
 | 
					                    PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE,
 | 
				
			||||||
                    IS_COMPATIBLE, Repo.VERSION, Repo.ADDRESS, INCOMPATIBLE_REASONS,
 | 
					                    IS_COMPATIBLE, Repo.VERSION, Repo.ADDRESS, INCOMPATIBLE_REASONS,
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,7 @@ import java.util.List;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.ApkTable;
 | 
					import org.fdroid.fdroid.data.Schema.ApkTable;
 | 
				
			||||||
import org.fdroid.fdroid.data.Schema.AppMetadataTable;
 | 
					import org.fdroid.fdroid.data.Schema.AppMetadataTable;
 | 
				
			||||||
 | 
					import org.fdroid.fdroid.data.Schema.PackageTable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * This class does all of its operations in a temporary sqlite table.
 | 
					 * This class does all of its operations in a temporary sqlite table.
 | 
				
			||||||
@ -41,7 +42,7 @@ public class TempAppProvider extends AppProvider {
 | 
				
			|||||||
        MATCHER.addURI(getAuthority(), PATH_INIT, CODE_INIT);
 | 
					        MATCHER.addURI(getAuthority(), PATH_INIT, CODE_INIT);
 | 
				
			||||||
        MATCHER.addURI(getAuthority(), PATH_COMMIT, CODE_COMMIT);
 | 
					        MATCHER.addURI(getAuthority(), PATH_COMMIT, CODE_COMMIT);
 | 
				
			||||||
        MATCHER.addURI(getAuthority(), PATH_APPS + "/*", APPS);
 | 
					        MATCHER.addURI(getAuthority(), PATH_APPS + "/*", APPS);
 | 
				
			||||||
        MATCHER.addURI(getAuthority(), "*", CODE_SINGLE);
 | 
					        MATCHER.addURI(getAuthority(), PATH_APP + "/#/*", CODE_SINGLE);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
@ -57,8 +58,13 @@ public class TempAppProvider extends AppProvider {
 | 
				
			|||||||
        return Uri.parse("content://" + getAuthority());
 | 
					        return Uri.parse("content://" + getAuthority());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static Uri getAppUri(App app) {
 | 
					    public static Uri getAppUri(String packageName, long repoId) {
 | 
				
			||||||
        return Uri.withAppendedPath(getContentUri(), app.packageName);
 | 
					        return getContentUri()
 | 
				
			||||||
 | 
					                .buildUpon()
 | 
				
			||||||
 | 
					                .appendPath(PATH_APP)
 | 
				
			||||||
 | 
					                .appendPath(Long.toString(repoId))
 | 
				
			||||||
 | 
					                .appendPath(packageName)
 | 
				
			||||||
 | 
					                .build();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static Uri getAppsUri(List<String> apps) {
 | 
					    public static Uri getAppsUri(List<String> apps) {
 | 
				
			||||||
@ -69,7 +75,7 @@ public class TempAppProvider extends AppProvider {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private AppQuerySelection queryApps(String packageNames) {
 | 
					    private AppQuerySelection queryApps(String packageNames) {
 | 
				
			||||||
        return queryPackageNames(packageNames, getTableName() + "." + AppMetadataTable.Cols.PACKAGE_NAME);
 | 
					        return queryPackageNames(packageNames, PackageTable.NAME + "." + PackageTable.Cols.PACKAGE_NAME);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static class Helper {
 | 
					    public static class Helper {
 | 
				
			||||||
@ -126,7 +132,13 @@ public class TempAppProvider extends AppProvider {
 | 
				
			|||||||
            throw new UnsupportedOperationException("Update not supported for " + uri + ".");
 | 
					            throw new UnsupportedOperationException("Update not supported for " + uri + ".");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        QuerySelection query = new QuerySelection(where, whereArgs).add(querySingle(uri.getLastPathSegment()));
 | 
					        List<String> pathParts = uri.getPathSegments();
 | 
				
			||||||
 | 
					        String packageName = pathParts.get(2);
 | 
				
			||||||
 | 
					        long repoId = Long.parseLong(pathParts.get(1));
 | 
				
			||||||
 | 
					        QuerySelection query = new QuerySelection(where, whereArgs).add(querySingleForUpdate(packageName, repoId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Package names for apps cannot change...
 | 
				
			||||||
 | 
					        values.remove(AppMetadataTable.Cols.Package.PACKAGE_NAME);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        int count = db().update(getTableName(), values, query.getSelection(), query.getArgs());
 | 
					        int count = db().update(getTableName(), values, query.getSelection(), query.getArgs());
 | 
				
			||||||
        if (!isApplyingBatch()) {
 | 
					        if (!isApplyingBatch()) {
 | 
				
			||||||
@ -163,7 +175,7 @@ public class TempAppProvider extends AppProvider {
 | 
				
			|||||||
        db.execSQL("ATTACH DATABASE ':memory:' AS " + DB);
 | 
					        db.execSQL("ATTACH DATABASE ':memory:' AS " + DB);
 | 
				
			||||||
        db.execSQL(DBHelper.CREATE_TABLE_APP_METADATA.replaceFirst(AppMetadataTable.NAME, DB + "." + getTableName()));
 | 
					        db.execSQL(DBHelper.CREATE_TABLE_APP_METADATA.replaceFirst(AppMetadataTable.NAME, DB + "." + getTableName()));
 | 
				
			||||||
        db.execSQL(copyData(AppMetadataTable.Cols.ALL_COLS, AppMetadataTable.NAME, DB + "." + getTableName()));
 | 
					        db.execSQL(copyData(AppMetadataTable.Cols.ALL_COLS, AppMetadataTable.NAME, DB + "." + getTableName()));
 | 
				
			||||||
        db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_id ON " + getTableName() + " (" + AppMetadataTable.Cols.PACKAGE_NAME + ");");
 | 
					        db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_id ON " + getTableName() + " (" + AppMetadataTable.Cols.PACKAGE_ID + ");");
 | 
				
			||||||
        db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_upstreamVercode ON " + getTableName() + " (" + AppMetadataTable.Cols.UPSTREAM_VERSION_CODE + ");");
 | 
					        db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_upstreamVercode ON " + getTableName() + " (" + AppMetadataTable.Cols.UPSTREAM_VERSION_CODE + ");");
 | 
				
			||||||
        db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_compatible ON " + getTableName() + " (" + AppMetadataTable.Cols.IS_COMPATIBLE + ");");
 | 
					        db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_compatible ON " + getTableName() + " (" + AppMetadataTable.Cols.IS_COMPATIBLE + ");");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -259,7 +259,7 @@ public class InstallManagerService extends Service {
 | 
				
			|||||||
                            App app = getAppFromActive(downloadUrl);
 | 
					                            App app = getAppFromActive(downloadUrl);
 | 
				
			||||||
                            if (app == null) {
 | 
					                            if (app == null) {
 | 
				
			||||||
                                ContentResolver resolver = context.getContentResolver();
 | 
					                                ContentResolver resolver = context.getContentResolver();
 | 
				
			||||||
                                app = AppProvider.Helper.findByPackageName(resolver, apk.packageName);
 | 
					                                app = AppProvider.Helper.findByPackageName(resolver, apk.packageName, apk.repo);
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                            // show notification if app details is not visible
 | 
					                            // show notification if app details is not visible
 | 
				
			||||||
                            if (app != null && AppDetails.isAppVisible(app.packageName)) {
 | 
					                            if (app != null && AppDetails.isAppVisible(app.packageName)) {
 | 
				
			||||||
@ -346,7 +346,7 @@ public class InstallManagerService extends Service {
 | 
				
			|||||||
            String name = getAppName(apk);
 | 
					            String name = getAppName(apk);
 | 
				
			||||||
            if (TextUtils.isEmpty(name) || name.equals(new App().name)) {
 | 
					            if (TextUtils.isEmpty(name) || name.equals(new App().name)) {
 | 
				
			||||||
                ContentResolver resolver = getContentResolver();
 | 
					                ContentResolver resolver = getContentResolver();
 | 
				
			||||||
                App app = AppProvider.Helper.findByPackageName(resolver, apk.packageName);
 | 
					                App app = AppProvider.Helper.findByPackageName(resolver, apk.packageName, apk.repo);
 | 
				
			||||||
                if (app == null || TextUtils.isEmpty(app.name)) {
 | 
					                if (app == null || TextUtils.isEmpty(app.name)) {
 | 
				
			||||||
                    return;  // do not have a name to display, so leave notification as is
 | 
					                    return;  // do not have a name to display, so leave notification as is
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
				
			|||||||
@ -191,7 +191,7 @@ public class InstallConfirmActivity extends FragmentActivity implements OnCancel
 | 
				
			|||||||
        intent = getIntent();
 | 
					        intent = getIntent();
 | 
				
			||||||
        Uri uri = intent.getData();
 | 
					        Uri uri = intent.getData();
 | 
				
			||||||
        Apk apk = ApkProvider.Helper.findByUri(this, uri, Schema.ApkTable.Cols.ALL);
 | 
					        Apk apk = ApkProvider.Helper.findByUri(this, uri, Schema.ApkTable.Cols.ALL);
 | 
				
			||||||
        app = AppProvider.Helper.findByPackageName(getContentResolver(), apk.packageName);
 | 
					        app = AppProvider.Helper.findByPackageName(getContentResolver(), apk.packageName, apk.repo, Schema.AppMetadataTable.Cols.ALL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        appDiff = new AppDiff(getPackageManager(), apk);
 | 
					        appDiff = new AppDiff(getPackageManager(), apk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -39,7 +39,7 @@ public abstract class AppListFragment extends ListFragment implements
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private static final String[] APP_PROJECTION = {
 | 
					    private static final String[] APP_PROJECTION = {
 | 
				
			||||||
            AppMetadataTable.Cols._ID, // Required for cursor loader to work.
 | 
					            AppMetadataTable.Cols._ID, // Required for cursor loader to work.
 | 
				
			||||||
            AppMetadataTable.Cols.PACKAGE_NAME,
 | 
					            AppMetadataTable.Cols.Package.PACKAGE_NAME,
 | 
				
			||||||
            AppMetadataTable.Cols.NAME,
 | 
					            AppMetadataTable.Cols.NAME,
 | 
				
			||||||
            AppMetadataTable.Cols.SUMMARY,
 | 
					            AppMetadataTable.Cols.SUMMARY,
 | 
				
			||||||
            AppMetadataTable.Cols.IS_COMPATIBLE,
 | 
					            AppMetadataTable.Cols.IS_COMPATIBLE,
 | 
				
			||||||
 | 
				
			|||||||
@ -289,7 +289,8 @@ public class SwapAppsView extends ListView implements
 | 
				
			|||||||
                public void onChange(boolean selfChange) {
 | 
					                public void onChange(boolean selfChange) {
 | 
				
			||||||
                    Activity activity = getActivity();
 | 
					                    Activity activity = getActivity();
 | 
				
			||||||
                    if (activity != null) {
 | 
					                    if (activity != null) {
 | 
				
			||||||
                        app = AppProvider.Helper.findByPackageName(getActivity().getContentResolver(), app.packageName);
 | 
					                        app = AppProvider.Helper.findByPackageName(getActivity().getContentResolver(),
 | 
				
			||||||
 | 
					                                app.packageName, app.repoId, AppMetadataTable.Cols.ALL);
 | 
				
			||||||
                        resetView();
 | 
					                        resetView();
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -318,7 +319,7 @@ public class SwapAppsView extends ListView implements
 | 
				
			|||||||
                    // implemented on API-16, so leaving like this for now.
 | 
					                    // implemented on API-16, so leaving like this for now.
 | 
				
			||||||
                    getActivity().getContentResolver().unregisterContentObserver(appObserver);
 | 
					                    getActivity().getContentResolver().unregisterContentObserver(appObserver);
 | 
				
			||||||
                    getActivity().getContentResolver().registerContentObserver(
 | 
					                    getActivity().getContentResolver().registerContentObserver(
 | 
				
			||||||
                            AppProvider.getContentUri(this.app.packageName), true, appObserver);
 | 
					                            AppProvider.getAppUri(this.app), true, appObserver);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                resetView();
 | 
					                resetView();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -183,7 +183,8 @@ public class Assert {
 | 
				
			|||||||
    public static App insertApp(Context context, String packageName, String name, ContentValues additionalValues) {
 | 
					    public static App insertApp(Context context, String packageName, String name, ContentValues additionalValues) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ContentValues values = new ContentValues();
 | 
					        ContentValues values = new ContentValues();
 | 
				
			||||||
        values.put(AppMetadataTable.Cols.PACKAGE_NAME, packageName);
 | 
					        values.put(AppMetadataTable.Cols.REPO_ID, 1);
 | 
				
			||||||
 | 
					        values.put(AppMetadataTable.Cols.Package.PACKAGE_NAME, packageName);
 | 
				
			||||||
        values.put(AppMetadataTable.Cols.NAME, name);
 | 
					        values.put(AppMetadataTable.Cols.NAME, name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Required fields (NOT NULL in the database).
 | 
					        // Required fields (NOT NULL in the database).
 | 
				
			||||||
@ -197,14 +198,14 @@ public class Assert {
 | 
				
			|||||||
        Uri uri = AppProvider.getContentUri();
 | 
					        Uri uri = AppProvider.getContentUri();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        context.getContentResolver().insert(uri, values);
 | 
					        context.getContentResolver().insert(uri, values);
 | 
				
			||||||
        return AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName);
 | 
					        return AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName, 1);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static App ensureApp(Context context, String packageName) {
 | 
					    private static App ensureApp(Context context, String packageName) {
 | 
				
			||||||
        App app = AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName);
 | 
					        App app = AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName, 1);
 | 
				
			||||||
        if (app == null) {
 | 
					        if (app == null) {
 | 
				
			||||||
            insertApp(context, packageName, packageName);
 | 
					            insertApp(context, packageName, packageName);
 | 
				
			||||||
            app = AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName);
 | 
					            app = AppProvider.Helper.findByPackageName(context.getContentResolver(), packageName, 1);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        assertNotNull(app);
 | 
					        assertNotNull(app);
 | 
				
			||||||
        return app;
 | 
					        return app;
 | 
				
			||||||
 | 
				
			|||||||
@ -284,7 +284,7 @@ public class ApkProviderTest extends FDroidProviderTest {
 | 
				
			|||||||
        Collections.addAll(apksToCheck, unknown);
 | 
					        Collections.addAll(apksToCheck, unknown);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        String[] projection = {
 | 
					        String[] projection = {
 | 
				
			||||||
            Cols.App.PACKAGE_NAME,
 | 
					            Cols.Package.PACKAGE_NAME,
 | 
				
			||||||
            Cols.VERSION_CODE,
 | 
					            Cols.VERSION_CODE,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -424,7 +424,7 @@ public class ApkProviderTest extends FDroidProviderTest {
 | 
				
			|||||||
        assertEquals("a hash type", apk.hashType);
 | 
					        assertEquals("a hash type", apk.hashType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        String[] projection = {
 | 
					        String[] projection = {
 | 
				
			||||||
            Cols.App.PACKAGE_NAME,
 | 
					            Cols.Package.PACKAGE_NAME,
 | 
				
			||||||
            Cols.HASH,
 | 
					            Cols.HASH,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -68,7 +68,7 @@ public class AppProviderTest extends FDroidProviderTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
    public void testCantFindApp() {
 | 
					    public void testCantFindApp() {
 | 
				
			||||||
        assertNull(AppProvider.Helper.findByPackageName(context.getContentResolver(), "com.example.doesnt-exist"));
 | 
					        assertNull(AppProvider.Helper.findByPackageName(context.getContentResolver(), "com.example.doesnt-exist", 1));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
@ -111,14 +111,14 @@ public class AppProviderTest extends FDroidProviderTest {
 | 
				
			|||||||
        ContentResolver r = context.getContentResolver();
 | 
					        ContentResolver r = context.getContentResolver();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Can't "update", although can "install"...
 | 
					        // Can't "update", although can "install"...
 | 
				
			||||||
        App notInstalled = AppProvider.Helper.findByPackageName(r, "not installed");
 | 
					        App notInstalled = AppProvider.Helper.findByPackageName(r, "not installed", 1);
 | 
				
			||||||
        assertFalse(notInstalled.canAndWantToUpdate(context));
 | 
					        assertFalse(notInstalled.canAndWantToUpdate(context));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        App installedOnlyOneVersionAvailable   = AppProvider.Helper.findByPackageName(r, "installed, only one version available");
 | 
					        App installedOnlyOneVersionAvailable   = AppProvider.Helper.findByPackageName(r, "installed, only one version available", 1);
 | 
				
			||||||
        App installedAlreadyLatestNoIgnore     = AppProvider.Helper.findByPackageName(r, "installed, already latest, no ignore");
 | 
					        App installedAlreadyLatestNoIgnore     = AppProvider.Helper.findByPackageName(r, "installed, already latest, no ignore", 1);
 | 
				
			||||||
        App installedAlreadyLatestIgnoreAll    = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore all");
 | 
					        App installedAlreadyLatestIgnoreAll    = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore all", 1);
 | 
				
			||||||
        App installedAlreadyLatestIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore latest");
 | 
					        App installedAlreadyLatestIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore latest", 1);
 | 
				
			||||||
        App installedAlreadyLatestIgnoreOld    = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore old");
 | 
					        App installedAlreadyLatestIgnoreOld    = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore old", 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertFalse(installedOnlyOneVersionAvailable.canAndWantToUpdate(context));
 | 
					        assertFalse(installedOnlyOneVersionAvailable.canAndWantToUpdate(context));
 | 
				
			||||||
        assertFalse(installedAlreadyLatestNoIgnore.canAndWantToUpdate(context));
 | 
					        assertFalse(installedAlreadyLatestNoIgnore.canAndWantToUpdate(context));
 | 
				
			||||||
@ -126,10 +126,10 @@ public class AppProviderTest extends FDroidProviderTest {
 | 
				
			|||||||
        assertFalse(installedAlreadyLatestIgnoreLatest.canAndWantToUpdate(context));
 | 
					        assertFalse(installedAlreadyLatestIgnoreLatest.canAndWantToUpdate(context));
 | 
				
			||||||
        assertFalse(installedAlreadyLatestIgnoreOld.canAndWantToUpdate(context));
 | 
					        assertFalse(installedAlreadyLatestIgnoreOld.canAndWantToUpdate(context));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        App installedOldNoIgnore             = AppProvider.Helper.findByPackageName(r, "installed, old version, no ignore");
 | 
					        App installedOldNoIgnore             = AppProvider.Helper.findByPackageName(r, "installed, old version, no ignore", 1);
 | 
				
			||||||
        App installedOldIgnoreAll            = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore all");
 | 
					        App installedOldIgnoreAll            = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore all", 1);
 | 
				
			||||||
        App installedOldIgnoreLatest         = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore latest");
 | 
					        App installedOldIgnoreLatest         = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore latest", 1);
 | 
				
			||||||
        App installedOldIgnoreNewerNotLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore newer, but not latest");
 | 
					        App installedOldIgnoreNewerNotLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore newer, but not latest", 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertTrue(installedOldNoIgnore.canAndWantToUpdate(context));
 | 
					        assertTrue(installedOldNoIgnore.canAndWantToUpdate(context));
 | 
				
			||||||
        assertFalse(installedOldIgnoreAll.canAndWantToUpdate(context));
 | 
					        assertFalse(installedOldIgnoreAll.canAndWantToUpdate(context));
 | 
				
			||||||
@ -169,7 +169,7 @@ public class AppProviderTest extends FDroidProviderTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        assertResultCount(contentResolver, 10, AppProvider.getContentUri(), PROJ);
 | 
					        assertResultCount(contentResolver, 10, AppProvider.getContentUri(), PROJ);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        String[] projection = {Cols.PACKAGE_NAME};
 | 
					        String[] projection = {Cols.Package.PACKAGE_NAME};
 | 
				
			||||||
        List<App> canUpdateApps = AppProvider.Helper.findCanUpdate(context, projection);
 | 
					        List<App> canUpdateApps = AppProvider.Helper.findCanUpdate(context, projection);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        String[] expectedCanUpdate = {
 | 
					        String[] expectedCanUpdate = {
 | 
				
			||||||
@ -239,7 +239,7 @@ public class AppProviderTest extends FDroidProviderTest {
 | 
				
			|||||||
        assertEquals("org.fdroid.fdroid", app.packageName);
 | 
					        assertEquals("org.fdroid.fdroid", app.packageName);
 | 
				
			||||||
        assertEquals("F-Droid", app.name);
 | 
					        assertEquals("F-Droid", app.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        App otherApp = AppProvider.Helper.findByPackageName(context.getContentResolver(), "org.fdroid.fdroid");
 | 
					        App otherApp = AppProvider.Helper.findByPackageName(context.getContentResolver(), "org.fdroid.fdroid", 1);
 | 
				
			||||||
        assertNotNull(otherApp);
 | 
					        assertNotNull(otherApp);
 | 
				
			||||||
        assertEquals("org.fdroid.fdroid", otherApp.packageName);
 | 
					        assertEquals("org.fdroid.fdroid", otherApp.packageName);
 | 
				
			||||||
        assertEquals("F-Droid", otherApp.name);
 | 
					        assertEquals("F-Droid", otherApp.name);
 | 
				
			||||||
@ -260,7 +260,7 @@ public class AppProviderTest extends FDroidProviderTest {
 | 
				
			|||||||
        String[] projection = new String[] {
 | 
					        String[] projection = new String[] {
 | 
				
			||||||
                Cols._ID,
 | 
					                Cols._ID,
 | 
				
			||||||
                Cols.NAME,
 | 
					                Cols.NAME,
 | 
				
			||||||
                Cols.PACKAGE_NAME,
 | 
					                Cols.Package.PACKAGE_NAME,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        return contentResolver.query(AppProvider.getContentUri(), projection, null, null, null);
 | 
					        return contentResolver.query(AppProvider.getContentUri(), projection, null, null, null);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -356,7 +356,8 @@ public class AppProviderTest extends FDroidProviderTest {
 | 
				
			|||||||
    public App insertApp(String id, String name, ContentValues additionalValues) {
 | 
					    public App insertApp(String id, String name, ContentValues additionalValues) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ContentValues values = new ContentValues();
 | 
					        ContentValues values = new ContentValues();
 | 
				
			||||||
        values.put(Cols.PACKAGE_NAME, id);
 | 
					        values.put(Cols.Package.PACKAGE_NAME, id);
 | 
				
			||||||
 | 
					        values.put(Cols.REPO_ID, 1);
 | 
				
			||||||
        values.put(Cols.NAME, name);
 | 
					        values.put(Cols.NAME, name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Required fields (NOT NULL in the database).
 | 
					        // Required fields (NOT NULL in the database).
 | 
				
			||||||
@ -370,6 +371,6 @@ public class AppProviderTest extends FDroidProviderTest {
 | 
				
			|||||||
        Uri uri = AppProvider.getContentUri();
 | 
					        Uri uri = AppProvider.getContentUri();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        contentResolver.insert(uri, values);
 | 
					        contentResolver.insert(uri, values);
 | 
				
			||||||
        return AppProvider.Helper.findByPackageName(context.getContentResolver(), id);
 | 
					        return AppProvider.Helper.findByPackageName(context.getContentResolver(), id, 1);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -93,10 +93,10 @@ public class ProviderUriTests {
 | 
				
			|||||||
        assertValidUri(resolver, AppProvider.getCanUpdateUri(), "content://org.fdroid.fdroid.data.AppProvider/canUpdate", projection);
 | 
					        assertValidUri(resolver, AppProvider.getCanUpdateUri(), "content://org.fdroid.fdroid.data.AppProvider/canUpdate", projection);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        App app = new App();
 | 
					        App app = new App();
 | 
				
			||||||
 | 
					        app.repoId = 1;
 | 
				
			||||||
        app.packageName = "org.fdroid.fdroid";
 | 
					        app.packageName = "org.fdroid.fdroid";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertValidUri(resolver, AppProvider.getContentUri(app), "content://org.fdroid.fdroid.data.AppProvider/org.fdroid.fdroid", projection);
 | 
					        assertValidUri(resolver, AppProvider.getAppUri(app), "content://org.fdroid.fdroid.data.AppProvider/app/1/org.fdroid.fdroid", projection);
 | 
				
			||||||
        assertValidUri(resolver, AppProvider.getContentUri("org.fdroid.fdroid"), "content://org.fdroid.fdroid.data.AppProvider/org.fdroid.fdroid", projection);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Test
 | 
					    @Test
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user