/* * Copyright (C) 2012-2014 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 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.text.TextUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.util.Log; import java.util.Arrays; import java.util.HashMap; public class KeychainProvider extends ContentProvider { private static final int KEY_RINGS_UNIFIED = 101; private static final int KEY_RINGS_PUBLIC = 102; private static final int KEY_RINGS_SECRET = 103; private static final int KEY_RING_UNIFIED = 200; private static final int KEY_RING_KEYS = 201; private static final int KEY_RING_USER_IDS = 202; private static final int KEY_RING_PUBLIC = 203; private static final int KEY_RING_SECRET = 204; private static final int KEY_RING_CERTS = 205; private static final int KEY_RING_CERTS_SPECIFIC = 206; private static final int API_APPS = 301; private static final int API_APPS_BY_PACKAGE_NAME = 303; private static final int API_ACCOUNTS = 304; private static final int API_ACCOUNTS_BY_ACCOUNT_NAME = 306; private static final int KEY_RINGS_FIND_BY_EMAIL = 400; private static final int KEY_RINGS_FIND_BY_SUBKEY = 401; // private static final int DATA_STREAM = 501; 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; /** * list key_rings * *
         * key_rings/unified
         * key_rings/public
         * 
*/ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_UNIFIED, KEY_RINGS_UNIFIED); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_PUBLIC, KEY_RINGS_PUBLIC); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_SECRET, KEY_RINGS_SECRET); /** * find by criteria other than master key id * * key_rings/find/email/_ * key_rings/find/subkey/_ * */ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_FIND + "/" + KeychainContract.PATH_BY_EMAIL + "/*", KEY_RINGS_FIND_BY_EMAIL); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/" + KeychainContract.PATH_FIND + "/" + KeychainContract.PATH_BY_SUBKEY + "/*", KEY_RINGS_FIND_BY_SUBKEY); /** * list key_ring specifics * *
         * key_rings/_/unified
         * key_rings/_/keys
         * key_rings/_/user_ids
         * key_rings/_/public
         * key_rings/_/secret
         * key_rings/_/certs
         * key_rings/_/certs/_/_
         * 
*/ matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + KeychainContract.PATH_UNIFIED, KEY_RING_UNIFIED); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + KeychainContract.PATH_KEYS, KEY_RING_KEYS); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + KeychainContract.PATH_USER_IDS, KEY_RING_USER_IDS); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + KeychainContract.PATH_PUBLIC, KEY_RING_PUBLIC); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + KeychainContract.PATH_SECRET, KEY_RING_SECRET); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + KeychainContract.PATH_CERTS, KEY_RING_CERTS); matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/*/" + KeychainContract.PATH_CERTS + "/*/*", KEY_RING_CERTS_SPECIFIC); /** * API apps * *
         * api_apps
         * api_apps/_ (package name)
         *
         * api_apps/_/accounts
         * api_apps/_/accounts/_ (account name)
         * 
*/ matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS); matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME); matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/" + KeychainContract.PATH_ACCOUNTS, API_ACCOUNTS); matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/*/" + KeychainContract.PATH_ACCOUNTS + "/*", API_ACCOUNTS_BY_ACCOUNT_NAME); /** * data stream * *
         * data / _
         * 
*/ // matcher.addURI(authority, KeychainContract.BASE_DATA + "/*", DATA_STREAM); return matcher; } private KeychainDatabase mKeychainDatabase; /** * {@inheritDoc} */ @Override public boolean onCreate() { mUriMatcher = buildUriMatcher(); return true; } public KeychainDatabase getDb() { if(mKeychainDatabase == null) mKeychainDatabase = new KeychainDatabase(getContext()); return mKeychainDatabase; } /** * {@inheritDoc} */ @Override public String getType(Uri uri) { final int match = mUriMatcher.match(uri); switch (match) { case KEY_RING_PUBLIC: return KeyRings.CONTENT_ITEM_TYPE; case KEY_RING_KEYS: return Keys.CONTENT_TYPE; case KEY_RING_USER_IDS: return UserIds.CONTENT_TYPE; case KEY_RING_SECRET: return KeyRings.CONTENT_ITEM_TYPE; case API_APPS: return ApiApps.CONTENT_TYPE; case API_APPS_BY_PACKAGE_NAME: return ApiApps.CONTENT_ITEM_TYPE; case API_ACCOUNTS: return ApiAccounts.CONTENT_TYPE; case API_ACCOUNTS_BY_ACCOUNT_NAME: return ApiAccounts.CONTENT_ITEM_TYPE; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } } /** * {@inheritDoc} */ @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(); int match = mUriMatcher.match(uri); // all query() parameters, for good measure String groupBy = null, having = null; switch (match) { case KEY_RING_UNIFIED: case KEY_RINGS_UNIFIED: case KEY_RINGS_FIND_BY_EMAIL: case KEY_RINGS_FIND_BY_SUBKEY: { HashMap projectionMap = new HashMap(); projectionMap.put(KeyRings._ID, Tables.KEYS + ".oid AS _id"); projectionMap.put(KeyRings.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID); projectionMap.put(KeyRings.KEY_ID, Keys.KEY_ID); projectionMap.put(KeyRings.KEY_SIZE, Keys.KEY_SIZE); projectionMap.put(KeyRings.IS_REVOKED, Tables.KEYS + "." + Keys.IS_REVOKED); projectionMap.put(KeyRings.CAN_CERTIFY, Keys.CAN_CERTIFY); projectionMap.put(KeyRings.CAN_ENCRYPT, Keys.CAN_ENCRYPT); projectionMap.put(KeyRings.CAN_SIGN, Keys.CAN_SIGN); projectionMap.put(KeyRings.CREATION, Tables.KEYS + "." + Keys.CREATION); projectionMap.put(KeyRings.EXPIRY, Keys.EXPIRY); projectionMap.put(KeyRings.ALGORITHM, Keys.ALGORITHM); projectionMap.put(KeyRings.FINGERPRINT, Keys.FINGERPRINT); projectionMap.put(KeyRings.USER_ID, UserIds.USER_ID); projectionMap.put(KeyRings.VERIFIED, KeyRings.VERIFIED); projectionMap.put(KeyRings.HAS_SECRET, "(" + Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + " IS NOT NULL) AS " + KeyRings.HAS_SECRET); qb.setProjectionMap(projectionMap); qb.setTables( Tables.KEYS + " INNER JOIN " + Tables.USER_IDS + " ON (" + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = " + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " AND " + Tables.USER_IDS + "." + UserIds.RANK + " = 0" + ") LEFT JOIN " + Tables.KEY_RINGS_SECRET + " ON (" + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = " + Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + ") LEFT JOIN " + Tables.CERTS + " ON (" + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = " + Tables.CERTS + "." + KeyRings.MASTER_KEY_ID + " AND " + Tables.CERTS + "." + Certs.VERIFIED + " = " + Certs.VERIFIED_SECRET + ")" ); qb.appendWhere(Tables.KEYS + "." + Keys.RANK + " = 0"); // in case there are multiple verifying certificates groupBy = Tables.KEYS + "." + Keys.MASTER_KEY_ID; switch(match) { case KEY_RING_UNIFIED: { qb.appendWhere(" AND " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(1)); break; } case KEY_RINGS_FIND_BY_SUBKEY: { try { String subkey = Long.valueOf(uri.getLastPathSegment()).toString(); qb.appendWhere(" AND EXISTS (" + " SELECT 1 FROM " + Tables.KEYS + " AS tmp" + " WHERE tmp." + UserIds.MASTER_KEY_ID + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " AND tmp." + Keys.KEY_ID + " = " + subkey + "" + ")"); } catch(NumberFormatException e) { Log.e(Constants.TAG, "Malformed find by subkey query!", e); qb.appendWhere(" AND 0"); } break; } case KEY_RINGS_FIND_BY_EMAIL: { String chunks[] = uri.getLastPathSegment().split(" *, *"); boolean gotCondition = false; String emailWhere = ""; // JAVA ♥ for (int i = 0; i < chunks.length; ++i) { if (chunks[i].length() == 0) { continue; } if (i != 0) { emailWhere += " OR "; } emailWhere += "tmp." + UserIds.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 1 FROM " + Tables.USER_IDS + " AS tmp" + " WHERE tmp." + UserIds.MASTER_KEY_ID + " = " + Tables.KEYS + "." + Keys.MASTER_KEY_ID + " AND (" + emailWhere + ")" + ")"); } else { // TODO better way to do this? Log.e(Constants.TAG, "Malformed find by email query!"); qb.appendWhere(" AND 0"); } break; } } if (TextUtils.isEmpty(sortOrder)) { sortOrder = Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + " IS NULL ASC, " + Tables.USER_IDS + "." + UserIds.USER_ID + " ASC"; } // uri to watch is all /key_rings/ uri = KeyRings.CONTENT_URI; break; } case KEY_RING_KEYS: { HashMap projectionMap = new HashMap(); projectionMap.put(Keys._ID, Tables.KEYS + ".oid AS _id"); projectionMap.put(Keys.MASTER_KEY_ID, Tables.KEYS + "." + Keys.MASTER_KEY_ID); projectionMap.put(Keys.RANK, Tables.KEYS + "." + Keys.RANK); projectionMap.put(Keys.KEY_ID, Keys.KEY_ID); projectionMap.put(Keys.KEY_SIZE, Keys.KEY_SIZE); projectionMap.put(Keys.IS_REVOKED, Keys.IS_REVOKED); projectionMap.put(Keys.CAN_CERTIFY, Keys.CAN_CERTIFY); projectionMap.put(Keys.CAN_ENCRYPT, Keys.CAN_ENCRYPT); projectionMap.put(Keys.CAN_SIGN, Keys.CAN_SIGN); projectionMap.put(Keys.CREATION, Keys.CREATION); projectionMap.put(Keys.EXPIRY, Keys.EXPIRY); projectionMap.put(Keys.ALGORITHM, Keys.ALGORITHM); projectionMap.put(Keys.FINGERPRINT, Keys.FINGERPRINT); qb.setProjectionMap(projectionMap); qb.setTables(Tables.KEYS); qb.appendWhere(Keys.MASTER_KEY_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(1)); break; } case KEY_RING_USER_IDS: { HashMap projectionMap = new HashMap(); projectionMap.put(UserIds._ID, Tables.USER_IDS + ".oid AS _id"); projectionMap.put(UserIds.MASTER_KEY_ID, Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID); projectionMap.put(UserIds.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID); projectionMap.put(UserIds.RANK, Tables.USER_IDS + "." + UserIds.RANK); projectionMap.put(UserIds.IS_PRIMARY, Tables.USER_IDS + "." + UserIds.IS_PRIMARY); projectionMap.put(UserIds.IS_REVOKED, Tables.USER_IDS + "." + UserIds.IS_REVOKED); // we take the minimum (>0) here, where "1" is "verified by known secret key" projectionMap.put(UserIds.VERIFIED, "MIN(" + Certs.VERIFIED + ") AS " + UserIds.VERIFIED); qb.setProjectionMap(projectionMap); qb.setTables(Tables.USER_IDS + " LEFT JOIN " + Tables.CERTS + " ON (" + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " = " + Tables.CERTS + "." + Certs.MASTER_KEY_ID + " AND " + Tables.USER_IDS + "." + UserIds.RANK + " = " + Tables.CERTS + "." + Certs.RANK + " AND " + Tables.CERTS + "." + Certs.VERIFIED + " > 0" + ")"); groupBy = Tables.USER_IDS + "." + UserIds.RANK; qb.appendWhere(Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(1)); if (TextUtils.isEmpty(sortOrder)) { sortOrder = Tables.USER_IDS + "." + UserIds.RANK + " ASC"; } break; } case KEY_RINGS_PUBLIC: case KEY_RING_PUBLIC: { HashMap projectionMap = new HashMap(); projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_PUBLIC + ".oid AS _id"); projectionMap.put(KeyRingData.MASTER_KEY_ID, KeyRingData.MASTER_KEY_ID); projectionMap.put(KeyRingData.KEY_RING_DATA, KeyRingData.KEY_RING_DATA); qb.setProjectionMap(projectionMap); qb.setTables(Tables.KEY_RINGS_PUBLIC); if(match == KEY_RING_PUBLIC) { qb.appendWhere(KeyRings.MASTER_KEY_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(1)); } break; } case KEY_RINGS_SECRET: case KEY_RING_SECRET: { HashMap projectionMap = new HashMap(); projectionMap.put(KeyRingData._ID, Tables.KEY_RINGS_SECRET + ".oid AS _id"); projectionMap.put(KeyRingData.MASTER_KEY_ID, KeyRingData.MASTER_KEY_ID); projectionMap.put(KeyRingData.KEY_RING_DATA, KeyRingData.KEY_RING_DATA); qb.setProjectionMap(projectionMap); qb.setTables(Tables.KEY_RINGS_SECRET); if(match == KEY_RING_SECRET) { qb.appendWhere(KeyRings.MASTER_KEY_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(1)); } break; } case KEY_RING_CERTS: case KEY_RING_CERTS_SPECIFIC: { HashMap projectionMap = new HashMap(); projectionMap.put(Certs._ID, Tables.CERTS + ".oid AS " + Certs._ID); projectionMap.put(Certs.MASTER_KEY_ID, Tables.CERTS + "." + Certs.MASTER_KEY_ID); projectionMap.put(Certs.RANK, Tables.CERTS + "." + Certs.RANK); projectionMap.put(Certs.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED); projectionMap.put(Certs.TYPE, Tables.CERTS + "." + Certs.TYPE); projectionMap.put(Certs.CREATION, Tables.CERTS + "." + Certs.CREATION); projectionMap.put(Certs.KEY_ID_CERTIFIER, Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER); projectionMap.put(Certs.DATA, Tables.CERTS + "." + Certs.DATA); projectionMap.put(Certs.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID); projectionMap.put(Certs.SIGNER_UID, "signer." + UserIds.USER_ID + " AS " + Certs.SIGNER_UID); qb.setProjectionMap(projectionMap); qb.setTables(Tables.CERTS + " JOIN " + Tables.USER_IDS + " ON (" + Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = " + Tables.USER_IDS + "." + UserIds.MASTER_KEY_ID + " AND " + Tables.CERTS + "." + Certs.RANK + " = " + Tables.USER_IDS + "." + UserIds.RANK + ") LEFT JOIN " + Tables.USER_IDS + " AS signer ON (" + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER + " = " + "signer." + UserIds.MASTER_KEY_ID + " AND " + "signer." + Keys.RANK + " = 0" + ")"); groupBy = Tables.CERTS + "." + Certs.RANK + ", " + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER; qb.appendWhere(Tables.CERTS + "." + Certs.MASTER_KEY_ID + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(1)); if(match == KEY_RING_CERTS_SPECIFIC) { qb.appendWhere(" AND " + Tables.CERTS + "." + Certs.RANK + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(3)); qb.appendWhere(" AND " + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER+ " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(4)); } break; } case API_APPS: qb.setTables(Tables.API_APPS); break; case API_APPS_BY_PACKAGE_NAME: qb.setTables(Tables.API_APPS); qb.appendWhere(ApiApps.PACKAGE_NAME + " = "); qb.appendWhereEscapeString(uri.getLastPathSegment()); break; case API_ACCOUNTS: qb.setTables(Tables.API_ACCOUNTS); qb.appendWhere(Tables.API_ACCOUNTS + "." + ApiAccounts.PACKAGE_NAME + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(1)); break; case API_ACCOUNTS_BY_ACCOUNT_NAME: qb.setTables(Tables.API_ACCOUNTS); qb.appendWhere(Tables.API_ACCOUNTS + "." + ApiAccounts.PACKAGE_NAME + " = "); qb.appendWhereEscapeString(uri.getPathSegments().get(1)); qb.appendWhere(" AND " + Tables.API_ACCOUNTS + "." + ApiAccounts.ACCOUNT_NAME + " = "); qb.appendWhereEscapeString(uri.getLastPathSegment()); break; default: throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); } // If no sort order is specified use the default String orderBy; if (TextUtils.isEmpty(sortOrder)) { orderBy = null; } else { orderBy = sortOrder; } SQLiteDatabase db = getDb().getReadableDatabase(); Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, having, 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 = getDb().getWritableDatabase(); Uri rowUri = null; Long keyId = null; try { final int match = mUriMatcher.match(uri); switch (match) { case KEY_RING_PUBLIC: db.insertOrThrow(Tables.KEY_RINGS_PUBLIC, null, values); keyId = values.getAsLong(KeyRings.MASTER_KEY_ID); break; case KEY_RING_SECRET: db.insertOrThrow(Tables.KEY_RINGS_SECRET, null, values); keyId = values.getAsLong(KeyRings.MASTER_KEY_ID); break; case KEY_RING_KEYS: Log.d(Constants.TAG, "keys"); db.insertOrThrow(Tables.KEYS, null, values); keyId = values.getAsLong(Keys.MASTER_KEY_ID); break; case KEY_RING_USER_IDS: db.insertOrThrow(Tables.USER_IDS, null, values); keyId = values.getAsLong(UserIds.MASTER_KEY_ID); break; case KEY_RING_CERTS: // we replace here, keeping only the latest signature // TODO this would be better handled in saveKeyRing directly! db.replaceOrThrow(Tables.CERTS, null, values); keyId = values.getAsLong(Certs.MASTER_KEY_ID); break; case API_APPS: db.insertOrThrow(Tables.API_APPS, null, values); break; case API_ACCOUNTS: // set foreign key automatically based on given uri // e.g., api_apps/com.example.app/accounts/ String packageName = uri.getPathSegments().get(1); values.put(ApiAccounts.PACKAGE_NAME, packageName); Log.d(Constants.TAG, "provider packageName: " + packageName); db.insertOrThrow(Tables.API_ACCOUNTS, null, values); // TODO: this is wrong: // rowUri = ApiAccounts.buildIdUri(Long.toString(rowId)); break; default: throw new UnsupportedOperationException("Unknown uri: " + uri); } if(keyId != null) { uri = KeyRings.buildGenericKeyRingUri(keyId.toString()); rowUri = 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?", e); } return rowUri; } /** * {@inheritDoc} */ @Override public int delete(Uri uri, String additionalSelection, String[] selectionArgs) { Log.v(Constants.TAG, "delete(uri=" + uri + ")"); final SQLiteDatabase db = getDb().getWritableDatabase(); int count; final int match = mUriMatcher.match(uri); switch (match) { case KEY_RING_PUBLIC: { @SuppressWarnings("ConstantConditions") // ensured by uriMatcher above String selection = KeyRings.MASTER_KEY_ID + " = " + uri.getPathSegments().get(1); if (!TextUtils.isEmpty(additionalSelection)) { selection += " AND (" + additionalSelection + ")"; } // corresponding keys and userIds are deleted by ON DELETE CASCADE count = db.delete(Tables.KEY_RINGS_PUBLIC, selection, selectionArgs); uri = KeyRings.buildGenericKeyRingUri(uri.getPathSegments().get(1)); break; } case KEY_RING_SECRET: { @SuppressWarnings("ConstantConditions") // ensured by uriMatcher above String selection = KeyRings.MASTER_KEY_ID + " = " + uri.getPathSegments().get(1); if (!TextUtils.isEmpty(additionalSelection)) { selection += " AND (" + additionalSelection + ")"; } count = db.delete(Tables.KEY_RINGS_SECRET, selection, selectionArgs); uri = KeyRings.buildGenericKeyRingUri(uri.getPathSegments().get(1)); break; } case API_APPS_BY_PACKAGE_NAME: count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, additionalSelection), selectionArgs); break; case API_ACCOUNTS_BY_ACCOUNT_NAME: count = db.delete(Tables.API_ACCOUNTS, buildDefaultApiAccountsSelection(uri, additionalSelection), 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 = mKeychainDatabase.getWritableDatabase(); String defaultSelection = null; int count = 0; try { final int match = mUriMatcher.match(uri); switch (match) { case API_APPS_BY_PACKAGE_NAME: count = db.update(Tables.API_APPS, values, buildDefaultApiAppsSelection(uri, selection), selectionArgs); break; case API_ACCOUNTS_BY_ACCOUNT_NAME: count = db.update(Tables.API_ACCOUNTS, values, buildDefaultApiAccountsSelection(uri, 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 API apps. If no extra selection is specified only build * where clause with rowId * * @param uri * @param selection * @return */ private String buildDefaultApiAppsSelection(Uri uri, String selection) { String packageName = DatabaseUtils.sqlEscapeString(uri.getLastPathSegment()); String andSelection = ""; if (!TextUtils.isEmpty(selection)) { andSelection = " AND (" + selection + ")"; } return ApiApps.PACKAGE_NAME + "=" + packageName + andSelection; } private String buildDefaultApiAccountsSelection(Uri uri, String selection) { String packageName = DatabaseUtils.sqlEscapeString(uri.getPathSegments().get(1)); String accountName = DatabaseUtils.sqlEscapeString(uri.getLastPathSegment()); String andSelection = ""; if (!TextUtils.isEmpty(selection)) { andSelection = " AND (" + selection + ")"; } return ApiAccounts.PACKAGE_NAME + "=" + packageName + " AND " + ApiAccounts.ACCOUNT_NAME + "=" + accountName + andSelection; } }