diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index e9de55ec9..dca61bd27 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -1,6 +1,7 @@ apply plugin: 'com.android.application' apply plugin: 'witness' apply plugin: 'jacoco' +apply plugin: 'com.squareup.sqldelight' // apply plugin: 'com.github.kt3k.coveralls' dependencies { @@ -98,6 +99,8 @@ dependencies { compile "android.arch.lifecycle:extensions:1.0.0" annotationProcessor "android.arch.lifecycle:compiler:1.0.0" + + compile "android.arch.persistence:db-framework:1.0.0" } // Output of ./gradlew -q calculateChecksums diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/ApiAllowedKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/ApiAllowedKey.java new file mode 100644 index 000000000..d635d2931 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/ApiAllowedKey.java @@ -0,0 +1,11 @@ +package org.sufficientlysecure.keychain.model; + + +import com.google.auto.value.AutoValue; +import org.sufficientlysecure.keychain.ApiAllowedKeysModel; + + +@AutoValue +public abstract class ApiAllowedKey implements ApiAllowedKeysModel { + public static final Factory FACTORY = new Factory(AutoValue_ApiAllowedKey::new); +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/ApiApp.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/ApiApp.java new file mode 100644 index 000000000..2e84094cb --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/ApiApp.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 Schürmann & Breitmoser GbR + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.model; + + +import com.google.auto.value.AutoValue; +import org.sufficientlysecure.keychain.ApiAppsModel; + + +@AutoValue +public abstract class ApiApp implements ApiAppsModel { + public static final ApiAppsModel.Factory FACTORY = + new ApiAppsModel.Factory(AutoValue_ApiApp::new); + + public static ApiApp create(String packageName, byte[] packageSignature) { + return new AutoValue_ApiApp(null, packageName, packageSignature); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java index ed33c1863..16082fb56 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ApiDataAccessObject.java @@ -20,179 +20,97 @@ package org.sufficientlysecure.keychain.provider; import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; -import android.content.ContentResolver; -import android.content.ContentValues; +import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.Context; -import android.content.OperationApplicationException; import android.database.Cursor; -import android.net.Uri; -import android.os.RemoteException; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; -import org.sufficientlysecure.keychain.remote.AppSettings; +import com.squareup.sqldelight.SqlDelightQuery; +import org.sufficientlysecure.keychain.ApiAllowedKeysModel.InsertAllowedKey; +import org.sufficientlysecure.keychain.ApiAppsModel; +import org.sufficientlysecure.keychain.ApiAppsModel.DeleteByPackageName; +import org.sufficientlysecure.keychain.ApiAppsModel.InsertApiApp; +import org.sufficientlysecure.keychain.model.ApiAllowedKey; +import org.sufficientlysecure.keychain.model.ApiApp; public class ApiDataAccessObject { - private final SimpleContentResolverInterface mQueryInterface; + private final SupportSQLiteDatabase db; public ApiDataAccessObject(Context context) { - final ContentResolver contentResolver = context.getContentResolver(); - mQueryInterface = new SimpleContentResolverInterface() { - @Override - public Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - return contentResolver.query(contentUri, projection, selection, selectionArgs, sortOrder); - } - - @Override - public Uri insert(Uri contentUri, ContentValues values) { - return contentResolver.insert(contentUri, values); - } - - @Override - public int update(Uri contentUri, ContentValues values, String where, String[] selectionArgs) { - return contentResolver.update(contentUri, values, where, selectionArgs); - } - - @Override - public int delete(Uri contentUri, String where, String[] selectionArgs) { - return contentResolver.delete(contentUri, where, selectionArgs); - } - }; + KeychainDatabase keychainDatabase = new KeychainDatabase(context); + db = keychainDatabase.getWritableDatabase(); } - public ApiDataAccessObject(SimpleContentResolverInterface queryInterface) { - mQueryInterface = queryInterface; - } - - public ArrayList getRegisteredApiApps() { - Cursor cursor = mQueryInterface.query(ApiApps.CONTENT_URI, null, null, null, null); - - ArrayList packageNames = new ArrayList<>(); - try { - if (cursor != null) { - int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME); - if (cursor.moveToFirst()) { - do { - packageNames.add(cursor.getString(packageNameCol)); - } while (cursor.moveToNext()); - } - } - } finally { - if (cursor != null) { - cursor.close(); + public ApiApp getApiApp(String packageName) { + try (Cursor cursor = db.query(ApiApp.FACTORY.selectByPackageName(packageName))) { + if (cursor.moveToFirst()) { + return ApiApp.FACTORY.selectByPackageNameMapper().map(cursor); } + return null; } - - return packageNames; - } - - private ContentValues contentValueForApiApps(AppSettings appSettings) { - ContentValues values = new ContentValues(); - values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName()); - values.put(ApiApps.PACKAGE_CERTIFICATE, appSettings.getPackageCertificate()); - return values; - } - - public void insertApiApp(AppSettings appSettings) { - mQueryInterface.insert(ApiApps.CONTENT_URI, contentValueForApiApps(appSettings)); - } - - public void deleteApiApp(String packageName) { - mQueryInterface.delete(ApiApps.buildByPackageNameUri(packageName), null, null); - } - - /** - * Must be an uri pointing to an account - */ - public AppSettings getApiAppSettings(Uri uri) { - AppSettings settings = null; - - Cursor cursor = mQueryInterface.query(uri, null, null, null, null); - try { - if (cursor != null && cursor.moveToFirst()) { - settings = new AppSettings(); - settings.setPackageName(cursor.getString( - cursor.getColumnIndex(ApiApps.PACKAGE_NAME))); - settings.setPackageCertificate(cursor.getBlob( - cursor.getColumnIndex(ApiApps.PACKAGE_CERTIFICATE))); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return settings; - } - - public HashSet getAllowedKeyIdsForApp(Uri uri) { - HashSet keyIds = new HashSet<>(); - - Cursor cursor = mQueryInterface.query(uri, null, null, null, null); - try { - if (cursor != null) { - int keyIdColumn = cursor.getColumnIndex(ApiAllowedKeys.KEY_ID); - while (cursor.moveToNext()) { - keyIds.add(cursor.getLong(keyIdColumn)); - } - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - - return keyIds; - } - - public void saveAllowedKeyIdsForApp(Uri uri, Set allowedKeyIds) - throws RemoteException, OperationApplicationException { - // wipe whole table of allowed keys for this account - mQueryInterface.delete(uri, null, null); - - // re-insert allowed key ids - for (Long keyId : allowedKeyIds) { - ContentValues values = new ContentValues(); - values.put(ApiAllowedKeys.KEY_ID, keyId); - mQueryInterface.insert(uri, values); - } - } - - public void addAllowedKeyIdForApp(Uri uri, long allowedKeyId) { - ContentValues values = new ContentValues(); - values.put(ApiAllowedKeys.KEY_ID, allowedKeyId); - mQueryInterface.insert(uri, values); - } - - public void addAllowedKeyIdForApp(String packageName, long allowedKeyId) { - Uri uri = ApiAllowedKeys.buildBaseUri(packageName); - addAllowedKeyIdForApp(uri, allowedKeyId); } public byte[] getApiAppCertificate(String packageName) { - Uri queryUri = ApiApps.buildByPackageNameUri(packageName); + Cursor cursor = db.query(ApiApp.FACTORY.getCertificate(packageName)); + return ApiApp.FACTORY.getCertificateMapper().map(cursor); + } - String[] projection = new String[]{ApiApps.PACKAGE_CERTIFICATE}; + public void insertApiApp(ApiApp apiApp) { + InsertApiApp statement = new ApiAppsModel.InsertApiApp(db); + statement.bind(apiApp.package_name(), apiApp.package_signature()); + statement.execute(); + } - Cursor cursor = mQueryInterface.query(queryUri, projection, null, null, null); - try { - byte[] signature = null; - if (cursor != null && cursor.moveToFirst()) { - int signatureCol = 0; + public void deleteApiApp(String packageName) { + DeleteByPackageName deleteByPackageName = new DeleteByPackageName(db); + deleteByPackageName.bind(packageName); + deleteByPackageName.executeUpdateDelete(); + } - signature = cursor.getBlob(signatureCol); - } - return signature; - } finally { - if (cursor != null) { - cursor.close(); + public HashSet getAllowedKeyIdsForApp(String packageName) { + SqlDelightQuery allowedKeys = ApiAllowedKey.FACTORY.getAllowedKeys(packageName); + HashSet keyIds = new HashSet<>(); + try (Cursor cursor = db.query(allowedKeys)) { + while (cursor.moveToNext()) { + long allowedKeyId = ApiAllowedKey.FACTORY.getAllowedKeysMapper().map(cursor); + keyIds.add(allowedKeyId); } } + return keyIds; + } + + public void saveAllowedKeyIdsForApp(String packageName, Set allowedKeyIds) { + ApiAllowedKey.DeleteByPackageName deleteByPackageName = new ApiAllowedKey.DeleteByPackageName(db); + deleteByPackageName.bind(packageName); + deleteByPackageName.executeUpdateDelete(); + + InsertAllowedKey statement = new InsertAllowedKey(db); + for (Long keyId : allowedKeyIds) { + statement.bind(packageName, keyId); + statement.execute(); + } } + public void addAllowedKeyIdForApp(String packageName, long allowedKeyId) { + InsertAllowedKey statement = new InsertAllowedKey(db); + statement.bind(packageName, allowedKeyId); + statement.execute(); + } + + public List getAllApiApps() { + SqlDelightQuery query = ApiApp.FACTORY.selectAll(); + + ArrayList result = new ArrayList<>(); + try (Cursor cursor = db.query(query)) { + while (cursor.moveToNext()) { + ApiApp apiApp = ApiApp.FACTORY.selectAllMapper().map(cursor); + result.add(apiApp); + } + } + return result; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java index e6dcff07d..f0afe66ee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeyRepository.java @@ -333,7 +333,7 @@ public class KeyRepository { try { return localSecretKeyStorage.readSecretKey(masterKeyId); } catch (IOException e) { - Timber.e(e, "Error reading public key from storage!"); + Timber.e(e, "Error reading secret key from storage!"); throw new NotFoundException(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java index 85d88d4fc..a47583170 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java @@ -141,9 +141,6 @@ public class KeychainContract { public static final String PATH_KEYS = "keys"; public static final String PATH_CERTS = "certs"; - public static final String BASE_API_APPS = "api_apps"; - public static final String PATH_ALLOWED_KEYS = "allowed_keys"; - public static final String PATH_BY_PACKAGE_NAME = "by_package_name"; public static final String PATH_BY_KEY_ID = "by_key_id"; @@ -323,43 +320,6 @@ public class KeychainContract { } - public static class ApiApps implements ApiAppsColumns, BaseColumns { - public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() - .appendPath(BASE_API_APPS).build(); - - /** - * Use if multiple items get returned - */ - public static final String CONTENT_TYPE - = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.api_apps"; - - /** - * Use if a single item is returned - */ - public static final String CONTENT_ITEM_TYPE - = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.provider.api_apps"; - - public static Uri buildByPackageNameUri(String packageName) { - return CONTENT_URI.buildUpon().appendEncodedPath(packageName).build(); - } - } - - public static class ApiAllowedKeys implements ApiAppsAllowedKeysColumns, BaseColumns { - public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() - .appendPath(BASE_API_APPS).build(); - - /** - * Use if multiple items get returned - */ - public static final String CONTENT_TYPE - = "vnd.android.cursor.dir/vnd.org.sufficientlysecure.keychain.provider.api_apps.allowed_keys"; - - public static Uri buildBaseUri(String packageName) { - return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ALLOWED_KEYS) - .build(); - } - } - public static class ApiAutocryptPeer implements ApiAutocryptPeerColumns, BaseColumns { public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon() .appendPath(BASE_AUTOCRYPT_PEERS).build(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 25d0f0743..e35b6a85d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -23,16 +23,18 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import android.arch.persistence.db.SupportSQLiteDatabase; +import android.arch.persistence.db.SupportSQLiteOpenHelper; +import android.arch.persistence.db.SupportSQLiteOpenHelper.Callback; +import android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration; +import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory; import android.content.Context; import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteOpenHelper; import android.provider.BaseColumns; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAllowedKeysColumns; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeerColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns; @@ -53,10 +55,11 @@ import timber.log.Timber; * - TEXT. The value is a text string, stored using the database encoding (UTF-8, UTF-16BE or UTF-16LE). * - BLOB. The value is a blob of data, stored exactly as it was input. */ -public class KeychainDatabase extends SQLiteOpenHelper { +public class KeychainDatabase { private static final String DATABASE_NAME = "openkeychain.db"; private static final int DATABASE_VERSION = 26; - private Context mContext; + private final SupportSQLiteOpenHelper supportSQLiteOpenHelper; + private Context context; public interface Tables { String KEY_RINGS_PUBLIC = "keyrings_public"; @@ -65,18 +68,11 @@ public class KeychainDatabase extends SQLiteOpenHelper { String KEY_SIGNATURES = "key_signatures"; String USER_PACKETS = "user_packets"; String CERTS = "certs"; - String API_APPS = "api_apps"; String API_ALLOWED_KEYS = "api_allowed_keys"; String OVERRIDDEN_WARNINGS = "overridden_warnings"; String API_AUTOCRYPT_PEERS = "api_autocrypt_peers"; } - private static final String CREATE_KEYRINGS_PUBLIC = - "CREATE TABLE IF NOT EXISTS keyrings_public (" - + KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY," - + KeyRingsColumns.KEY_RING_DATA + " BLOB" - + ")"; - private static final String CREATE_KEYS = "CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " (" + KeysColumns.MASTER_KEY_ID + " INTEGER, " @@ -143,15 +139,6 @@ public class KeychainDatabase extends SQLiteOpenHelper { + Tables.USER_PACKETS + "(" + UserPacketsColumns.MASTER_KEY_ID + ", " + UserPacketsColumns.RANK + ") ON DELETE CASCADE" + ")"; - private static final String CREATE_UPDATE_KEYS = - "CREATE TABLE IF NOT EXISTS " + Tables.UPDATED_KEYS + " (" - + UpdatedKeysColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY, " - + UpdatedKeysColumns.LAST_UPDATED + " INTEGER, " - + UpdatedKeysColumns.SEEN_ON_KEYSERVERS + " INTEGER, " - + "FOREIGN KEY(" + UpdatedKeysColumns.MASTER_KEY_ID + ") REFERENCES " - + Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE" - + ")"; - private static final String CREATE_KEY_SIGNATURES = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_SIGNATURES + " (" + KeySignaturesColumns.MASTER_KEY_ID + " INTEGER NOT NULL, " @@ -175,16 +162,9 @@ public class KeychainDatabase extends SQLiteOpenHelper { + "PRIMARY KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ", " + ApiAutocryptPeerColumns.IDENTIFIER + "), " + "FOREIGN KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ") REFERENCES " - + Tables.API_APPS + "(" + ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE" + + "api_apps (package_signature) ON DELETE CASCADE" + ")"; - private static final String CREATE_API_APPS = - "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS + " (" - + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, " - + ApiAppsColumns.PACKAGE_CERTIFICATE + " BLOB" - + ")"; - private static final String CREATE_API_APPS_ALLOWED_KEYS = "CREATE TABLE IF NOT EXISTS " + Tables.API_ALLOWED_KEYS + " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " @@ -194,7 +174,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { + "UNIQUE(" + ApiAppsAllowedKeysColumns.KEY_ID + ", " + ApiAppsAllowedKeysColumns.PACKAGE_NAME + "), " + "FOREIGN KEY(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") REFERENCES " - + Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE" + + "api_apps (" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE" + ")"; private static final String CREATE_OVERRIDDEN_WARNINGS = @@ -204,12 +184,46 @@ public class KeychainDatabase extends SQLiteOpenHelper { + ")"; public KeychainDatabase(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - mContext = context; + this.context = context; + supportSQLiteOpenHelper = + new FrameworkSQLiteOpenHelperFactory() + .create(Configuration.builder(context).name(DATABASE_NAME).callback( + new Callback(DATABASE_VERSION) { + @Override + public void onCreate(SupportSQLiteDatabase db) { + KeychainDatabase.this.onCreate(db); + } + + @Override + public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { + KeychainDatabase.this.onUpgrade(db, oldVersion, newVersion); + } + + @Override + public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { + KeychainDatabase.this.onDowngrade(db, oldVersion, newVersion); + } + + @Override + public void onOpen(SupportSQLiteDatabase db) { + super.onOpen(db); + if (!db.isReadOnly()) { + // Enable foreign key constraints + db.execSQL("PRAGMA foreign_keys=ON;"); + } + } + }).build()); } - @Override - public void onCreate(SQLiteDatabase db) { + public SupportSQLiteDatabase getReadableDatabase() { + return supportSQLiteOpenHelper.getReadableDatabase(); + } + + public SupportSQLiteDatabase getWritableDatabase() { + return supportSQLiteOpenHelper.getWritableDatabase(); + } + + private void onCreate(SupportSQLiteDatabase db) { Timber.w("Creating database..."); db.execSQL(CREATE_KEYRINGS_PUBLIC); @@ -218,7 +232,6 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL(CREATE_CERTS); db.execSQL(CREATE_UPDATE_KEYS); db.execSQL(CREATE_KEY_SIGNATURES); - db.execSQL(CREATE_API_APPS); db.execSQL(CREATE_API_APPS_ALLOWED_KEYS); db.execSQL(CREATE_OVERRIDDEN_WARNINGS); db.execSQL(CREATE_API_AUTOCRYPT_PEERS); @@ -231,20 +244,10 @@ public class KeychainDatabase extends SQLiteOpenHelper { db.execSQL("CREATE INDEX uids_by_email ON user_packets (" + UserPacketsColumns.EMAIL + ");"); - Preferences.getPreferences(mContext).setKeySignaturesTableInitialized(); + Preferences.getPreferences(context).setKeySignaturesTableInitialized(); } - @Override - public void onOpen(SQLiteDatabase db) { - super.onOpen(db); - if (!db.isReadOnly()) { - // Enable foreign key constraints - db.execSQL("PRAGMA foreign_keys=ON;"); - } - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + private void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { Timber.d("Upgrading db from " + oldVersion + " to " + newVersion); switch (oldVersion) { @@ -459,9 +462,9 @@ public class KeychainDatabase extends SQLiteOpenHelper { } } - private void migrateSecretKeysFromDbToLocalStorage(SQLiteDatabase db) throws IOException { - LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(mContext); - Cursor cursor = db.rawQuery("SELECT master_key_id, key_ring_data FROM keyrings_secret", null); + private void migrateSecretKeysFromDbToLocalStorage(SupportSQLiteDatabase db) throws IOException { + LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context); + Cursor cursor = db.query("SELECT master_key_id, key_ring_data FROM keyrings_secret"); while (cursor.moveToNext()) { long masterKeyId = cursor.getLong(0); byte[] secretKeyBlob = cursor.getBlob(1); @@ -473,8 +476,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { // db.execSQL("DROP TABLE keyrings_secret"); } - @Override - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { // Downgrade is ok for the debug version, makes it easier to work with branches if (Constants.DEBUG) { return; @@ -528,7 +530,7 @@ public class KeychainDatabase extends SQLiteOpenHelper { public void clearDatabase() { getWritableDatabase().execSQL("delete from " + Tables.KEY_RINGS_PUBLIC); getWritableDatabase().execSQL("delete from " + Tables.API_ALLOWED_KEYS); - getWritableDatabase().execSQL("delete from " + Tables.API_APPS); + getWritableDatabase().execSQL("delete from api_apps"); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 1d7e39b6a..1f64a9a49 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -23,6 +23,7 @@ import java.util.Date; import java.util.HashMap; import java.util.List; +import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; @@ -38,8 +39,6 @@ import android.text.TextUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAutocryptPeer; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; @@ -71,10 +70,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe private static final int KEY_RING_LINKED_IDS = 207; private static final int KEY_RING_LINKED_ID_CERTS = 208; - private static final int API_APPS = 301; - private static final int API_APPS_BY_PACKAGE_NAME = 302; - private static final int API_ALLOWED_KEYS = 305; - private static final int KEY_RINGS_FIND_BY_EMAIL = 400; private static final int KEY_RINGS_FIND_BY_SUBKEY = 401; private static final int KEY_RINGS_FIND_BY_USER_ID = 402; @@ -182,22 +177,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe + KeychainContract.PATH_CERTS + "/*/*", KEY_RING_CERTS_SPECIFIC); - /* - * API apps - * - *
-         * api_apps
-         * api_apps/_ (package name)
-         *
-         * api_apps/_/allowed_keys
-         * 
- */ - 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_ALLOWED_KEYS, API_ALLOWED_KEYS); - /* * Trust Identity access * @@ -269,15 +248,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe case KEY_SIGNATURES: return KeySignatures.CONTENT_TYPE; - case API_APPS: - return ApiApps.CONTENT_TYPE; - - case API_APPS_BY_PACKAGE_NAME: - return ApiApps.CONTENT_ITEM_TYPE; - - case API_ALLOWED_KEYS: - return ApiAllowedKeys.CONTENT_TYPE; - default: throw new UnsupportedOperationException("Unknown uri: " + uri); } @@ -781,26 +751,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe } 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_ALLOWED_KEYS: { - qb.setTables(Tables.API_ALLOWED_KEYS); - qb.appendWhere(Tables.API_ALLOWED_KEYS + "." + ApiAllowedKeys.PACKAGE_NAME + " = "); - qb.appendWhereEscapeString(uri.getPathSegments().get(1)); - - break; - } default: { throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")"); } @@ -815,9 +765,10 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe orderBy = sortOrder; } - SQLiteDatabase db = getDb().getReadableDatabase(); + SupportSQLiteDatabase db = getDb().getReadableDatabase(); - Cursor cursor = qb.query(db, projection, selection, selectionArgs, groupBy, null, orderBy); + String query = qb.buildQuery(projection, selection, groupBy, null, orderBy, null); + Cursor cursor = db.query(query, selectionArgs); if (cursor != null) { // Tell the cursor what uri to watch, so it knows when its source data changes cursor.setNotificationUri(getContext().getContentResolver(), uri); @@ -844,7 +795,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe public Uri insert(Uri uri, ContentValues values) { Timber.d("insert(uri=" + uri + ", values=" + values.toString() + ")"); - final SQLiteDatabase db = getDb().getWritableDatabase(); + final SupportSQLiteDatabase db = getDb().getWritableDatabase(); Uri rowUri = null; Long keyId = null; @@ -853,12 +804,12 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe switch (match) { case KEY_RING_PUBLIC: { - db.insertOrThrow(Tables.KEY_RINGS_PUBLIC, null, values); + db.insert(Tables.KEY_RINGS_PUBLIC, SQLiteDatabase.CONFLICT_FAIL, values); keyId = values.getAsLong(KeyRings.MASTER_KEY_ID); break; } case KEY_RING_KEYS: { - db.insertOrThrow(Tables.KEYS, null, values); + db.insert(Tables.KEYS, SQLiteDatabase.CONFLICT_FAIL, values); keyId = values.getAsLong(Keys.MASTER_KEY_ID); break; } @@ -873,46 +824,33 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe if (((Number) values.get(UserPacketsColumns.RANK)).intValue() == 0 && values.get(UserPacketsColumns.USER_ID) == null) { throw new AssertionError("Rank 0 user packet must be a user id!"); } - db.insertOrThrow(Tables.USER_PACKETS, null, values); + db.insert(Tables.USER_PACKETS, SQLiteDatabase.CONFLICT_FAIL, values); keyId = values.getAsLong(UserPackets.MASTER_KEY_ID); break; } case KEY_RING_CERTS: { // we replace here, keeping only the latest signature // TODO this would be better handled in savePublicKeyRing directly! - db.replaceOrThrow(Tables.CERTS, null, values); + db.insert(Tables.CERTS, SQLiteDatabase.CONFLICT_FAIL, values); keyId = values.getAsLong(Certs.MASTER_KEY_ID); break; } case UPDATED_KEYS: { keyId = values.getAsLong(UpdatedKeys.MASTER_KEY_ID); try { - db.insertOrThrow(Tables.UPDATED_KEYS, null, values); + db.insert(Tables.UPDATED_KEYS, SQLiteDatabase.CONFLICT_FAIL, values); } catch (SQLiteConstraintException e) { - db.update(Tables.UPDATED_KEYS, values, + db.update(Tables.UPDATED_KEYS, SQLiteDatabase.CONFLICT_IGNORE, values, UpdatedKeys.MASTER_KEY_ID + " = ?", new String[] { Long.toString(keyId) }); } rowUri = UpdatedKeys.CONTENT_URI; break; } case KEY_SIGNATURES: { - db.insert(Tables.KEY_SIGNATURES, null, values); + db.insert(Tables.KEY_SIGNATURES, SQLiteDatabase.CONFLICT_FAIL, values); rowUri = KeySignatures.CONTENT_URI; break; } - case API_APPS: { - db.insert(Tables.API_APPS, null, values); - break; - } - case API_ALLOWED_KEYS: { - // set foreign key automatically based on given uri - // e.g., api_apps/com.example.app/allowed_keys/ - String packageName = uri.getPathSegments().get(1); - values.put(ApiAllowedKeys.PACKAGE_NAME, packageName); - - db.insert(Tables.API_ALLOWED_KEYS, null, values); - break; - } default: { throw new UnsupportedOperationException("Unknown uri: " + uri); } @@ -937,7 +875,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe public int delete(Uri uri, String additionalSelection, String[] selectionArgs) { Timber.v("delete(uri=" + uri + ")"); - final SQLiteDatabase db = getDb().getWritableDatabase(); + final SupportSQLiteDatabase db = getDb().getWritableDatabase(); int count; final int match = mUriMatcher.match(uri); @@ -980,16 +918,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe count = db.delete(Tables.API_AUTOCRYPT_PEERS, selection, selectionArgs); break; - case API_APPS_BY_PACKAGE_NAME: { - count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, additionalSelection), - selectionArgs); - break; - } - case API_ALLOWED_KEYS: { - count = db.delete(Tables.API_ALLOWED_KEYS, buildDefaultApiAllowedKeysSelection(uri, additionalSelection), - selectionArgs); - break; - } default: { throw new UnsupportedOperationException("Unknown uri: " + uri); } @@ -1005,7 +933,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Timber.v("update(uri=" + uri + ", values=" + values.toString() + ")"); - final SQLiteDatabase db = getDb().getWritableDatabase(); + final SupportSQLiteDatabase db = getDb().getWritableDatabase(); int count = 0; try { @@ -1022,12 +950,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe if (!TextUtils.isEmpty(selection)) { actualSelection += " AND (" + selection + ")"; } - count = db.update(Tables.KEYS, values, actualSelection, selectionArgs); - break; - } - case API_APPS_BY_PACKAGE_NAME: { - count = db.update(Tables.API_APPS, values, - buildDefaultApiAppsSelection(uri, selection), selectionArgs); + count = db.update(Tables.KEYS, SQLiteDatabase.CONFLICT_FAIL, values, actualSelection, selectionArgs); break; } case UPDATED_KEYS: { @@ -1040,7 +963,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe throw new UnsupportedOperationException("can only reset all keys"); } - db.update(Tables.UPDATED_KEYS, values, null, null); + db.update(Tables.UPDATED_KEYS, SQLiteDatabase.CONFLICT_FAIL, values, null, null); break; } case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: { @@ -1049,11 +972,11 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe values.put(ApiAutocryptPeer.PACKAGE_NAME, packageName); values.put(ApiAutocryptPeer.IDENTIFIER, identifier); - int updated = db.update(Tables.API_AUTOCRYPT_PEERS, values, + int updated = db.update(Tables.API_AUTOCRYPT_PEERS, SQLiteDatabase.CONFLICT_IGNORE, values, ApiAutocryptPeer.PACKAGE_NAME + "=? AND " + ApiAutocryptPeer.IDENTIFIER + "=?", new String[] { packageName, identifier }); if (updated == 0) { - db.insertOrThrow(Tables.API_AUTOCRYPT_PEERS, null, values); + db.insert(Tables.API_AUTOCRYPT_PEERS, SQLiteDatabase.CONFLICT_FAIL,values); } break; @@ -1068,35 +991,4 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe 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 buildDefaultApiAllowedKeysSelection(Uri uri, String selection) { - String packageName = DatabaseUtils.sqlEscapeString(uri.getPathSegments().get(1)); - - String andSelection = ""; - if (!TextUtils.isEmpty(selection)) { - andSelection = " AND (" + selection + ")"; - } - - return ApiAllowedKeys.PACKAGE_NAME + "=" + packageName + andSelection; - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LocalSecretKeyStorage.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LocalSecretKeyStorage.java new file mode 100644 index 000000000..cda09fde3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/LocalSecretKeyStorage.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 Schürmann & Breitmoser GbR + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.provider; + + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +import android.content.Context; + +import okhttp3.internal.Util; + + +class LocalSecretKeyStorage { + private static final String FORMAT_STR_SECRET_KEY = "0x%016x.sec"; + private static final String SECRET_KEYS_DIR_NAME = "secret_keys"; + + + private final File localSecretKeysDir; + + + public static LocalSecretKeyStorage getInstance(Context context) { + File localSecretKeysDir = new File(context.getFilesDir(), SECRET_KEYS_DIR_NAME); + return new LocalSecretKeyStorage(localSecretKeysDir); + } + + private LocalSecretKeyStorage(File localSecretKeysDir) { + this.localSecretKeysDir = localSecretKeysDir; + } + + private File getSecretKeyFile(long masterKeyId) throws IOException { + if (!localSecretKeysDir.exists()) { + localSecretKeysDir.mkdir(); + } + if (!localSecretKeysDir.isDirectory()) { + throw new IOException("Failed creating public key directory!"); + } + + String keyFilename = String.format(FORMAT_STR_SECRET_KEY, masterKeyId); + return new File(localSecretKeysDir, keyFilename); + } + + void writeSecretKey(long masterKeyId, byte[] encoded) throws IOException { + File publicKeyFile = getSecretKeyFile(masterKeyId); + + FileOutputStream fileOutputStream = new FileOutputStream(publicKeyFile); + try { + fileOutputStream.write(encoded); + } finally { + Util.closeQuietly(fileOutputStream); + } + } + + byte[] readSecretKey(long masterKeyId) throws IOException { + File publicKeyFile = getSecretKeyFile(masterKeyId); + + try { + FileInputStream fileInputStream = new FileInputStream(publicKeyFile); + return readIntoByteArray(fileInputStream); + } catch (FileNotFoundException e) { + return null; + } + } + + private static byte[] readIntoByteArray(FileInputStream fileInputStream) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + byte[] buf = new byte[128]; + int bytesRead; + while ((bytesRead = fileInputStream.read(buf)) != -1) { + baos.write(buf, 0, bytesRead); + } + + return baos.toByteArray(); + } + + void deleteSecretKey(long masterKeyId) throws IOException { + File publicKeyFile = getSecretKeyFile(masterKeyId); + if (publicKeyFile.exists()) { + boolean deleteSuccess = publicKeyFile.delete(); + if (!deleteSuccess) { + throw new IOException("File exists, but could not be deleted!"); + } + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java index 4405363fe..fe1b23ca5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/OverriddenWarningsRepository.java @@ -18,6 +18,9 @@ package org.sufficientlysecure.keychain.provider; +import android.arch.persistence.db.SupportSQLiteDatabase; +import android.arch.persistence.db.SupportSQLiteQuery; +import android.arch.persistence.db.SupportSQLiteQueryBuilder; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; @@ -47,34 +50,31 @@ public class OverriddenWarningsRepository { } public boolean isWarningOverridden(String identifier) { - SQLiteDatabase db = getDb().getReadableDatabase(); - Cursor cursor = db.query( - Tables.OVERRIDDEN_WARNINGS, - new String[] { "COUNT(*)" }, - OverriddenWarnings.IDENTIFIER + " = ?", - new String[] { identifier }, - null, null, null); + SupportSQLiteDatabase db = getDb().getReadableDatabase(); + SupportSQLiteQuery query = SupportSQLiteQueryBuilder + .builder(Tables.OVERRIDDEN_WARNINGS) + .columns(new String[] { "COUNT(*) FROM " }) + .selection(OverriddenWarnings.IDENTIFIER + " = ?", new String[] { identifier }) + .create(); + Cursor cursor = db.query(query); try { cursor.moveToFirst(); return cursor.getInt(0) > 0; } finally { cursor.close(); - db.close(); } } public void putOverride(String identifier) { - SQLiteDatabase db = getDb().getWritableDatabase(); + SupportSQLiteDatabase db = getDb().getWritableDatabase(); ContentValues cv = new ContentValues(); cv.put(OverriddenWarnings.IDENTIFIER, identifier); - db.replace(Tables.OVERRIDDEN_WARNINGS, null, cv); - db.close(); + db.insert(Tables.OVERRIDDEN_WARNINGS, SQLiteDatabase.CONFLICT_REPLACE, cv); } public void deleteOverride(String identifier) { - SQLiteDatabase db = getDb().getWritableDatabase(); + SupportSQLiteDatabase db = getDb().getWritableDatabase(); db.delete(Tables.OVERRIDDEN_WARNINGS, OverriddenWarnings.IDENTIFIER + " = ?", new String[] { identifier }); - db.close(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java index 18a467299..4f135f14f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java @@ -138,7 +138,7 @@ public class ApiPendingIntentFactory { PendingIntent createSelectSignKeyIdLegacyPendingIntent(Intent data, String packageName, String preferredUserId) { Intent intent = new Intent(mContext, SelectSignKeyIdActivity.class); - intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(packageName)); + intent.putExtra(SelectSignKeyIdActivity.EXTRA_PACKAGE_NAME, packageName); intent.putExtra(SelectSignKeyIdActivity.EXTRA_USER_ID, preferredUserId); return createInternal(data, intent); @@ -147,7 +147,6 @@ public class ApiPendingIntentFactory { PendingIntent createSelectSignKeyIdPendingIntent(Intent data, String packageName, byte[] packageSignature, String preferredUserId, boolean showAutocryptHint) { Intent intent = new Intent(mContext, RemoteSelectIdKeyActivity.class); - intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(packageName)); intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_PACKAGE_NAME, packageName); intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature); intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_USER_ID, preferredUserId); @@ -158,7 +157,6 @@ public class ApiPendingIntentFactory { PendingIntent createSelectAuthenticationKeyIdPendingIntent(Intent data, String packageName) { Intent intent = new Intent(mContext, RemoteSelectAuthenticationKeyActivity.class); - intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(packageName)); intent.putExtra(RemoteSelectAuthenticationKeyActivity.EXTRA_PACKAGE_NAME, packageName); return createInternal(data, intent); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java deleted file mode 100644 index a5dcc24bd..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/AppSettings.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.remote; - -public class AppSettings { - private String mPackageName; - private byte[] mPackageCertificate; - - public AppSettings() { - - } - - public AppSettings(String packageName, byte[] packageSignature) { - super(); - this.mPackageName = packageName; - this.mPackageCertificate = packageSignature; - } - - public String getPackageName() { - return mPackageName; - } - - public void setPackageName(String packageName) { - this.mPackageName = packageName; - } - - public byte[] getPackageCertificate() { - return mPackageCertificate; - } - - public void setPackageCertificate(byte[] packageCertificate) { - this.mPackageCertificate = packageCertificate; - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java index 2253e3e4d..855babaaf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/KeychainExternalProvider.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import android.arch.persistence.db.SupportSQLiteDatabase; import android.content.ContentProvider; import android.content.ContentValues; import android.content.Context; @@ -42,7 +43,6 @@ import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject; import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptRecommendationResult; import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptState; import org.sufficientlysecure.keychain.provider.DatabaseNotifyManager; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; import org.sufficientlysecure.keychain.provider.KeychainContract.Certs; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; @@ -54,7 +54,6 @@ import org.sufficientlysecure.keychain.provider.KeychainExternalContract.Autocry import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus; import org.sufficientlysecure.keychain.provider.KeychainProvider; import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface; -import org.sufficientlysecure.keychain.util.CloseDatabaseCursorFactory; import timber.log.Timber; @@ -64,9 +63,6 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC private static final int AUTOCRYPT_STATUS = 201; private static final int AUTOCRYPT_STATUS_INTERNAL = 202; - private static final int API_APPS = 301; - private static final int API_APPS_BY_PACKAGE_NAME = 302; - public static final String TEMP_TABLE_QUERIED_ADDRESSES = "queried_addresses"; public static final String TEMP_TABLE_COLUMN_ADDRES = "address"; @@ -113,7 +109,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC internalKeychainProvider = new KeychainProvider(); internalKeychainProvider.attachInfo(context, null); - apiPermissionHelper = new ApiPermissionHelper(context, new ApiDataAccessObject(internalKeychainProvider)); + apiPermissionHelper = new ApiPermissionHelper(context, new ApiDataAccessObject(getContext())); databaseNotifyManager = DatabaseNotifyManager.create(context); return true; } @@ -127,13 +123,6 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC switch (match) { case EMAIL_STATUS: return EmailStatus.CONTENT_TYPE; - - case API_APPS: - return ApiApps.CONTENT_TYPE; - - case API_APPS_BY_PACKAGE_NAME: - return ApiApps.CONTENT_ITEM_TYPE; - default: throw new UnsupportedOperationException("Unknown uri: " + uri); } @@ -154,7 +143,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC String groupBy = null; - SQLiteDatabase db = new KeychainDatabase(getContext()).getReadableDatabase(); + SupportSQLiteDatabase db = new KeychainDatabase(getContext()).getReadableDatabase(); String callingPackageName = apiPermissionHelper.getCurrentCallingPackage(); @@ -169,7 +158,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC ContentValues cv = new ContentValues(); for (String address : selectionArgs) { cv.put(TEMP_TABLE_COLUMN_ADDRES, address); - db.insert(TEMP_TABLE_QUERIED_ADDRESSES, null, cv); + db.insert(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_FAIL, cv); } HashMap projectionMap = new HashMap<>(); @@ -256,7 +245,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC ContentValues cv = new ContentValues(); for (String address : selectionArgs) { cv.put(TEMP_TABLE_COLUMN_ADDRES, address); - db.insert(TEMP_TABLE_QUERIED_ADDRESSES, null, cv); + db.insert(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_FAIL, cv); } boolean isWildcardSelector = selectionArgs.length == 1 && selectionArgs[0].contains("%"); @@ -321,8 +310,8 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC } qb.setStrict(true); - qb.setCursorFactory(new CloseDatabaseCursorFactory()); - Cursor cursor = qb.query(db, projection, null, null, groupBy, null, orderBy); + String query = qb.buildQuery(projection, null, null, groupBy, null, orderBy); + Cursor cursor = db.query(query); if (cursor != null) { // Tell the cursor what uri to watch, so it knows when its source data changes cursor.setNotificationUri(getContext().getContentResolver(), uri); @@ -337,7 +326,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC return cursor; } - private void fillTempTableWithAutocryptRecommendations(SQLiteDatabase db, + private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db, AutocryptPeerDataAccessObject autocryptPeerDao, String[] peerIds) { List autocryptStates = autocryptPeerDao.determineAutocryptRecommendations(peerIds); @@ -345,7 +334,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC fillTempTableWithAutocryptRecommendations(db, autocryptStates); } - private void fillTempTableWithAutocryptRecommendations(SQLiteDatabase db, + private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db, List autocryptRecommendations) { ContentValues cv = new ContentValues(); for (AutocryptRecommendationResult peerResult : autocryptRecommendations) { @@ -359,12 +348,12 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC KeychainExternalContract.KEY_STATUS_UNVERIFIED); } - db.update(TEMP_TABLE_QUERIED_ADDRESSES, cv,TEMP_TABLE_COLUMN_ADDRES + "=?", + db.update(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_IGNORE, cv,TEMP_TABLE_COLUMN_ADDRES + "=?", new String[] { peerResult.peerId }); } } - private void fillTempTableWithUidResult(SQLiteDatabase db, boolean isWildcardSelector) { + private void fillTempTableWithUidResult(SupportSQLiteDatabase db, boolean isWildcardSelector) { String cmpOperator = isWildcardSelector ? " LIKE " : " = "; long unixSeconds = System.currentTimeMillis() / 1000; db.execSQL("REPLACE INTO " + TEMP_TABLE_QUERIED_ADDRESSES + diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 6b1ea3ef6..8406c297b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -915,8 +915,7 @@ public class OpenPgpService extends Service { private HashSet getAllowedKeyIds() { String currentPkg = mApiPermissionHelper.getCurrentCallingPackage(); - return mApiDao.getAllowedKeyIdsForApp( - KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg)); + return mApiDao.getAllowedKeyIdsForApp(currentPkg); } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/PackageUninstallReceiver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/PackageUninstallReceiver.java index 338807ec0..85c6b2dad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/PackageUninstallReceiver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/PackageUninstallReceiver.java @@ -17,12 +17,13 @@ package org.sufficientlysecure.keychain.remote; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.net.Uri; -import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; public class PackageUninstallReceiver extends BroadcastReceiver { @@ -34,8 +35,9 @@ public class PackageUninstallReceiver extends BroadcastReceiver { return; } String packageName = uri.getEncodedSchemeSpecificPart(); - Uri appUri = KeychainContract.ApiApps.buildByPackageNameUri(packageName); - context.getContentResolver().delete(appUri, null, null); + + ApiDataAccessObject apiDao = new ApiDataAccessObject(context); + apiDao.deleteApiApp(packageName); } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java index 57dab705e..aead4985c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/SshAuthenticationService.java @@ -17,11 +17,19 @@ package org.sufficientlysecure.keychain.remote; + +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; + import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.os.IBinder; -import android.util.Log; + import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; import org.openintents.ssh.authentication.ISshAuthenticationService; @@ -40,7 +48,6 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeyRepository; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ssh.AuthenticationData; @@ -50,13 +57,6 @@ import org.sufficientlysecure.keychain.ssh.AuthenticationResult; import org.sufficientlysecure.keychain.ssh.signature.SshSignatureConverter; import timber.log.Timber; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.List; - public class SshAuthenticationService extends Service { private static final String TAG = "SshAuthService"; @@ -394,7 +394,7 @@ public class SshAuthenticationService extends Service { private HashSet getAllowedKeyIds() { String currentPkg = mApiPermissionHelper.getCurrentCallingPackage(); - return mApiDao.getAllowedKeyIdsForApp(KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg)); + return mApiDao.getAllowedKeyIdsForApp(currentPkg); } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java index a54da0fb7..79c54147e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java @@ -26,7 +26,6 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; -import android.net.Uri; import android.os.Bundle; import android.support.v4.app.FragmentManager; import android.view.Menu; @@ -36,24 +35,26 @@ import android.widget.TextView; import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.ApiApp; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.remote.AppSettings; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment; import timber.log.Timber; public class AppSettingsActivity extends BaseActivity { - private Uri mAppUri; + public static final String EXTRA_PACKAGE_NAME = "package_name"; + + private String packageName; private TextView mAppNameView; private ImageView mAppIconView; // model - AppSettings mAppSettings; + ApiApp mApiApp; + private ApiDataAccessObject apiDataAccessObject; @Override protected void onCreate(Bundle savedInstanceState) { @@ -68,15 +69,16 @@ public class AppSettingsActivity extends BaseActivity { setTitle(null); Intent intent = getIntent(); - mAppUri = intent.getData(); - if (mAppUri == null) { - Timber.e("Intent data missing. Should be Uri of app!"); + packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME); + if (packageName == null) { + Timber.e("Required extra package_name missing!"); finish(); return; } - Timber.d("uri: %s", mAppUri); - loadData(savedInstanceState, mAppUri); + apiDataAccessObject = new ApiDataAccessObject(this); + + loadData(savedInstanceState); } private void save() { @@ -138,7 +140,7 @@ public class AppSettingsActivity extends BaseActivity { // advanced info: package certificate SHA-256 try { MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(mAppSettings.getPackageCertificate()); + md.update(mApiApp.package_signature()); byte[] digest = md.digest(); certificate = new String(Hex.encode(digest)); } catch (NoSuchAlgorithmException e) { @@ -146,7 +148,7 @@ public class AppSettingsActivity extends BaseActivity { } AdvancedAppSettingsDialogFragment dialogFragment = - AdvancedAppSettingsDialogFragment.newInstance(mAppSettings.getPackageName(), certificate); + AdvancedAppSettingsDialogFragment.newInstance(mApiApp.package_name(), certificate); dialogFragment.show(getSupportFragmentManager(), "advancedDialog"); } @@ -155,7 +157,7 @@ public class AppSettingsActivity extends BaseActivity { Intent i; PackageManager manager = getPackageManager(); try { - i = manager.getLaunchIntentForPackage(mAppSettings.getPackageName()); + i = manager.getLaunchIntentForPackage(mApiApp.package_name()); if (i == null) throw new PackageManager.NameNotFoundException(); // start like the Android launcher would do @@ -167,31 +169,29 @@ public class AppSettingsActivity extends BaseActivity { } } - private void loadData(Bundle savedInstanceState, Uri appUri) { - mAppSettings = new ApiDataAccessObject(this).getApiAppSettings(appUri); + private void loadData(Bundle savedInstanceState) { + mApiApp = apiDataAccessObject.getApiApp(packageName); // get application name and icon from package manager String appName; Drawable appIcon = null; PackageManager pm = getApplicationContext().getPackageManager(); try { - ApplicationInfo ai = pm.getApplicationInfo(mAppSettings.getPackageName(), 0); + ApplicationInfo ai = pm.getApplicationInfo(mApiApp.package_name(), 0); appName = (String) pm.getApplicationLabel(ai); appIcon = pm.getApplicationIcon(ai); } catch (PackageManager.NameNotFoundException e) { // fallback - appName = mAppSettings.getPackageName(); + appName = mApiApp.package_name(); } mAppNameView.setText(appName); mAppIconView.setImageDrawable(appIcon); - Uri allowedKeysUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build(); - Timber.d("allowedKeysUri: " + allowedKeysUri); - startListFragments(savedInstanceState, allowedKeysUri); + startListFragments(savedInstanceState); } - private void startListFragments(Bundle savedInstanceState, Uri allowedKeysUri) { + private void startListFragments(Bundle savedInstanceState) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. @@ -199,7 +199,8 @@ public class AppSettingsActivity extends BaseActivity { return; } - AppSettingsAllowedKeysListFragment allowedKeysFragment = AppSettingsAllowedKeysListFragment.newInstance(allowedKeysUri); + // Create an instance of the fragments + AppSettingsAllowedKeysListFragment allowedKeysFragment = AppSettingsAllowedKeysListFragment.newInstance(packageName); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! getSupportFragmentManager().beginTransaction() @@ -210,9 +211,7 @@ public class AppSettingsActivity extends BaseActivity { } private void revokeAccess() { - if (getContentResolver().delete(mAppUri, null, null) <= 0) { - throw new RuntimeException(); - } + apiDataAccessObject.deleteApiApp(packageName); finish(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java index fd4f37525..5c8b99926 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsAllowedKeysListFragment.java @@ -20,11 +20,9 @@ package org.sufficientlysecure.keychain.remote.ui; import java.util.Set; -import android.content.OperationApplicationException; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; -import android.os.RemoteException; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -40,25 +38,24 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import org.sufficientlysecure.keychain.ui.adapter.KeySelectableAdapter; import org.sufficientlysecure.keychain.ui.widget.FixedListView; -import timber.log.Timber; public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround implements LoaderManager.LoaderCallbacks { - private static final String ARG_DATA_URI = "uri"; + private static final String ARG_PACKAGE_NAME = "package_name"; private KeySelectableAdapter mAdapter; private ApiDataAccessObject mApiDao; - private Uri mDataUri; + private String packageName; /** * Creates new instance of this fragment */ - public static AppSettingsAllowedKeysListFragment newInstance(Uri dataUri) { + public static AppSettingsAllowedKeysListFragment newInstance(String packageName) { AppSettingsAllowedKeysListFragment frag = new AppSettingsAllowedKeysListFragment(); Bundle args = new Bundle(); - args.putParcelable(ARG_DATA_URI, dataUri); + args.putString(ARG_PACKAGE_NAME, packageName); frag.setArguments(args); @@ -101,13 +98,13 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - mDataUri = getArguments().getParcelable(ARG_DATA_URI); + packageName = getArguments().getString(ARG_PACKAGE_NAME); // Give some text to display if there is no data. In a real // application this would come from a resource. setEmptyText(getString(R.string.list_empty)); - Set checked = mApiDao.getAllowedKeyIdsForApp(mDataUri); + Set checked = mApiDao.getAllowedKeyIdsForApp(packageName); mAdapter = new KeySelectableAdapter(getActivity(), null, 0, checked); setListAdapter(mAdapter); getListView().setOnItemClickListener(mAdapter); @@ -140,11 +137,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i } */ public void saveAllowedKeys() { - try { - mApiDao.saveAllowedKeyIdsForApp(mDataUri, getSelectedMasterKeyIds()); - } catch (RemoteException | OperationApplicationException e) { - Timber.e(e, "Problem saving allowed key ids!"); - } + mApiDao.saveAllowedKeyIdsForApp(packageName, getSelectedMasterKeyIds()); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java deleted file mode 100644 index dc9b4d487..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsHeaderFragment.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2017 Schürmann & Breitmoser GbR - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.remote.ui; - -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import org.bouncycastle.util.encoders.Hex; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.remote.AppSettings; -import timber.log.Timber; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public class AppSettingsHeaderFragment extends Fragment { - - // model - private AppSettings mAppSettings; - - // view - private TextView mAppNameView; - private ImageView mAppIconView; - private TextView mPackageName; - private TextView mPackageCertificate; - - public AppSettings getAppSettings() { - return mAppSettings; - } - - public void setAppSettings(AppSettings appSettings) { - this.mAppSettings = appSettings; - updateView(appSettings); - } - - /** - * Inflate the layout for this fragment - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.api_app_settings_fragment, container, false); - mAppNameView = view.findViewById(R.id.api_app_settings_app_name); - mAppIconView = view.findViewById(R.id.api_app_settings_app_icon); - mPackageName = view.findViewById(R.id.api_app_settings_package_name); - mPackageCertificate = view.findViewById(R.id.api_app_settings_package_certificate); - return view; - } - - private void updateView(AppSettings appSettings) { - // get application name and icon from package manager - String appName; - Drawable appIcon = null; - PackageManager pm = getActivity().getApplicationContext().getPackageManager(); - try { - ApplicationInfo ai = pm.getApplicationInfo(appSettings.getPackageName(), 0); - - appName = (String) pm.getApplicationLabel(ai); - appIcon = pm.getApplicationIcon(ai); - } catch (NameNotFoundException e) { - // fallback - appName = appSettings.getPackageName(); - } - mAppNameView.setText(appName); - mAppIconView.setImageDrawable(appIcon); - - // advanced info: package name - mPackageName.setText(appSettings.getPackageName()); - - // advanced info: package signature SHA-256 - try { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(appSettings.getPackageCertificate()); - byte[] digest = md.digest(); - String signature = new String(Hex.encode(digest)); - - mPackageCertificate.setText(signature); - } catch (NoSuchAlgorithmException e) { - Timber.e(e, "Should not happen!"); - } - } - - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java index 6410d8f10..e55b1e19f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java @@ -17,87 +17,77 @@ package org.sufficientlysecure.keychain.remote.ui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.database.Cursor; -import android.database.CursorJoiner; -import android.database.MatrixCursor; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; -import android.support.v4.app.ListFragment; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.support.v4.widget.CursorAdapter; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.Adapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; import android.widget.ImageView; import android.widget.TextView; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps; +import org.sufficientlysecure.keychain.model.ApiApp; +import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; +import org.sufficientlysecure.keychain.remote.ui.AppsListFragment.ApiAppAdapter; +import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; +import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData; import timber.log.Timber; -public class AppsListFragment extends ListFragment implements - LoaderManager.LoaderCallbacks, OnItemClickListener { - - AppsAdapter mAdapter; +public class AppsListFragment extends RecyclerFragment { + private ApiAppAdapter adapter; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - getListView().setOnItemClickListener(this); - - // NOTE: No setEmptyText(), we always have the default entries - - // We have a menu item to show in action bar. setHasOptionsMenu(true); - // Create an empty adapter we will use to display the loaded data. - mAdapter = new AppsAdapter(getActivity(), null, 0); - setListAdapter(mAdapter); + adapter = new ApiAppAdapter(getActivity()); + setAdapter(adapter); + setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false)); - // NOTE: Loader is started in onResume! + new ApiAppsLiveData(getContext()).observe(this, this::onLoad); } - @Override - public void onResume() { - super.onResume(); - - // Start out with a progress indicator. - setListShown(false); - - // After coming back from Google Play -> reload - getLoaderManager().restartLoader(0, null, this); + private void onLoad(List apiApps) { + if (apiApps == null) { + hideList(false); + adapter.setData(null); + return; + } + adapter.setData(apiApps); + showList(true); } - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - String selectedPackageName = mAdapter.getItemPackageName(position); - boolean installed = mAdapter.getItemIsInstalled(position); - boolean registered = mAdapter.getItemIsRegistered(position); + public void onItemClick(int position) { + ListedApp listedApp = adapter.data.get(position); - if (installed) { - if (registered) { + if (listedApp.isInstalled) { + if (listedApp.isRegistered) { // Edit app settings Intent intent = new Intent(getActivity(), AppSettingsActivity.class); - intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName)); + intent.putExtra(AppSettingsActivity.EXTRA_PACKAGE_NAME, listedApp.packageName); startActivity(intent); } else { Intent i; PackageManager manager = getActivity().getPackageManager(); try { - i = manager.getLaunchIntentForPackage(selectedPackageName); + i = manager.getLaunchIntentForPackage(listedApp.packageName); if (i == null) { throw new PackageManager.NameNotFoundException(); } @@ -112,256 +102,163 @@ public class AppsListFragment extends ListFragment implements } else { try { startActivity(new Intent(Intent.ACTION_VIEW, - Uri.parse("market://details?id=" + selectedPackageName))); + Uri.parse("market://details?id=" + listedApp.packageName))); } catch (ActivityNotFoundException anfe) { startActivity(new Intent(Intent.ACTION_VIEW, - Uri.parse("https://play.google.com/store/apps/details?id=" + selectedPackageName))); + Uri.parse("https://play.google.com/store/apps/details?id=" + listedApp.packageName))); } } } - private static final String TEMP_COLUMN_NAME = "NAME"; - private static final String TEMP_COLUMN_INSTALLED = "INSTALLED"; - private static final String TEMP_COLUMN_REGISTERED = "REGISTERED"; - private static final String TEMP_COLUMN_ICON_RES_ID = "ICON_RES_ID"; + public class ApiAppAdapter extends Adapter { + private final LayoutInflater inflater; - static final String[] PROJECTION = new String[]{ - ApiApps._ID, // 0 - ApiApps.PACKAGE_NAME, // 1 - "null as " + TEMP_COLUMN_NAME, // installed apps can retrieve app name from Android OS - "0 as " + TEMP_COLUMN_INSTALLED, // changed later in cursor joiner - "1 as " + TEMP_COLUMN_REGISTERED, // if it is in db it is registered - "0 as " + TEMP_COLUMN_ICON_RES_ID // not used + private List data; + + ApiAppAdapter(Context context) { + super(); + + inflater = LayoutInflater.from(context); + } + + @Override + public ApiAppViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new ApiAppViewHolder(inflater.inflate(R.layout.api_apps_adapter_list_item, parent, false)); + } + + @Override + public void onBindViewHolder(ApiAppViewHolder holder, int position) { + ListedApp item = data.get(position); + holder.bind(item); + } + + @Override + public int getItemCount() { + return data != null ? data.size() : 0; + } + + public void setData(List data) { + this.data = data; + notifyDataSetChanged(); + } + } + + public class ApiAppViewHolder extends RecyclerView.ViewHolder { + private final TextView text; + private final ImageView icon; + private final ImageView installIcon; + + ApiAppViewHolder(View itemView) { + super(itemView); + + text = itemView.findViewById(R.id.api_apps_adapter_item_name); + icon = itemView.findViewById(R.id.api_apps_adapter_item_icon); + installIcon = itemView.findViewById(R.id.api_apps_adapter_install_icon); + itemView.setOnClickListener((View view) -> onItemClick(getAdapterPosition())); + } + + void bind(ListedApp listedApp) { + text.setText(listedApp.readableName); + if (listedApp.applicationIconRes != null) { + icon.setImageResource(listedApp.applicationIconRes); + } else { + icon.setImageDrawable(listedApp.applicationIcon); + } + installIcon.setVisibility(listedApp.isInstalled ? View.GONE : View.VISIBLE); + } + } + + public static class ApiAppsLiveData extends AsyncTaskLiveData> { + private final ApiDataAccessObject apiDao; + private final PackageManager packageManager; + + ApiAppsLiveData(Context context) { + super(context, null); + + packageManager = getContext().getPackageManager(); + apiDao = new ApiDataAccessObject(context); + } + + @Override + protected List asyncLoadData() { + ArrayList result = new ArrayList<>(); + + loadRegisteredApps(result); + addPlaceholderApps(result); + + Collections.sort(result, (o1, o2) -> o1.readableName.compareTo(o2.readableName)); + return result; + } + + private void loadRegisteredApps(ArrayList result) { + List registeredApiApps = apiDao.getAllApiApps(); + + for (ApiApp apiApp : registeredApiApps) { + ListedApp listedApp; + try { + ApplicationInfo ai = packageManager.getApplicationInfo(apiApp.package_name(), 0); + CharSequence applicationLabel = packageManager.getApplicationLabel(ai); + Drawable applicationIcon = packageManager.getApplicationIcon(ai); + + listedApp = new ListedApp(apiApp.package_name(), true, true, applicationLabel, applicationIcon, null); + } catch (PackageManager.NameNotFoundException e) { + listedApp = new ListedApp(apiApp.package_name(), false, true, apiApp.package_name(), null, null); + } + result.add(listedApp); + } + } + + private void addPlaceholderApps(ArrayList result) { + for (ListedApp placeholderApp : PLACERHOLDER_APPS) { + if (!containsByPackageName(result, placeholderApp.packageName)) { + try { + packageManager.getApplicationInfo(placeholderApp.packageName, 0); + result.add(placeholderApp.withIsInstalled()); + } catch (PackageManager.NameNotFoundException e) { + result.add(placeholderApp); + } + } + } + } + + private boolean containsByPackageName(ArrayList result, String packageName) { + for (ListedApp app : result) { + if (packageName.equals(app.packageName)) { + return true; + } + } + return false; + } + } + + public static class ListedApp { + final String packageName; + final boolean isInstalled; + final boolean isRegistered; + final String readableName; + final Drawable applicationIcon; + final Integer applicationIconRes; + + ListedApp(String packageName, boolean isInstalled, boolean isRegistered, CharSequence readableName, + Drawable applicationIcon, Integer applicationIconRes) { + this.packageName = packageName; + this.isInstalled = isInstalled; + this.isRegistered = isRegistered; + this.readableName = readableName.toString(); + this.applicationIcon = applicationIcon; + this.applicationIconRes = applicationIconRes; + } + + public ListedApp withIsInstalled() { + return new ListedApp(packageName, true, isRegistered, readableName, applicationIcon, applicationIconRes); + } + } + + private static final ListedApp[] PLACERHOLDER_APPS = { + new ListedApp("com.fsck.k9", false, false, "K-9 Mail", null, R.drawable.apps_k9), + new ListedApp("com.zeapo.pwdstore", false, false, "Password Store", null, R.drawable.apps_password_store), + new ListedApp("eu.siacs.conversations", false, false, "Conversations (Instant Messaging)", null, + R.drawable.apps_conversations) }; - private static final int INDEX_ID = 0; - private static final int INDEX_PACKAGE_NAME = 1; - private static final int INDEX_NAME = 2; - private static final int INDEX_INSTALLED = 3; - private static final int INDEX_REGISTERED = 4; - private static final int INDEX_ICON_RES_ID = 5; - - public Loader onCreateLoader(int id, Bundle args) { - // This is called when a new Loader needs to be created. This - // sample only has one Loader, so we don't care about the ID. - // First, pick the base URI to use depending on whether we are - // currently filtering. - Uri baseUri = ApiApps.CONTENT_URI; - - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new AppsLoader(getActivity(), baseUri, PROJECTION, null, null, - ApiApps.PACKAGE_NAME + " COLLATE LOCALIZED ASC"); - } - - public void onLoadFinished(Loader loader, Cursor data) { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - mAdapter.swapCursor(data); - - // The list should now be shown. - setListShown(true); - } - - public void onLoaderReset(Loader loader) { - // This is called when the last Cursor provided to onLoadFinished() - // above is about to be closed. We need to make sure we are no - // longer using it. - mAdapter.swapCursor(null); - } - - /** - * Besides the queried cursor with all registered apps, this loader also returns non-installed - * proposed apps using a MatrixCursor. - */ - private static class AppsLoader extends CursorLoader { - - public AppsLoader(Context context) { - super(context); - } - - public AppsLoader(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - super(context, uri, projection, selection, selectionArgs, sortOrder); - } - - @Override - public Cursor loadInBackground() { - // Load registered apps from content provider - Cursor data = super.loadInBackground(); - - MatrixCursor availableAppsCursor = new MatrixCursor(new String[]{ - ApiApps._ID, - ApiApps.PACKAGE_NAME, - TEMP_COLUMN_NAME, - TEMP_COLUMN_INSTALLED, - TEMP_COLUMN_REGISTERED, - TEMP_COLUMN_ICON_RES_ID - }); - // NOTE: SORT ascending by package name, this is REQUIRED for CursorJoiner! - // Drawables taken from projects res/drawables-xxhdpi/ic_launcher.png - availableAppsCursor.addRow(new Object[]{1, "com.fsck.k9", "K-9 Mail", 0, 0, R.drawable.apps_k9}); - availableAppsCursor.addRow(new Object[]{1, "com.zeapo.pwdstore", "Password Store", 0, 0, R.drawable.apps_password_store}); - availableAppsCursor.addRow(new Object[]{1, "eu.siacs.conversations", "Conversations (Instant Messaging)", 0, 0, R.drawable.apps_conversations}); - - MatrixCursor mergedCursor = new MatrixCursor(new String[]{ - ApiApps._ID, - ApiApps.PACKAGE_NAME, - TEMP_COLUMN_NAME, - TEMP_COLUMN_INSTALLED, - TEMP_COLUMN_REGISTERED, - TEMP_COLUMN_ICON_RES_ID - }); - - CursorJoiner joiner = new CursorJoiner( - availableAppsCursor, - new String[]{ApiApps.PACKAGE_NAME}, - data, - new String[]{ApiApps.PACKAGE_NAME}); - for (CursorJoiner.Result joinerResult : joiner) { - switch (joinerResult) { - case LEFT: { - // handle case where a row in availableAppsCursor is unique - String packageName = availableAppsCursor.getString(INDEX_PACKAGE_NAME); - - mergedCursor.addRow(new Object[]{ - 1, // no need for unique _ID - packageName, - availableAppsCursor.getString(INDEX_NAME), - isInstalled(packageName), - 0, - availableAppsCursor.getInt(INDEX_ICON_RES_ID) - }); - break; - } - case RIGHT: { - // handle case where a row in data is unique - String packageName = data.getString(INDEX_PACKAGE_NAME); - - mergedCursor.addRow(new Object[]{ - 1, // no need for unique _ID - packageName, - null, - isInstalled(packageName), - 1, // registered! - R.mipmap.ic_launcher // icon is retrieved later - }); - break; - } - case BOTH: { - // handle case where a row with the same key is in both cursors - String packageName = data.getString(INDEX_PACKAGE_NAME); - - String name; - if (isInstalled(packageName) == 1) { - name = data.getString(INDEX_NAME); - } else { - // if not installed take name from available apps list - name = availableAppsCursor.getString(INDEX_NAME); - } - - mergedCursor.addRow(new Object[]{ - 1, // no need for unique _ID - packageName, - name, - isInstalled(packageName), - 1, // registered! - R.mipmap.ic_launcher // icon is retrieved later - }); - break; - } - } - } - - return mergedCursor; - } - - private int isInstalled(String packageName) { - try { - getContext().getPackageManager().getApplicationInfo(packageName, 0); - return 1; - } catch (final PackageManager.NameNotFoundException e) { - return 0; - } - } - } - - private class AppsAdapter extends CursorAdapter { - - private LayoutInflater mInflater; - private PackageManager mPM; - - public AppsAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - - mInflater = LayoutInflater.from(context); - mPM = context.getApplicationContext().getPackageManager(); - } - - /** - * Similar to CursorAdapter.getItemId(). - * Required to build Uris for api apps, which are not based on row ids - */ - public String getItemPackageName(int position) { - if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) { - return mCursor.getString(INDEX_PACKAGE_NAME); - } else { - return null; - } - } - - public boolean getItemIsInstalled(int position) { - return mDataValid && mCursor != null - && mCursor.moveToPosition(position) && (mCursor.getInt(INDEX_INSTALLED) == 1); - } - - public boolean getItemIsRegistered(int position) { - return mDataValid && mCursor != null - && mCursor.moveToPosition(position) && (mCursor.getInt(INDEX_REGISTERED) == 1); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - TextView text = view.findViewById(R.id.api_apps_adapter_item_name); - ImageView icon = view.findViewById(R.id.api_apps_adapter_item_icon); - ImageView installIcon = view.findViewById(R.id.api_apps_adapter_install_icon); - - String packageName = cursor.getString(INDEX_PACKAGE_NAME); - Timber.d("packageName: " + packageName); - int installed = cursor.getInt(INDEX_INSTALLED); - String name = cursor.getString(INDEX_NAME); - int iconResName = cursor.getInt(INDEX_ICON_RES_ID); - - // get application name and icon - try { - ApplicationInfo ai = mPM.getApplicationInfo(packageName, 0); - - text.setText(mPM.getApplicationLabel(ai)); - icon.setImageDrawable(mPM.getApplicationIcon(ai)); - } catch (final PackageManager.NameNotFoundException e) { - // fallback - if (name == null) { - text.setText(packageName); - } else { - text.setText(name); - try { - icon.setImageDrawable(getResources().getDrawable(iconResName)); - } catch (Resources.NotFoundException e1) { - // silently fail - } - } - } - - if (installed == 1) { - installIcon.setVisibility(View.GONE); - } else { - installIcon.setVisibility(View.VISIBLE); - } - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.api_apps_adapter_list_item, null); - } - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java index e753654ab..66b4f2965 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteRegisterPresenter.java @@ -26,8 +26,8 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.drawable.Drawable; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.model.ApiApp; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; -import org.sufficientlysecure.keychain.remote.AppSettings; import timber.log.Timber; @@ -39,7 +39,7 @@ class RemoteRegisterPresenter { private RemoteRegisterView view; private Intent resultData; - private AppSettings appSettings; + private ApiApp apiApp; RemoteRegisterPresenter(Context context) { @@ -54,7 +54,7 @@ class RemoteRegisterPresenter { } void setupFromIntentData(Intent resultData, String packageName, byte[] packageSignature) { - this.appSettings = new AppSettings(packageName, packageSignature); + this.apiApp = ApiApp.create(packageName, packageSignature); this.resultData = resultData; try { @@ -76,7 +76,7 @@ class RemoteRegisterPresenter { } void onClickAllow() { - apiDao.insertApiApp(appSettings); + apiDao.insertApiApp(apiApp); view.finishWithResult(resultData); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java index 058b4ffac..91440e018 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java @@ -34,6 +34,7 @@ import timber.log.Timber; public class SelectSignKeyIdActivity extends BaseActivity { + public static final String EXTRA_PACKAGE_NAME = "package_name"; public static final String EXTRA_USER_ID = OpenPgpApi.EXTRA_USER_ID; public static final String EXTRA_DATA = "data"; @@ -68,19 +69,18 @@ public class SelectSignKeyIdActivity extends BaseActivity { }); Intent intent = getIntent(); - Uri appUri = intent.getData(); + String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME); mPreferredUserId = intent.getStringExtra(EXTRA_USER_ID); mData = intent.getParcelableExtra(EXTRA_DATA); - if (appUri == null) { + if (packageName == null) { Timber.e("Intent data missing. Should be Uri of app!"); finish(); } else { - Timber.d("uri: " + appUri); - startListFragments(savedInstanceState, appUri, mData, mPreferredUserId); + startListFragments(savedInstanceState, packageName, mData, mPreferredUserId); } } - private void startListFragments(Bundle savedInstanceState, Uri dataUri, Intent data, String preferredUserId) { + private void startListFragments(Bundle savedInstanceState, String packageName, Intent data, String preferredUserId) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. @@ -90,7 +90,7 @@ public class SelectSignKeyIdActivity extends BaseActivity { // Create an instance of the fragments SelectSignKeyIdListFragment listFragment = SelectSignKeyIdListFragment - .newInstance(dataUri, data, preferredUserId); + .newInstance(packageName, data, preferredUserId); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! getSupportFragmentManager().beginTransaction() diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java index 7ddbdd82e..ff5940a05 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java @@ -33,35 +33,33 @@ import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; -import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.remote.ui.adapter.SelectSignKeyAdapter; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; -import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; import org.sufficientlysecure.keychain.ui.base.RecyclerFragment; -import timber.log.Timber; +import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; public class SelectSignKeyIdListFragment extends RecyclerFragment implements SelectSignKeyAdapter.SelectSignKeyListener, LoaderManager.LoaderCallbacks { - private static final String ARG_DATA_URI = "uri"; + private static final String ARG_PACKAGE_NAME = "package_name"; private static final String ARG_PREF_UID = "pref_uid"; public static final String ARG_DATA = "data"; - private Uri mDataUri; private Intent mResult; private String mPrefUid; private ApiDataAccessObject mApiDao; + private String mPackageName; /** * Creates new instance of this fragment */ - public static SelectSignKeyIdListFragment newInstance(Uri dataUri, Intent data, String preferredUserId) { + public static SelectSignKeyIdListFragment newInstance(String packageName, Intent data, String preferredUserId) { SelectSignKeyIdListFragment frag = new SelectSignKeyIdListFragment(); Bundle args = new Bundle(); - args.putParcelable(ARG_DATA_URI, dataUri); + args.putString(ARG_PACKAGE_NAME, packageName); args.putParcelable(ARG_DATA, data); args.putString(ARG_PREF_UID, preferredUserId); @@ -85,7 +83,7 @@ public class SelectSignKeyIdListFragment extends RecyclerFragment