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 org.fdroid.fdroid.data.Schema.PackageTable; import org.fdroid.fdroid.data.Schema.PackageTable.Cols; public class PackageProvider extends FDroidProvider { 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) { String[] projection = new String[] {Cols.ROW_ID}; Cursor cursor = context.getContentResolver().query(getPackageUri(packageName), projection, null, null, null); if (cursor == null) { return 0; } try { if (cursor.getCount() == 0) { return 0; } else { cursor.moveToFirst(); return cursor.getLong(cursor.getColumnIndexOrThrow(Cols.ROW_ID)); } } 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) { if (MATCHER.match(uri) != CODE_SINGLE) { throw new UnsupportedOperationException("Invalid URI for content provider: " + uri); } QuerySelection selection = new QuerySelection(customSelection, selectionArgs) .add(querySingle(uri.getLastPathSegment())); 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.AppMetadataTable} 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); } /** * Package names never change. If a package name has changed, then that means that it is a * new app all together as far as Android is concerned. */ @Override public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { throw new UnsupportedOperationException("Update not supported for " + uri + "."); } }