/* * Copyright (C) 2012-2013 Dominik Schürmann * Copyright (C) 2010 Thialfihar * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sufficientlysecure.keychain.provider; import java.util.Arrays; import java.util.HashMap; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.util.Log; import android.content.ContentProvider; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.provider.BaseColumns; import android.text.TextUtils; public class KeychainProvider extends ContentProvider { // public static final String ACTION_BROADCAST_DATABASE_CHANGE = Constants.PACKAGE_NAME // + ".action.DATABASE_CHANGE"; // // public static final String EXTRA_BROADCAST_KEY_TYPE = "key_type"; // public static final String EXTRA_BROADCAST_CONTENT_ITEM_TYPE = "contentItemType"; private static final int PUBLIC_KEY_RING = 101; private static final int PUBLIC_KEY_RING_BY_ROW_ID = 102; private static final int PUBLIC_KEY_RING_BY_MASTER_KEY_ID = 103; private static final int PUBLIC_KEY_RING_BY_KEY_ID = 104; private static final int PUBLIC_KEY_RING_BY_EMAILS = 105; private static final int PUBLIC_KEY_RING_BY_LIKE_EMAIL = 106; private static final int PUBLIC_KEY_RING_KEY = 111; private static final int PUBLIC_KEY_RING_KEY_BY_ROW_ID = 112; private static final int PUBLIC_KEY_RING_USER_ID = 121; private static final int PUBLIC_KEY_RING_USER_ID_BY_ROW_ID = 122; private static final int SECRET_KEY_RING = 201; private static final int SECRET_KEY_RING_BY_ROW_ID = 202; private static final int SECRET_KEY_RING_BY_MASTER_KEY_ID = 203; private static final int SECRET_KEY_RING_BY_KEY_ID = 204; private static final int SECRET_KEY_RING_BY_EMAILS = 205; private static final int SECRET_KEY_RING_BY_LIKE_EMAIL = 206; private static final int SECRET_KEY_RING_KEY = 211; private static final int SECRET_KEY_RING_KEY_BY_ROW_ID = 212; private static final int SECRET_KEY_RING_USER_ID = 221; private static final int SECRET_KEY_RING_USER_ID_BY_ROW_ID = 222; private static final int API_APPS = 301; private static final int API_APPS_BY_ROW_ID = 302; private static final int API_APPS_BY_PACKAGE_NAME = 303; // private static final int DATA_STREAM = 401; protected UriMatcher mUriMatcher; /** * Build and return a {@link UriMatcher} that catches all {@link Uri} variations supported by * this {@link ContentProvider}. */ protected UriMatcher buildUriMatcher() { final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); String authority = KeychainContract.CONTENT_AUTHORITY; /** * public key rings * *
         * key_rings/public
         * key_rings/public/#
         * key_rings/public/master_key_id/_
         * key_rings/public/key_id/_
         * key_rings/public/emails/_
         * key_rings/public/like_email/_
         * 
*/ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_PUBLIC, PUBLIC_KEY_RING); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_PUBLIC + "/#", PUBLIC_KEY_RING_BY_ROW_ID); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_MASTER_KEY_ID + "/*", PUBLIC_KEY_RING_BY_MASTER_KEY_ID); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_KEY_ID + "/*", PUBLIC_KEY_RING_BY_KEY_ID); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_EMAILS + "/*", PUBLIC_KEY_RING_BY_EMAILS); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_EMAILS, PUBLIC_KEY_RING_BY_EMAILS); // without emails specified matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_LIKE_EMAIL + "/*", PUBLIC_KEY_RING_BY_LIKE_EMAIL); /** * public keys * *
         * key_rings/public/#/keys
         * key_rings/public/#/keys/#
         * 
*/ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_KEYS, PUBLIC_KEY_RING_KEY); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_KEYS + "/#", PUBLIC_KEY_RING_KEY_BY_ROW_ID); /** * public user ids * *
         * key_rings/public/#/user_ids
         * key_rings/public/#/user_ids/#
         * 
*/ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_USER_IDS, PUBLIC_KEY_RING_USER_ID); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_PUBLIC + "/#/" + KeychainContract.PATH_USER_IDS + "/#", PUBLIC_KEY_RING_USER_ID_BY_ROW_ID); /** * secret key rings * *
         * key_rings/secret
         * key_rings/secret/#
         * key_rings/secret/master_key_id/_
         * key_rings/secret/key_id/_
         * key_rings/secret/emails/_
         * key_rings/secret/like_email/_
         * 
*/ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_SECRET, SECRET_KEY_RING); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_SECRET + "/#", SECRET_KEY_RING_BY_ROW_ID); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_MASTER_KEY_ID + "/*", SECRET_KEY_RING_BY_MASTER_KEY_ID); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_KEY_ID + "/*", SECRET_KEY_RING_BY_KEY_ID); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_EMAILS + "/*", SECRET_KEY_RING_BY_EMAILS); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_EMAILS, SECRET_KEY_RING_BY_EMAILS); // without emails specified matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_LIKE_EMAIL + "/*", SECRET_KEY_RING_BY_LIKE_EMAIL); /** * secret keys * *
         * key_rings/secret/#/keys
         * key_rings/secret/#/keys/#
         * 
*/ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_KEYS, SECRET_KEY_RING_KEY); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_KEYS + "/#", SECRET_KEY_RING_KEY_BY_ROW_ID); /** * secret user ids * *
         * key_rings/secret/#/user_ids
         * key_rings/secret/#/user_ids/#
         * 
*/ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_USER_IDS, SECRET_KEY_RING_USER_ID); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_SECRET + "/#/" + KeychainContract.PATH_USER_IDS + "/#", SECRET_KEY_RING_USER_ID_BY_ROW_ID); /** * API apps */ matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS); matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/#", API_APPS_BY_ROW_ID); matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/" + KeychainContract.PATH_BY_PACKAGE_NAME + "/*", API_APPS_BY_PACKAGE_NAME); /** * data stream * *
         * data / _
         * 
*/ // matcher.addURI(authority, KeychainContract.BASE_DATA + "/*", DATA_STREAM); return matcher; } private KeychainDatabase mApgDatabase; /** {@inheritDoc} */ @Override public boolean onCreate() { mUriMatcher = buildUriMatcher(); mApgDatabase = new KeychainDatabase(getContext()); return true; } /** {@inheritDoc} */ @Override public String getType(Uri uri) { final int match = mUriMatcher.match(uri); switch (match) { case PUBLIC_KEY_RING: case PUBLIC_KEY_RING_BY_EMAILS: case PUBLIC_KEY_RING_BY_LIKE_EMAIL: case SECRET_KEY_RING: case SECRET_KEY_RING_BY_EMAILS: case SECRET_KEY_RING_BY_LIKE_EMAIL: return KeyRings.CONTENT_TYPE; case PUBLIC_KEY_RING_BY_ROW_ID: case PUBLIC_KEY_RING_BY_MASTER_KEY_ID: case PUBLIC_KEY_RING_BY_KEY_ID: case SECRET_KEY_RING_BY_ROW_ID: case SECRET_KEY_RING_BY_MASTER_KEY_ID: case SECRET_KEY_RING_BY_KEY_ID: return KeyRings.CONTENT_ITEM_TYPE; case PUBLIC_KEY_RING_KEY: case SECRET_KEY_RING_KEY: return Keys.CONTENT_TYPE; case PUBLIC_KEY_RING_KEY_BY_ROW_ID: case SECRET_KEY_RING_KEY_BY_ROW_ID: return Keys.CONTENT_ITEM_TYPE; case PUBLIC_KEY_RING_USER_ID: case SECRET_KEY_RING_USER_ID: return UserIds.CONTENT_TYPE; case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID: case SECRET_KEY_RING_USER_ID_BY_ROW_ID: return UserIds.CONTENT_ITEM_TYPE; case API_APPS: return ApiApps.CONTENT_TYPE; case API_APPS_BY_ROW_ID: case API_APPS_BY_PACKAGE_NAME: return ApiApps.CONTENT_ITEM_TYPE; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } } /** * Returns type of the query (secret/public) * * @param uri * @return */ private int getKeyType(int match) { int type; switch (match) { case PUBLIC_KEY_RING: case PUBLIC_KEY_RING_BY_ROW_ID: case PUBLIC_KEY_RING_BY_MASTER_KEY_ID: case PUBLIC_KEY_RING_BY_KEY_ID: case PUBLIC_KEY_RING_BY_EMAILS: case PUBLIC_KEY_RING_BY_LIKE_EMAIL: case PUBLIC_KEY_RING_KEY: case PUBLIC_KEY_RING_KEY_BY_ROW_ID: case PUBLIC_KEY_RING_USER_ID: case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID: type = KeyTypes.PUBLIC; break; case SECRET_KEY_RING: case SECRET_KEY_RING_BY_ROW_ID: case SECRET_KEY_RING_BY_MASTER_KEY_ID: case SECRET_KEY_RING_BY_KEY_ID: case SECRET_KEY_RING_BY_EMAILS: case SECRET_KEY_RING_BY_LIKE_EMAIL: case SECRET_KEY_RING_KEY: case SECRET_KEY_RING_KEY_BY_ROW_ID: case SECRET_KEY_RING_USER_ID: case SECRET_KEY_RING_USER_ID_BY_ROW_ID: type = KeyTypes.SECRET; break; default: Log.e(Constants.TAG, "Unknown match " + match); type = -1; break; } return type; } /** * Set result of query to specific columns, don't show blob column for external content provider * * @return */ private HashMap getProjectionMapForKeyRings() { HashMap projectionMap = new HashMap(); projectionMap.put(BaseColumns._ID, Tables.KEY_RINGS + "." + BaseColumns._ID); projectionMap.put(KeyRingsColumns.MASTER_KEY_ID, Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID); projectionMap.put(KeyRingsColumns.KEY_RING_DATA, Tables.KEY_RINGS + "." + KeyRingsColumns.KEY_RING_DATA); projectionMap.put(UserIdsColumns.USER_ID, Tables.USER_IDS + "." + UserIdsColumns.USER_ID); return projectionMap; } /** * Set result of query to specific columns, don't show blob column for external content provider * * @return */ private HashMap getProjectionMapForKeys() { HashMap projectionMap = new HashMap(); projectionMap.put(BaseColumns._ID, BaseColumns._ID); projectionMap.put(KeysColumns.KEY_ID, KeysColumns.KEY_ID); projectionMap.put(KeysColumns.IS_MASTER_KEY, KeysColumns.IS_MASTER_KEY); projectionMap.put(KeysColumns.ALGORITHM, KeysColumns.ALGORITHM); projectionMap.put(KeysColumns.KEY_SIZE, KeysColumns.KEY_SIZE); projectionMap.put(KeysColumns.CAN_CERTIFY, KeysColumns.CAN_CERTIFY); projectionMap.put(KeysColumns.CAN_SIGN, KeysColumns.CAN_SIGN); projectionMap.put(KeysColumns.CAN_ENCRYPT, KeysColumns.CAN_ENCRYPT); projectionMap.put(KeysColumns.IS_REVOKED, KeysColumns.IS_REVOKED); projectionMap.put(KeysColumns.CREATION, KeysColumns.CREATION); projectionMap.put(KeysColumns.EXPIRY, KeysColumns.EXPIRY); projectionMap.put(KeysColumns.KEY_RING_ROW_ID, KeysColumns.KEY_RING_ROW_ID); projectionMap.put(KeysColumns.KEY_DATA, KeysColumns.KEY_DATA); projectionMap.put(KeysColumns.RANK, KeysColumns.RANK); return projectionMap; } /** * Builds default query for keyRings: KeyRings table is joined with UserIds * * @param qb * @param match * @param isMasterKey * @param sortOrder * @return */ private SQLiteQueryBuilder buildKeyRingQuery(SQLiteQueryBuilder qb, int match, String sortOrder) { // public or secret keyring qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = "); qb.appendWhereEscapeString(Integer.toString(getKeyType(match))); // join keyrings with userIds to every keyring qb.setTables(Tables.KEY_RINGS + " INNER JOIN " + Tables.USER_IDS + " ON " + "(" + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.USER_IDS + "." + UserIdsColumns.KEY_RING_ROW_ID + " AND " + Tables.USER_IDS + "." + UserIdsColumns.RANK + " = '0')"); qb.setProjectionMap(getProjectionMapForKeyRings()); return qb; } /** * Builds default query for keyRings: KeyRings table is joined with Keys and UserIds * * @param qb * @param match * @param isMasterKey * @param sortOrder * @return */ private SQLiteQueryBuilder buildKeyRingQueryWithKeys(SQLiteQueryBuilder qb, int match, String sortOrder) { // public or secret keyring qb.appendWhere(Tables.KEY_RINGS + "." + KeyRingsColumns.TYPE + " = "); qb.appendWhereEscapeString(Integer.toString(getKeyType(match))); // join keyrings with keys and userIds to every keyring qb.setTables(Tables.KEY_RINGS + " INNER JOIN " + Tables.KEYS + " ON " + "(" + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.KEYS + "." + KeysColumns.KEY_RING_ROW_ID + ") " + " INNER JOIN " + Tables.USER_IDS + " ON " + "(" + Tables.KEY_RINGS + "." + BaseColumns._ID + " = " + Tables.USER_IDS + "." + UserIdsColumns.KEY_RING_ROW_ID + " AND " + Tables.USER_IDS + "." + UserIdsColumns.RANK + " = '0')"); qb.setProjectionMap(getProjectionMapForKeyRings()); return qb; } /** {@inheritDoc} */ @SuppressWarnings("deprecation") @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Log.v(Constants.TAG, "query(uri=" + uri + ", proj=" + Arrays.toString(projection) + ")"); SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); SQLiteDatabase db = mApgDatabase.getReadableDatabase(); int match = mUriMatcher.match(uri); switch (match) { case PUBLIC_KEY_RING: case SECRET_KEY_RING: qb = buildKeyRingQuery(qb, match, sortOrder); if (TextUtils.isEmpty(sortOrder)) { sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC"; } break; case PUBLIC_KEY_RING_BY_ROW_ID: case SECRET_KEY_RING_BY_ROW_ID: qb = buildKeyRingQuery(qb, match, sortOrder); qb.appendWhere(" AND " + Tables.KEY_RINGS + "." + BaseColumns._ID + " = "); qb.appendWhereEscapeString(uri.getLastPathSegment()); if (TextUtils.isEmpty(sortOrder)) { sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC"; } break; case PUBLIC_KEY_RING_BY_MASTER_KEY_ID: case SECRET_KEY_RING_BY_MASTER_KEY_ID: qb = buildKeyRingQuery(qb, match, sortOrder); qb.appendWhere(" AND " + Tables.KEY_RINGS + "." + KeyRingsColumns.MASTER_KEY_ID + " = "); qb.appendWhereEscapeString(uri.getLastPathSegment()); if (TextUtils.isEmpty(sortOrder)) { sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC"; } break; case SECRET_KEY_RING_BY_KEY_ID: case PUBLIC_KEY_RING_BY_KEY_ID: qb = buildKeyRingQueryWithKeys(qb, match, sortOrder); qb.appendWhere(" AND " + Tables.KEYS + "." + KeysColumns.KEY_ID + " = "); qb.appendWhereEscapeString(uri.getLastPathSegment()); if (TextUtils.isEmpty(sortOrder)) { sortOrder = Tables.USER_IDS + "." + UserIdsColumns.USER_ID + " ASC"; } break; case SECRET_KEY_RING_BY_EMAILS: case PUBLIC_KEY_RING_BY_EMAILS: qb = buildKeyRingQuery(qb, match, sortOrder); String emails = uri.getLastPathSegment(); String chunks[] = emails.split(" *, *"); boolean gotCondition = false; String emailWhere = ""; for (int i = 0; i < chunks.length; ++i) { if (chunks[i].length() == 0) { continue; } if (i != 0) { emailWhere += " OR "; } emailWhere += "tmp." + UserIdsColumns.USER_ID + " LIKE "; // match '*', so it has to be at the *end* of the user id emailWhere += DatabaseUtils.sqlEscapeString("%<" + chunks[i] + ">"); gotCondition = true; } if (gotCondition) { qb.appendWhere(" AND EXISTS (SELECT tmp." + BaseColumns._ID + " FROM " + Tables.USER_IDS + " AS tmp WHERE tmp." + UserIdsColumns.KEY_RING_ROW_ID + " = " + Tables.KEY_RINGS + "." + BaseColumns._ID + " AND (" + emailWhere + "))"); } break; case SECRET_KEY_RING_BY_LIKE_EMAIL: case PUBLIC_KEY_RING_BY_LIKE_EMAIL: qb = buildKeyRingQuery(qb, match, sortOrder); String likeEmail = uri.getLastPathSegment(); String likeEmailWhere = "tmp." + UserIdsColumns.USER_ID + " LIKE " + DatabaseUtils.sqlEscapeString("%<%" + likeEmail + "%>"); qb.appendWhere(" AND EXISTS (SELECT tmp." + BaseColumns._ID + " FROM " + Tables.USER_IDS + " AS tmp WHERE tmp." + UserIdsColumns.KEY_RING_ROW_ID + " = " + Tables.KEY_RINGS + "." + BaseColumns._ID + " AND (" + likeEmailWhere + "))"); break; case PUBLIC_KEY_RING_KEY: case SECRET_KEY_RING_KEY: qb.setTables(Tables.KEYS); qb.appendWhere(KeysColumns.TYPE + " = "); qb.appendWhereEscapeString(Integer.toString(getKeyType(match))); qb.appendWhere(" AND " + KeysColumns.KEY_RING_ROW_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(2)); qb.setProjectionMap(getProjectionMapForKeys()); break; case PUBLIC_KEY_RING_KEY_BY_ROW_ID: case SECRET_KEY_RING_KEY_BY_ROW_ID: qb.setTables(Tables.KEYS); qb.appendWhere(KeysColumns.TYPE + " = "); qb.appendWhereEscapeString(Integer.toString(getKeyType(match))); qb.appendWhere(" AND " + KeysColumns.KEY_RING_ROW_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(2)); qb.appendWhere(" AND " + BaseColumns._ID + " = "); qb.appendWhereEscapeString(uri.getLastPathSegment()); qb.setProjectionMap(getProjectionMapForKeys()); break; case PUBLIC_KEY_RING_USER_ID: case SECRET_KEY_RING_USER_ID: qb.setTables(Tables.USER_IDS); qb.appendWhere(UserIdsColumns.KEY_RING_ROW_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(2)); break; case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID: case SECRET_KEY_RING_USER_ID_BY_ROW_ID: qb.setTables(Tables.USER_IDS); qb.appendWhere(UserIdsColumns.KEY_RING_ROW_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(2)); qb.appendWhere(" AND " + BaseColumns._ID + " = "); qb.appendWhereEscapeString(uri.getLastPathSegment()); break; case API_APPS: qb.setTables(Tables.API_APPS); break; case API_APPS_BY_ROW_ID: qb.setTables(Tables.API_APPS); qb.appendWhere(BaseColumns._ID + " = "); qb.appendWhereEscapeString(uri.getLastPathSegment()); break; case API_APPS_BY_PACKAGE_NAME: qb.setTables(Tables.API_APPS); qb.appendWhere(ApiApps.PACKAGE_NAME + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(2)); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } // If no sort order is specified use the default String orderBy; if (TextUtils.isEmpty(sortOrder)) { orderBy = null; } else { orderBy = sortOrder; } Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy); // Tell the cursor what uri to watch, so it knows when its source data changes c.setNotificationUri(getContext().getContentResolver(), uri); if (Constants.DEBUG) { Log.d(Constants.TAG, "Query: " + qb.buildQuery(projection, selection, selectionArgs, null, null, orderBy, null)); Log.d(Constants.TAG, "Cursor: " + DatabaseUtils.dumpCursorToString(c)); } return c; } /** {@inheritDoc} */ @Override public Uri insert(Uri uri, ContentValues values) { Log.d(Constants.TAG, "insert(uri=" + uri + ", values=" + values.toString() + ")"); final SQLiteDatabase db = mApgDatabase.getWritableDatabase(); Uri rowUri = null; long rowId = -1; try { final int match = mUriMatcher.match(uri); switch (match) { case PUBLIC_KEY_RING: values.put(KeyRings.TYPE, KeyTypes.PUBLIC); rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values); rowUri = KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)); sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); break; case PUBLIC_KEY_RING_KEY: values.put(Keys.TYPE, KeyTypes.PUBLIC); rowId = db.insertOrThrow(Tables.KEYS, null, values); rowUri = Keys.buildPublicKeysUri(Long.toString(rowId)); sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); break; case PUBLIC_KEY_RING_USER_ID: rowId = db.insertOrThrow(Tables.USER_IDS, null, values); rowUri = UserIds.buildPublicUserIdsUri(Long.toString(rowId)); sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); break; case SECRET_KEY_RING: values.put(KeyRings.TYPE, KeyTypes.SECRET); rowId = db.insertOrThrow(Tables.KEY_RINGS, null, values); rowUri = KeyRings.buildSecretKeyRingsUri(Long.toString(rowId)); sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); break; case SECRET_KEY_RING_KEY: values.put(Keys.TYPE, KeyTypes.SECRET); rowId = db.insertOrThrow(Tables.KEYS, null, values); rowUri = Keys.buildSecretKeysUri(Long.toString(rowId)); sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); break; case SECRET_KEY_RING_USER_ID: rowId = db.insertOrThrow(Tables.USER_IDS, null, values); rowUri = UserIds.buildSecretUserIdsUri(Long.toString(rowId)); break; case API_APPS: rowId = db.insertOrThrow(Tables.API_APPS, null, values); rowUri = ApiApps.buildIdUri(Long.toString(rowId)); break; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } // notify of changes in db getContext().getContentResolver().notifyChange(uri, null); } catch (SQLiteConstraintException e) { Log.e(Constants.TAG, "Constraint exception on insert! Entry already existing?"); } return rowUri; } /** {@inheritDoc} */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { Log.v(Constants.TAG, "delete(uri=" + uri + ")"); final SQLiteDatabase db = mApgDatabase.getWritableDatabase(); int count; final int match = mUriMatcher.match(uri); String defaultSelection = null; switch (match) { case PUBLIC_KEY_RING_BY_ROW_ID: case SECRET_KEY_RING_BY_ROW_ID: defaultSelection = BaseColumns._ID + "=" + uri.getLastPathSegment(); // corresponding keys and userIds are deleted by ON DELETE CASCADE count = db.delete(Tables.KEY_RINGS, buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection), selectionArgs); sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); break; case PUBLIC_KEY_RING_BY_MASTER_KEY_ID: case SECRET_KEY_RING_BY_MASTER_KEY_ID: defaultSelection = KeyRings.MASTER_KEY_ID + "=" + uri.getLastPathSegment(); // corresponding keys and userIds are deleted by ON DELETE CASCADE count = db.delete(Tables.KEY_RINGS, buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection), selectionArgs); sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); break; case PUBLIC_KEY_RING_KEY_BY_ROW_ID: case SECRET_KEY_RING_KEY_BY_ROW_ID: count = db.delete(Tables.KEYS, buildDefaultKeysSelection(uri, getKeyType(match), selection), selectionArgs); sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); break; case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID: case SECRET_KEY_RING_USER_ID_BY_ROW_ID: count = db.delete(Tables.KEYS, buildDefaultUserIdsSelection(uri, selection), selectionArgs); break; case API_APPS_BY_ROW_ID: count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, false, selection), selectionArgs); break; case API_APPS_BY_PACKAGE_NAME: count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, true, selection), selectionArgs); break; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } // notify of changes in db getContext().getContentResolver().notifyChange(uri, null); return count; } /** {@inheritDoc} */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Log.v(Constants.TAG, "update(uri=" + uri + ", values=" + values.toString() + ")"); final SQLiteDatabase db = mApgDatabase.getWritableDatabase(); String defaultSelection = null; int count = 0; try { final int match = mUriMatcher.match(uri); switch (match) { case PUBLIC_KEY_RING_BY_ROW_ID: case SECRET_KEY_RING_BY_ROW_ID: defaultSelection = BaseColumns._ID + "=" + uri.getLastPathSegment(); count = db.update( Tables.KEY_RINGS, values, buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection), selectionArgs); sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); break; case PUBLIC_KEY_RING_BY_MASTER_KEY_ID: case SECRET_KEY_RING_BY_MASTER_KEY_ID: defaultSelection = KeyRings.MASTER_KEY_ID + "=" + uri.getLastPathSegment(); count = db.update( Tables.KEY_RINGS, values, buildDefaultKeyRingsSelection(defaultSelection, getKeyType(match), selection), selectionArgs); sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); break; case PUBLIC_KEY_RING_KEY_BY_ROW_ID: case SECRET_KEY_RING_KEY_BY_ROW_ID: count = db .update(Tables.KEYS, values, buildDefaultKeysSelection(uri, getKeyType(match), selection), selectionArgs); sendBroadcastDatabaseChange(getKeyType(match), getType(uri)); break; case PUBLIC_KEY_RING_USER_ID_BY_ROW_ID: case SECRET_KEY_RING_USER_ID_BY_ROW_ID: count = db.update(Tables.USER_IDS, values, buildDefaultUserIdsSelection(uri, selection), selectionArgs); break; case API_APPS_BY_ROW_ID: count = db.update(Tables.API_APPS, values, buildDefaultApiAppsSelection(uri, false, selection), selectionArgs); break; case API_APPS_BY_PACKAGE_NAME: count = db.update(Tables.API_APPS, values, buildDefaultApiAppsSelection(uri, true, selection), selectionArgs); break; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } // notify of changes in db getContext().getContentResolver().notifyChange(uri, null); } catch (SQLiteConstraintException e) { Log.e(Constants.TAG, "Constraint exception on update! Entry already existing?"); } return count; } /** * Build default selection statement for KeyRings. If no extra selection is specified only build * where clause with rowId * * @param uri * @param selection * @return */ private String buildDefaultKeyRingsSelection(String defaultSelection, Integer keyType, String selection) { String andType = ""; if (keyType != null) { andType = " AND " + KeyRingsColumns.TYPE + "=" + keyType; } String andSelection = ""; if (!TextUtils.isEmpty(selection)) { andSelection = " AND (" + selection + ")"; } return defaultSelection + andType + andSelection; } /** * Build default selection statement for Keys. If no extra selection is specified only build * where clause with rowId * * @param uri * @param selection * @return */ private String buildDefaultKeysSelection(Uri uri, Integer keyType, String selection) { String rowId = uri.getLastPathSegment(); String foreignKeyRingRowId = uri.getPathSegments().get(2); String andForeignKeyRing = " AND " + KeysColumns.KEY_RING_ROW_ID + " = " + foreignKeyRingRowId; String andType = ""; if (keyType != null) { andType = " AND " + KeysColumns.TYPE + "=" + keyType; } String andSelection = ""; if (!TextUtils.isEmpty(selection)) { andSelection = " AND (" + selection + ")"; } return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andType + andSelection; } /** * Build default selection statement for UserIds. If no extra selection is specified only build * where clause with rowId * * @param uri * @param selection * @return */ private String buildDefaultUserIdsSelection(Uri uri, String selection) { String rowId = uri.getLastPathSegment(); String foreignKeyRingRowId = uri.getPathSegments().get(2); String andForeignKeyRing = " AND " + KeysColumns.KEY_RING_ROW_ID + " = " + foreignKeyRingRowId; String andSelection = ""; if (!TextUtils.isEmpty(selection)) { andSelection = " AND (" + selection + ")"; } return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andSelection; } /** * Build default selection statement for API apps. If no extra selection is specified only build * where clause with rowId * * @param uri * @param selection * @return */ private String buildDefaultApiAppsSelection(Uri uri, boolean packageSelection, String selection) { String lastPathSegment = uri.getLastPathSegment(); String andSelection = ""; if (!TextUtils.isEmpty(selection)) { andSelection = " AND (" + selection + ")"; } if (packageSelection) { return ApiApps.PACKAGE_NAME + "=" + lastPathSegment + andSelection; } else { return BaseColumns._ID + "=" + lastPathSegment + andSelection; } } // @Override // public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { // int match = mUriMatcher.match(uri); // if (match != DATA_STREAM) { // throw new FileNotFoundException(); // } // String fileName = uri.getLastPathSegment(); // File file = new File(getContext().getFilesDir().getAbsolutePath(), fileName); // return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); // } /** * This broadcast is send system wide to inform other application that a keyring was inserted, * updated, or deleted */ private void sendBroadcastDatabaseChange(int keyType, String contentItemType) { // TODO: Disabled, old API // Intent intent = new Intent(); // intent.setAction(ACTION_BROADCAST_DATABASE_CHANGE); // intent.putExtra(EXTRA_BROADCAST_KEY_TYPE, keyType); // intent.putExtra(EXTRA_BROADCAST_CONTENT_ITEM_TYPE, contentItemType); // // getContext().sendBroadcast(intent, Constants.PERMISSION_ACCESS_API); } }