More experimental work on API accounts

This commit is contained in:
Dominik Schürmann 2014-03-25 19:11:20 +01:00
parent b4b709b1e0
commit aa35b1f4b5
23 changed files with 884 additions and 434 deletions

View File

@ -126,6 +126,8 @@ public class OpenPgpApi {
/* Intent extras */
public static final String EXTRA_API_VERSION = "api_version";
public static final String EXTRA_ACCOUNT_NAME = "account_name";
// SIGN, ENCRYPT, SIGN_AND_ENCRYPT, DECRYPT_VERIFY
// request ASCII Armor for output
// OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)

View File

@ -390,7 +390,7 @@
<!--android:launchMode="singleTop"-->
<!--android:process=":remote_api"-->
<activity
android:name="org.sufficientlysecure.keychain.remote.ui.RegisteredAppsListActivity"
android:name=".remote.ui.AppsListActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:exported="false"
android:label="@string/title_api_registered_apps" />

View File

@ -19,7 +19,7 @@ package org.sufficientlysecure.keychain;
import android.os.Environment;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity;
import org.sufficientlysecure.keychain.remote.ui.AppsListActivity;
import org.sufficientlysecure.keychain.ui.DecryptActivity;
import org.sufficientlysecure.keychain.ui.EncryptActivity;
import org.sufficientlysecure.keychain.ui.ImportKeysActivity;
@ -73,7 +73,7 @@ public final class Constants {
public static final Class ENCRYPT = EncryptActivity.class;
public static final Class DECRYPT = DecryptActivity.class;
public static final Class IMPORT_KEYS = ImportKeysActivity.class;
public static final Class REGISTERED_APPS_LIST = RegisteredAppsListActivity.class;
public static final Class REGISTERED_APPS_LIST = AppsListActivity.class;
public static final Class[] ARRAY = new Class[]{KEY_LIST, ENCRYPT, DECRYPT,
IMPORT_KEYS, REGISTERED_APPS_LIST};
}

View File

@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.provider;
import android.net.Uri;
import android.provider.BaseColumns;
import org.sufficientlysecure.keychain.Constants;
public class KeychainContract {
@ -59,6 +60,7 @@ public class KeychainContract {
}
interface ApiAppsAccountsColumns {
String ACCOUNT_NAME = "account_name";
String KEY_ID = "key_id"; // not a database id
String ENCRYPTION_ALGORITHM = "encryption_algorithm";
String HASH_ALORITHM = "hash_algorithm";
@ -90,8 +92,7 @@ public class KeychainContract {
public static final String PATH_USER_IDS = "user_ids";
public static final String PATH_KEYS = "keys";
public static final String BASE_API = "api";
public static final String PATH_APPS = "apps";
public static final String BASE_API_APPS = "api_apps";
public static final String PATH_ACCOUNTS = "accounts";
public static final String PATH_BY_PACKAGE_NAME = "package_name";
@ -257,36 +258,9 @@ public class KeychainContract {
}
}
/**
* Join over ApiApps with ApiAppsAccounts
*/
public static class Api implements ApiAppsColumns, ApiAppsAccountsColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_API).build();
/**
* Use if multiple items get returned
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.apis";
/**
* Use if a single item is returned
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.api";
public static Uri buildIdUri(String rowId) {
return CONTENT_URI.buildUpon().appendPath(rowId).build();
}
public static Uri buildByPackageNameUri(String packageName) {
return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName)
.build();
}
}
public static class ApiApps implements ApiAppsColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_API).appendPath(PATH_APPS).build();
.appendPath(BASE_API_APPS).build();
/**
* Use if multiple items get returned
@ -303,14 +277,14 @@ public class KeychainContract {
}
public static Uri buildByPackageNameUri(String packageName) {
return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName)
.build();
return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME)
.appendEncodedPath(packageName).build();
}
}
public static class ApiAccounts implements ApiAppsAccountsColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_API).appendPath(PATH_ACCOUNTS).build();
.appendPath(BASE_API_APPS).build();
/**
* Use if multiple items get returned
@ -322,13 +296,24 @@ public class KeychainContract {
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.api.account";
public static Uri buildIdUri(String rowId) {
return CONTENT_URI.buildUpon().appendPath(rowId).build();
// public static Uri buildUri(String rowIdApp) {
// return CONTENT_URI.buildUpon().appendPath(rowIdApp).appendPath(PATH_ACCOUNTS)
// .build();
// }
//
// public static Uri buildIdUri(String rowIdApp, String rowId) {
// return CONTENT_URI.buildUpon().appendPath(rowIdApp).appendPath(PATH_ACCOUNTS)
// .appendPath(rowId).build();
// }
public static Uri buildBaseUri(String packageName) {
return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ACCOUNTS)
.build();
}
public static Uri buildByPackageNameUri(String packageName) {
return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName)
.build();
public static Uri buildByPackageAndAccountUri(String packageName, String accountName) {
return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ACCOUNTS)
.appendEncodedPath(accountName).build();
}
}

View File

@ -82,10 +82,11 @@ public class KeychainDatabase extends SQLiteOpenHelper {
private static final String CREATE_API_APPS_ACCOUNTS = "CREATE TABLE IF NOT EXISTS " + Tables.API_ACCOUNTS
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ ApiAppsAccountsColumns.ACCOUNT_NAME + " TEXT UNIQUE, "
+ ApiAppsAccountsColumns.KEY_ID + " INT64, "
+ ApiAppsAccountsColumns.ENCRYPTION_ALGORITHM + " INTEGER, "
+ ApiAppsAccountsColumns.HASH_ALORITHM + " INTEGER, "
+ ApiAppsAccountsColumns.COMPRESSION + " INTEGER"
+ ApiAppsAccountsColumns.COMPRESSION + " INTEGER, "
+ ApiAppsAccountsColumns.PACKAGE_NAME_FK + " TEXT NOT NULL, FOREIGN KEY("
+ ApiAppsAccountsColumns.PACKAGE_NAME_FK + ") REFERENCES " + Tables.API_APPS + "("
+ ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE)";

View File

@ -30,10 +30,8 @@ import android.provider.BaseColumns;
import android.text.TextUtils;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.KeychainContract.Api;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAccountsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;
@ -81,15 +79,12 @@ public class KeychainProvider extends ContentProvider {
private static final int SECRET_KEY_RING_USER_ID = 221;
private static final int SECRET_KEY_RING_USER_ID_BY_ROW_ID = 222;
private static final int API = 301;
private static final int API_BY_ROW_ID = 302;
private static final int API_BY_PACKAGE_NAME = 303;
private static final int API_APPS = 304;
private static final int API_APPS_BY_ROW_ID = 305;
private static final int API_APPS_BY_PACKAGE_NAME = 306;
private static final int API_ACCOUNTS = 307;
private static final int API_ACCOUNTS_BY_ROW_ID = 308;
private static final int API_ACCOUNTS_BY_PACKAGE_NAME = 309;
private static final int API_APPS = 301;
private static final int API_APPS_BY_ROW_ID = 302;
private static final int API_APPS_BY_PACKAGE_NAME = 303;
private static final int API_ACCOUNTS = 304;
private static final int API_ACCOUNTS_BY_ROW_ID = 305;
private static final int API_ACCOUNTS_BY_ACCOUNT_NAME = 306;
private static final int UNIFIED_KEY_RING = 401;
@ -247,26 +242,18 @@ public class KeychainProvider extends ContentProvider {
/**
* API apps
*/
matcher.addURI(authority, KeychainContract.BASE_API, API);
matcher.addURI(authority, KeychainContract.BASE_API + "/#", API_BY_ROW_ID);
matcher.addURI(authority, KeychainContract.BASE_API + "/"
+ KeychainContract.PATH_BY_PACKAGE_NAME + "/*", API_BY_PACKAGE_NAME);
matcher.addURI(authority, KeychainContract.BASE_API + "/"
+ KeychainContract.PATH_APPS, API_APPS);
matcher.addURI(authority, KeychainContract.BASE_API + "/"
+ KeychainContract.PATH_APPS + "/#", API_APPS_BY_ROW_ID);
matcher.addURI(authority, KeychainContract.BASE_API + "/"
+ KeychainContract.PATH_APPS + "/"
matcher.addURI(authority, KeychainContract.BASE_API_APPS, API_APPS);
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/#", API_APPS_BY_ROW_ID);
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/"
+ KeychainContract.PATH_BY_PACKAGE_NAME + "/*", API_APPS_BY_PACKAGE_NAME);
matcher.addURI(authority, KeychainContract.BASE_API + "/"
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/"
+ KeychainContract.PATH_ACCOUNTS, API_ACCOUNTS);
matcher.addURI(authority, KeychainContract.BASE_API + "/"
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/"
+ KeychainContract.PATH_ACCOUNTS + "/#", API_ACCOUNTS_BY_ROW_ID);
matcher.addURI(authority, KeychainContract.BASE_API + "/"
matcher.addURI(authority, KeychainContract.BASE_API_APPS + "/"
+ KeychainContract.PATH_ACCOUNTS + "/"
+ KeychainContract.PATH_BY_PACKAGE_NAME + "/*", API_ACCOUNTS_BY_PACKAGE_NAME);
+ KeychainContract.PATH_BY_PACKAGE_NAME + "/*", API_ACCOUNTS_BY_ACCOUNT_NAME);
/**
* data stream
@ -332,13 +319,6 @@ public class KeychainProvider extends ContentProvider {
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
return UserIds.CONTENT_ITEM_TYPE;
case API:
return Api.CONTENT_TYPE;
case API_BY_ROW_ID:
case API_BY_PACKAGE_NAME:
return Api.CONTENT_ITEM_TYPE;
case API_APPS:
return ApiApps.CONTENT_TYPE;
@ -350,7 +330,7 @@ public class KeychainProvider extends ContentProvider {
return ApiAccounts.CONTENT_TYPE;
case API_ACCOUNTS_BY_ROW_ID:
case API_ACCOUNTS_BY_PACKAGE_NAME:
case API_ACCOUNTS_BY_ACCOUNT_NAME:
return ApiAccounts.CONTENT_ITEM_TYPE;
default:
@ -695,28 +675,6 @@ public class KeychainProvider extends ContentProvider {
qb.appendWhere(" AND " + BaseColumns._ID + " = ");
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
case API:
qb.setTables(Tables.API_ACCOUNTS + " INNER JOIN " + Tables.API_APPS + " ON " + "("
+ Tables.API_APPS + "." + ApiApps.PACKAGE_NAME + " = " + Tables.API_ACCOUNTS + "."
+ ApiAccounts.PACKAGE_NAME_FK + " )");
break;
case API_BY_ROW_ID:
qb.setTables(Tables.API_ACCOUNTS + " INNER JOIN " + Tables.API_APPS + " ON " + "("
+ Tables.API_APPS + "." + ApiApps.PACKAGE_NAME + " = " + Tables.API_ACCOUNTS + "."
+ ApiAccounts.PACKAGE_NAME_FK + " )");
qb.appendWhere(Tables.API_APPS + "." + BaseColumns._ID + " = ");
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
case API_BY_PACKAGE_NAME:
qb.setTables(Tables.API_ACCOUNTS + " INNER JOIN " + Tables.API_APPS + " ON " + "("
+ Tables.API_APPS + "." + ApiApps.PACKAGE_NAME + " = " + Tables.API_ACCOUNTS + "."
+ ApiAccounts.PACKAGE_NAME_FK + " )");
qb.appendWhere(Tables.API_APPS + "." + ApiApps.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
case API_APPS:
qb.setTables(Tables.API_APPS);
@ -740,15 +698,24 @@ public class KeychainProvider extends ContentProvider {
break;
case API_ACCOUNTS_BY_ROW_ID:
qb.setTables(Tables.API_ACCOUNTS);
qb.setTables(Tables.API_ACCOUNTS + " INNER JOIN " + Tables.API_APPS + " ON " + "("
+ Tables.API_APPS + "." + ApiApps.PACKAGE_NAME + " = " + Tables.API_ACCOUNTS + "."
+ ApiAccounts.PACKAGE_NAME_FK + " )");
qb.appendWhere(Tables.API_APPS + "." + BaseColumns._ID + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(2));
qb.appendWhere(BaseColumns._ID + " = ");
qb.appendWhere(" AND " + Tables.API_ACCOUNTS + "." + BaseColumns._ID + " = ");
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
case API_ACCOUNTS_BY_PACKAGE_NAME:
qb.setTables(Tables.API_ACCOUNTS);
qb.appendWhere(ApiAppsAccountsColumns.PACKAGE_NAME_FK + " = ");
case API_ACCOUNTS_BY_ACCOUNT_NAME:
qb.setTables(Tables.API_ACCOUNTS + " INNER JOIN " + Tables.API_APPS + " ON " + "("
+ Tables.API_APPS + "." + ApiApps.PACKAGE_NAME + " = " + Tables.API_ACCOUNTS + "."
+ ApiAccounts.PACKAGE_NAME_FK + " )");
qb.appendWhere(Tables.API_APPS + "." + ApiApps.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(2));
qb.appendWhere(" AND " + Tables.API_ACCOUNTS + "." + ApiAccounts.ACCOUNT_NAME + " = ");
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
@ -808,13 +775,15 @@ public class KeychainProvider extends ContentProvider {
values.put(Keys.TYPE, KeyTypes.PUBLIC);
rowId = db.insertOrThrow(Tables.KEYS, null, values);
rowUri = Keys.buildPublicKeysUri(Long.toString(rowId));
// TODO: this is wrong:
// rowUri = Keys.buildPublicKeysUri(Long.toString(rowId));
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break;
case PUBLIC_KEY_RING_USER_ID:
rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
rowUri = UserIds.buildPublicUserIdsUri(Long.toString(rowId));
// TODO: this is wrong:
// rowUri = UserIds.buildPublicUserIdsUri(Long.toString(rowId));
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break;
@ -830,13 +799,15 @@ public class KeychainProvider extends ContentProvider {
values.put(Keys.TYPE, KeyTypes.SECRET);
rowId = db.insertOrThrow(Tables.KEYS, null, values);
rowUri = Keys.buildSecretKeysUri(Long.toString(rowId));
// TODO: this is wrong:
// rowUri = Keys.buildSecretKeysUri(Long.toString(rowId));
sendBroadcastDatabaseChange(getKeyType(match), getType(uri));
break;
case SECRET_KEY_RING_USER_ID:
rowId = db.insertOrThrow(Tables.USER_IDS, null, values);
rowUri = UserIds.buildSecretUserIdsUri(Long.toString(rowId));
// TODO: this is wrong:
// rowUri = UserIds.buildSecretUserIdsUri(Long.toString(rowId));
break;
case API_APPS:
@ -846,7 +817,8 @@ public class KeychainProvider extends ContentProvider {
break;
case API_ACCOUNTS:
rowId = db.insertOrThrow(Tables.API_ACCOUNTS, null, values);
rowUri = ApiAccounts.buildIdUri(Long.toString(rowId));
// TODO: this is wrong:
// rowUri = ApiAccounts.buildIdUri(Long.toString(rowId));
break;
default:
@ -918,7 +890,7 @@ public class KeychainProvider extends ContentProvider {
count = db.delete(Tables.API_ACCOUNTS, buildDefaultApiAccountsSelection(uri, false, selection),
selectionArgs);
break;
case API_ACCOUNTS_BY_PACKAGE_NAME:
case API_ACCOUNTS_BY_ACCOUNT_NAME:
count = db.delete(Tables.API_ACCOUNTS, buildDefaultApiAccountsSelection(uri, true, selection),
selectionArgs);
break;
@ -996,7 +968,7 @@ public class KeychainProvider extends ContentProvider {
count = db.update(Tables.API_ACCOUNTS, values,
buildDefaultApiAccountsSelection(uri, false, selection), selectionArgs);
break;
case API_ACCOUNTS_BY_PACKAGE_NAME:
case API_ACCOUNTS_BY_ACCOUNT_NAME:
count = db.update(Tables.API_ACCOUNTS, values,
buildDefaultApiAccountsSelection(uri, true, selection), selectionArgs);
break;

View File

@ -22,6 +22,7 @@ import android.database.Cursor;
import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.RemoteException;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.openpgp.*;
import org.sufficientlysecure.keychain.Constants;
@ -33,6 +34,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
@ -801,70 +803,93 @@ public class ProviderHelper {
ContentValues values = new ContentValues();
values.put(ApiApps.PACKAGE_NAME, appSettings.getPackageName());
values.put(ApiApps.PACKAGE_SIGNATURE, appSettings.getPackageSignature());
// values.put(ApiApps.KEY_ID, appSettings.getKeyId());
// values.put(ApiApps.COMPRESSION, appSettings.getCompression());
// values.put(ApiApps.ENCRYPTION_ALGORITHM, appSettings.getEncryptionAlgorithm());
// values.put(ApiApps.HASH_ALORITHM, appSettings.getHashAlgorithm());
return values;
}
private static ContentValues contentValueForApiAccounts(AppSettings appSettings) {
private static ContentValues contentValueForApiAccounts(AccountSettings accSettings) {
ContentValues values = new ContentValues();
values.put(KeychainContract.ApiAccounts.PACKAGE_NAME_FK, appSettings.getPackageName());
values.put(KeychainContract.ApiAccounts.KEY_ID, appSettings.getKeyId());
values.put(KeychainContract.ApiAccounts.COMPRESSION, appSettings.getCompression());
values.put(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM, appSettings.getEncryptionAlgorithm());
values.put(KeychainContract.ApiAccounts.HASH_ALORITHM, appSettings.getHashAlgorithm());
values.put(KeychainContract.ApiAccounts.ACCOUNT_NAME, accSettings.getAccountName());
values.put(KeychainContract.ApiAccounts.KEY_ID, accSettings.getKeyId());
values.put(KeychainContract.ApiAccounts.COMPRESSION, accSettings.getCompression());
values.put(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM, accSettings.getEncryptionAlgorithm());
values.put(KeychainContract.ApiAccounts.HASH_ALORITHM, accSettings.getHashAlgorithm());
// values.put(KeychainContract.ApiAccounts.PACKAGE_NAME_FK, accSettings.getPackageName());
return values;
}
public static void insertApi(Context context, AppSettings appSettings) {
public static void insertApiApp(Context context, AppSettings appSettings) {
context.getContentResolver().insert(KeychainContract.ApiApps.CONTENT_URI,
contentValueForApiApps(appSettings));
context.getContentResolver().insert(KeychainContract.ApiAccounts.CONTENT_URI,
contentValueForApiApps(appSettings));
}
// TODO: uri not working because it is used for both tables
public static void insertApiAccount(Context context, Uri uri, AccountSettings accSettings) {
context.getContentResolver().insert(uri,
contentValueForApiAccounts(accSettings));
}
public static void updateApiApp(Context context, AppSettings appSettings, Uri uri) {
if (context.getContentResolver().update(uri, contentValueForApiApps(appSettings), null,
null) <= 0) {
throw new RuntimeException();
}
if (context.getContentResolver().update(uri, contentValueForApiAccounts(appSettings), null,
}
public static void updateApiAccount(Context context, AccountSettings accSettings, Uri uri) {
if (context.getContentResolver().update(uri, contentValueForApiAccounts(accSettings), null,
null) <= 0) {
throw new RuntimeException();
}
}
public static AppSettings getApiSettings(Context context, Uri uri) {
/**
* Must be an uri pointing to an account
*
* @param context
* @param uri
* @return
*/
public static AppSettings getApiAppSettings(Context context, Uri uri) {
AppSettings settings = null;
Cursor cur = context.getContentResolver().query(uri, null, null, null, null);
if (cur != null && cur.moveToFirst()) {
settings = new AppSettings();
settings.setPackageName(cur.getString(cur
.getColumnIndex(KeychainContract.Api.PACKAGE_NAME)));
settings.setPackageSignature(cur.getBlob(cur
.getColumnIndex(KeychainContract.Api.PACKAGE_SIGNATURE)));
settings.setKeyId(cur.getLong(cur.getColumnIndex(KeychainContract.Api.KEY_ID)));
settings.setCompression(cur.getInt(cur
.getColumnIndexOrThrow(KeychainContract.Api.COMPRESSION)));
settings.setHashAlgorithm(cur.getInt(cur
.getColumnIndexOrThrow(KeychainContract.Api.HASH_ALORITHM)));
settings.setEncryptionAlgorithm(cur.getInt(cur
.getColumnIndexOrThrow(KeychainContract.Api.ENCRYPTION_ALGORITHM)));
settings.setPackageName(cur.getString(
cur.getColumnIndex(KeychainContract.ApiApps.PACKAGE_NAME)));
settings.setPackageSignature(cur.getBlob(
cur.getColumnIndex(KeychainContract.ApiApps.PACKAGE_SIGNATURE)));
}
return settings;
}
public static AccountSettings getApiAccountSettings(Context context, Uri uri) {
AccountSettings settings = null;
Cursor cur = context.getContentResolver().query(uri, null, null, null, null);
if (cur != null && cur.moveToFirst()) {
settings = new AccountSettings();
settings.setAccountName(cur.getString(
cur.getColumnIndex(KeychainContract.ApiAccounts.ACCOUNT_NAME)));
settings.setKeyId(cur.getLong(
cur.getColumnIndex(KeychainContract.ApiAccounts.KEY_ID)));
settings.setCompression(cur.getInt(
cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.COMPRESSION)));
settings.setHashAlgorithm(cur.getInt(
cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.HASH_ALORITHM)));
settings.setEncryptionAlgorithm(cur.getInt(
cur.getColumnIndexOrThrow(KeychainContract.ApiAccounts.ENCRYPTION_ALGORITHM)));
}
return settings;
}
public static byte[] getApiSignature(Context context, String packageName) {
Uri queryUri = KeychainContract.Api.buildByPackageNameUri(packageName);
Uri queryUri = ApiApps.buildByPackageNameUri(packageName);
String[] projection = new String[]{KeychainContract.Api.PACKAGE_SIGNATURE};
String[] projection = new String[]{ApiApps.PACKAGE_SIGNATURE};
ContentResolver cr = context.getContentResolver();
Cursor cursor = cr.query(queryUri, projection, null, null, null);

View File

@ -0,0 +1,85 @@
/*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.remote;
import org.spongycastle.bcpg.HashAlgorithmTags;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.sufficientlysecure.keychain.Id;
public class AccountSettings {
private String mAccountName;
private long mKeyId = Id.key.none;
private int mEncryptionAlgorithm;
private int mHashAlgorithm;
private int mCompression;
public AccountSettings() {
}
public AccountSettings(String accountName) {
super();
this.mAccountName = accountName;
// defaults:
this.mEncryptionAlgorithm = PGPEncryptedData.AES_256;
this.mHashAlgorithm = HashAlgorithmTags.SHA512;
this.mCompression = Id.choice.compression.zlib;
}
public String getAccountName() {
return mAccountName;
}
public void setAccountName(String mAccountName) {
this.mAccountName = mAccountName;
}
public long getKeyId() {
return mKeyId;
}
public void setKeyId(long scretKeyId) {
this.mKeyId = scretKeyId;
}
public int getEncryptionAlgorithm() {
return mEncryptionAlgorithm;
}
public void setEncryptionAlgorithm(int encryptionAlgorithm) {
this.mEncryptionAlgorithm = encryptionAlgorithm;
}
public int getHashAlgorithm() {
return mHashAlgorithm;
}
public void setHashAlgorithm(int hashAlgorithm) {
this.mHashAlgorithm = hashAlgorithm;
}
public int getCompression() {
return mCompression;
}
public void setCompression(int compression) {
this.mCompression = compression;
}
}

View File

@ -24,10 +24,6 @@ import org.sufficientlysecure.keychain.Id;
public class AppSettings {
private String mPackageName;
private byte[] mPackageSignature;
private long mKeyId = Id.key.none;
private int mEncryptionAlgorithm;
private int mHashAlgorithm;
private int mCompression;
public AppSettings() {
@ -37,10 +33,6 @@ public class AppSettings {
super();
this.mPackageName = packageName;
this.mPackageSignature = packageSignature;
// defaults:
this.mEncryptionAlgorithm = PGPEncryptedData.AES_256;
this.mHashAlgorithm = HashAlgorithmTags.SHA512;
this.mCompression = Id.choice.compression.zlib;
}
public String getPackageName() {
@ -59,36 +51,4 @@ public class AppSettings {
this.mPackageSignature = packageSignature;
}
public long getKeyId() {
return mKeyId;
}
public void setKeyId(long scretKeyId) {
this.mKeyId = scretKeyId;
}
public int getEncryptionAlgorithm() {
return mEncryptionAlgorithm;
}
public void setEncryptionAlgorithm(int encryptionAlgorithm) {
this.mEncryptionAlgorithm = encryptionAlgorithm;
}
public int getHashAlgorithm() {
return mHashAlgorithm;
}
public void setHashAlgorithm(int hashAlgorithm) {
this.mHashAlgorithm = hashAlgorithm;
}
public int getCompression() {
return mCompression;
}
public void setCompression(int compression) {
this.mCompression = compression;
}
}

View File

@ -138,7 +138,7 @@ public class OpenPgpService extends RemoteService {
}
private Intent signImpl(Intent data, ParcelFileDescriptor input,
ParcelFileDescriptor output, AppSettings appSettings) {
ParcelFileDescriptor output, AccountSettings accSettings) {
try {
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
@ -147,11 +147,11 @@ public class OpenPgpService extends RemoteService {
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
} else {
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), appSettings.getKeyId());
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), accSettings.getKeyId());
}
if (passphrase == null) {
// get PendingIntent for passphrase input, add it to given params and return to client
Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId());
Intent passphraseBundle = getPassphraseBundleIntent(data, accSettings.getKeyId());
return passphraseBundle;
}
@ -165,9 +165,9 @@ public class OpenPgpService extends RemoteService {
// sign-only
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
builder.enableAsciiArmorOutput(asciiArmor)
.signatureHashAlgorithm(appSettings.getHashAlgorithm())
.signatureHashAlgorithm(accSettings.getHashAlgorithm())
.signatureForceV3(false)
.signatureKeyId(appSettings.getKeyId())
.signatureKeyId(accSettings.getKeyId())
.signaturePassphrase(passphrase);
builder.build().execute();
} finally {
@ -188,7 +188,7 @@ public class OpenPgpService extends RemoteService {
}
private Intent encryptAndSignImpl(Intent data, ParcelFileDescriptor input,
ParcelFileDescriptor output, AppSettings appSettings, boolean sign) {
ParcelFileDescriptor output, AccountSettings accSettings, boolean sign) {
try {
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
@ -218,7 +218,7 @@ public class OpenPgpService extends RemoteService {
// add own key for encryption
keyIds = Arrays.copyOf(keyIds, keyIds.length + 1);
keyIds[keyIds.length - 1] = appSettings.getKeyId();
keyIds[keyIds.length - 1] = accSettings.getKeyId();
// build InputData and write into OutputStream
// Get Input- and OutputStream from ParcelFileDescriptor
@ -230,8 +230,8 @@ public class OpenPgpService extends RemoteService {
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
builder.enableAsciiArmorOutput(asciiArmor)
.compressionId(appSettings.getCompression())
.symmetricEncryptionAlgorithm(appSettings.getEncryptionAlgorithm())
.compressionId(accSettings.getCompression())
.symmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm())
.encryptionKeyIds(keyIds);
if (sign) {
@ -240,18 +240,18 @@ public class OpenPgpService extends RemoteService {
passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
} else {
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(),
appSettings.getKeyId());
accSettings.getKeyId());
}
if (passphrase == null) {
// get PendingIntent for passphrase input, add it to given params and return to client
Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId());
Intent passphraseBundle = getPassphraseBundleIntent(data, accSettings.getKeyId());
return passphraseBundle;
}
// sign and encrypt
builder.signatureHashAlgorithm(appSettings.getHashAlgorithm())
builder.signatureHashAlgorithm(accSettings.getHashAlgorithm())
.signatureForceV3(false)
.signatureKeyId(appSettings.getKeyId())
.signatureKeyId(accSettings.getKeyId())
.signaturePassphrase(passphrase);
} else {
// encrypt only
@ -277,7 +277,7 @@ public class OpenPgpService extends RemoteService {
}
private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor input,
ParcelFileDescriptor output, AppSettings appSettings) {
ParcelFileDescriptor output, AccountSettings accSettings) {
try {
// Get Input- and OutputStream from ParcelFileDescriptor
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
@ -293,7 +293,7 @@ public class OpenPgpService extends RemoteService {
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, os);
builder.assumeSymmetric(false) // no support for symmetric encryption
// allow only the private key for this app for decryption
.enforcedKeyId(appSettings.getKeyId())
.enforcedKeyId(accSettings.getKeyId())
.passphrase(passphrase);
// TODO: currently does not support binary signed-only content
@ -301,7 +301,7 @@ public class OpenPgpService extends RemoteService {
if (decryptVerifyResult.isKeyPassphraseNeeded()) {
// get PendingIntent for passphrase input, add it to given params and return to client
Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId());
Intent passphraseBundle = getPassphraseBundleIntent(data, accSettings.getKeyId());
return passphraseBundle;
} else if (decryptVerifyResult.isSymmetricPassphraseNeeded()) {
throw new PgpGeneralException("Decryption of symmetric content not supported by API!");
@ -433,17 +433,23 @@ public class OpenPgpService extends RemoteService {
return errorResult;
}
final AppSettings appSettings = getAppSettings();
String accName;
if (data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME) != null) {
accName = data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME);
} else {
accName = "default";
}
final AccountSettings accSettings = getAccSettings(accName);
String action = data.getAction();
if (OpenPgpApi.ACTION_SIGN.equals(action)) {
return signImpl(data, input, output, appSettings);
return signImpl(data, input, output, accSettings);
} else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) {
return encryptAndSignImpl(data, input, output, appSettings, false);
return encryptAndSignImpl(data, input, output, accSettings, false);
} else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) {
return encryptAndSignImpl(data, input, output, appSettings, true);
return encryptAndSignImpl(data, input, output, accSettings, true);
} else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) {
return decryptAndVerifyImpl(data, input, output, appSettings);
return decryptAndVerifyImpl(data, input, output, accSettings);
} else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) {
return getKeyImpl(data);
} else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) {

View File

@ -130,16 +130,16 @@ public abstract class RemoteService extends Service {
*
* @return
*/
protected AppSettings getAppSettings() {
protected AccountSettings getAccSettings(String accountName) {
String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
// get app settings for this package
for (int i = 0; i < callingPackages.length; i++) {
String currentPkg = callingPackages[i];
Uri uri = KeychainContract.ApiApps.buildByPackageNameUri(currentPkg);
Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName);
AppSettings settings = ProviderHelper.getApiAppSettings(this, uri);
AccountSettings settings = ProviderHelper.getApiAccountSettings(this, uri);
if (settings != null) {
return settings;
@ -210,7 +210,7 @@ public abstract class RemoteService extends Service {
throw new WrongPackageSignatureException(e.getMessage());
}
byte[] storedSig = ProviderHelper.getApiAppSignature(this, packageName);
byte[] storedSig = ProviderHelper.getApiSignature(this, packageName);
if (Arrays.equals(currentSig, storedSig)) {
Log.d(Constants.TAG,
"Package signature is correct! (equals signature from database)");

View File

@ -0,0 +1,203 @@
/*
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment;
import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter;
import org.sufficientlysecure.keychain.util.AlgorithmNames;
import org.sufficientlysecure.keychain.util.Log;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class AccountSettingsFragment extends Fragment implements
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback {
// model
private AccountSettings mAccSettings;
// view
private TextView mAppNameView;
private ImageView mAppIconView;
private Spinner mEncryptionAlgorithm;
private Spinner mHashAlgorithm;
private Spinner mCompression;
private TextView mPackageName;
private TextView mPackageSignature;
private SelectSecretKeyLayoutFragment mSelectKeyFragment;
KeyValueSpinnerAdapter mEncryptionAdapter;
KeyValueSpinnerAdapter mHashAdapter;
KeyValueSpinnerAdapter mCompressionAdapter;
public AccountSettings getAccSettings() {
return mAccSettings;
}
public void setAccSettings(AccountSettings appSettings) {
this.mAccSettings = appSettings;
// setPackage(appSettings.getPackageName());
// mPackageName.setText(appSettings.getPackageName());
// try {
// MessageDigest md = MessageDigest.getInstance("SHA-256");
// md.update(appSettings.getPackageSignature());
// byte[] digest = md.digest();
// String signature = new String(Hex.encode(digest));
//
// mPackageSignature.setText(signature);
// } catch (NoSuchAlgorithmException e) {
// Log.e(Constants.TAG, "Should not happen!", e);
// }
mSelectKeyFragment.selectKey(appSettings.getKeyId());
mEncryptionAlgorithm.setSelection(mEncryptionAdapter.getPosition(appSettings
.getEncryptionAlgorithm()));
mHashAlgorithm.setSelection(mHashAdapter.getPosition(appSettings.getHashAlgorithm()));
mCompression.setSelection(mCompressionAdapter.getPosition(appSettings.getCompression()));
}
/**
* Inflate the layout for this fragment
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.api_account_settings_fragment, container, false);
initView(view);
return view;
}
/**
* Set error String on key selection
*
* @param error
*/
public void setErrorOnSelectKeyFragment(String error) {
mSelectKeyFragment.setError(error);
}
private void initView(View view) {
mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById(
R.id.api_account_settings_select_key_fragment);
mSelectKeyFragment.setCallback(this);
mAppNameView = (TextView) view.findViewById(R.id.api_account_settings_app_name);
mAppIconView = (ImageView) view.findViewById(R.id.api_account_settings_app_icon);
mEncryptionAlgorithm = (Spinner) view
.findViewById(R.id.api_account_settings_encryption_algorithm);
mHashAlgorithm = (Spinner) view.findViewById(R.id.api_account_settings_hash_algorithm);
mCompression = (Spinner) view.findViewById(R.id.api_account_settings_compression);
mPackageName = (TextView) view.findViewById(R.id.api_account_settings_package_name);
mPackageSignature = (TextView) view.findViewById(R.id.api_account_settings_package_signature);
AlgorithmNames algorithmNames = new AlgorithmNames(getActivity());
mEncryptionAdapter = new KeyValueSpinnerAdapter(getActivity(),
algorithmNames.getEncryptionNames());
mEncryptionAlgorithm.setAdapter(mEncryptionAdapter);
mEncryptionAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mAccSettings.setEncryptionAlgorithm((int) id);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
mHashAdapter = new KeyValueSpinnerAdapter(getActivity(), algorithmNames.getHashNames());
mHashAlgorithm.setAdapter(mHashAdapter);
mHashAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mAccSettings.setHashAlgorithm((int) id);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
mCompressionAdapter = new KeyValueSpinnerAdapter(getActivity(),
algorithmNames.getCompressionNames());
mCompression.setAdapter(mCompressionAdapter);
mCompression.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mAccSettings.setCompression((int) id);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
//
// private void setPackage(String packageName) {
// PackageManager pm = getActivity().getApplicationContext().getPackageManager();
//
// // get application name and icon from package manager
// String appName = null;
// Drawable appIcon = null;
// try {
// ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
//
// appName = (String) pm.getApplicationLabel(ai);
// appIcon = pm.getApplicationIcon(ai);
// } catch (final NameNotFoundException e) {
// // fallback
// appName = packageName;
// }
// mAppNameView.setText(appName);
// mAppIconView.setImageDrawable(appIcon);
// }
/**
* callback from select secret key fragment
*/
@Override
public void onKeySelected(long secretKeyId) {
mAccSettings.setKeyId(secretKeyId);
}
}

View File

@ -0,0 +1,174 @@
/*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.remote.ui;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
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.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.SimpleCursorAdapter;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
// TODO: make compat with < 11
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class AccountsListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
private String mPackageName;
public String getPackageName() {
return mPackageName;
}
public void setPackageName(String packageName) {
this.mPackageName = packageName;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getListView().setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
// edit app settings
Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
intent.setData(ContentUris.withAppendedId(ApiApps.CONTENT_URI, id));
startActivity(intent);
}
});
// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText(getString(R.string.api_no_apps));
// 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 SimpleCursorAdapter(getActivity(),
android.R.layout.simple_list_item_1,
null,
new String[]{KeychainContract.ApiAccounts.ACCOUNT_NAME},
new int[]{android.R.id.text1},
0);
setListAdapter(mAdapter);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
// These are the Contacts rows that we will retrieve.
static final String[] PROJECTION = new String[]{
KeychainContract.ApiAccounts._ID,
KeychainContract.ApiAccounts.ACCOUNT_NAME};
public Loader<Cursor> 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 = KeychainContract.ApiAccounts.buildBaseUri(mPackageName);
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null,
KeychainContract.ApiAccounts.ACCOUNT_NAME + " COLLATE LOCALIZED ASC");
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
}
public void onLoaderReset(Loader<Cursor> 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);
}
// private class RegisteredAppsAdapter extends CursorAdapter {
//
// private LayoutInflater mInflater;
// private PackageManager mPM;
//
// public RegisteredAppsAdapter(Context context, Cursor c, int flags) {
// super(context, c, flags);
//
// mInflater = LayoutInflater.from(context);
// mPM = context.getApplicationContext().getPackageManager();
// }
//
// @Override
// public void bindView(View view, Context context, Cursor cursor) {
// TextView text = (TextView) view.findViewById(R.id.api_apps_adapter_item_name);
// ImageView icon = (ImageView) view.findViewById(R.id.api_apps_adapter_item_icon);
//
// String packageName = cursor.getString(cursor.getColumnIndex(ApiApps.PACKAGE_NAME));
// if (packageName != null) {
// // get application name
// try {
// ApplicationInfo ai = mPM.getApplicationInfo(packageName, 0);
//
// text.setText(mPM.getApplicationLabel(ai));
// icon.setImageDrawable(mPM.getApplicationIcon(ai));
// } catch (final PackageManager.NameNotFoundException e) {
// // fallback
// text.setText(packageName);
// }
// } else {
// // fallback
// text.setText(packageName);
// }
//
// }
//
// @Override
// public View newView(Context context, Cursor cursor, ViewGroup parent) {
// return mInflater.inflate(R.layout.api_apps_adapter_list_item, null);
// }
// }
}

View File

@ -44,8 +44,7 @@ import org.sufficientlysecure.keychain.util.Log;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class AppSettingsFragment extends Fragment implements
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback {
public class AppSettingsFragment extends Fragment {
// model
private AppSettings mAppSettings;
@ -53,18 +52,9 @@ public class AppSettingsFragment extends Fragment implements
// view
private TextView mAppNameView;
private ImageView mAppIconView;
private Spinner mEncryptionAlgorithm;
private Spinner mHashAlgorithm;
private Spinner mCompression;
private TextView mPackageName;
private TextView mPackageSignature;
private SelectSecretKeyLayoutFragment mSelectKeyFragment;
KeyValueSpinnerAdapter mEncryptionAdapter;
KeyValueSpinnerAdapter mHashAdapter;
KeyValueSpinnerAdapter mCompressionAdapter;
public AppSettings getAppSettings() {
return mAppSettings;
}
@ -85,11 +75,6 @@ public class AppSettingsFragment extends Fragment implements
Log.e(Constants.TAG, "Should not happen!", e);
}
mSelectKeyFragment.selectKey(appSettings.getKeyId());
mEncryptionAlgorithm.setSelection(mEncryptionAdapter.getPosition(appSettings
.getEncryptionAlgorithm()));
mHashAlgorithm.setSelection(mHashAdapter.getPosition(appSettings.getHashAlgorithm()));
mCompression.setSelection(mCompressionAdapter.getPosition(appSettings.getCompression()));
}
/**
@ -102,74 +87,13 @@ public class AppSettingsFragment extends Fragment implements
return view;
}
/**
* Set error String on key selection
*
* @param error
*/
public void setErrorOnSelectKeyFragment(String error) {
mSelectKeyFragment.setError(error);
}
private void initView(View view) {
mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById(
R.id.api_app_settings_select_key_fragment);
mSelectKeyFragment.setCallback(this);
mAppNameView = (TextView) view.findViewById(R.id.api_app_settings_app_name);
mAppIconView = (ImageView) view.findViewById(R.id.api_app_settings_app_icon);
mEncryptionAlgorithm = (Spinner) view
.findViewById(R.id.api_app_settings_encryption_algorithm);
mHashAlgorithm = (Spinner) view.findViewById(R.id.api_app_settings_hash_algorithm);
mCompression = (Spinner) view.findViewById(R.id.api_app_settings_compression);
mPackageName = (TextView) view.findViewById(R.id.api_app_settings_package_name);
mPackageSignature = (TextView) view.findViewById(R.id.api_app_settings_package_signature);
AlgorithmNames algorithmNames = new AlgorithmNames(getActivity());
mEncryptionAdapter = new KeyValueSpinnerAdapter(getActivity(),
algorithmNames.getEncryptionNames());
mEncryptionAlgorithm.setAdapter(mEncryptionAdapter);
mEncryptionAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mAppSettings.setEncryptionAlgorithm((int) id);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
mHashAdapter = new KeyValueSpinnerAdapter(getActivity(), algorithmNames.getHashNames());
mHashAlgorithm.setAdapter(mHashAdapter);
mHashAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mAppSettings.setHashAlgorithm((int) id);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
mCompressionAdapter = new KeyValueSpinnerAdapter(getActivity(),
algorithmNames.getCompressionNames());
mCompression.setAdapter(mCompressionAdapter);
mCompression.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mAppSettings.setCompression((int) id);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
private void setPackage(String packageName) {
@ -191,12 +115,5 @@ public class AppSettingsFragment extends Fragment implements
mAppIconView.setImageDrawable(appIcon);
}
/**
* callback from select secret key fragment
*/
@Override
public void onKeySelected(long secretKeyId) {
mAppSettings.setKeyId(secretKeyId);
}
}

View File

@ -21,7 +21,7 @@ import android.os.Bundle;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.DrawerActivity;
public class RegisteredAppsListActivity extends DrawerActivity {
public class AppsListActivity extends DrawerActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {

View File

@ -18,7 +18,10 @@
package org.sufficientlysecure.keychain.remote.ui;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
@ -26,14 +29,20 @@ 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.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;
public class RegisteredAppsListFragment extends ListFragment implements
public class AppsListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
// This is the Adapter being used to display the list's data.
@ -98,4 +107,46 @@ public class RegisteredAppsListFragment extends ListFragment implements
mAdapter.swapCursor(null);
}
private class RegisteredAppsAdapter extends CursorAdapter {
private LayoutInflater mInflater;
private PackageManager mPM;
public RegisteredAppsAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
mInflater = LayoutInflater.from(context);
mPM = context.getApplicationContext().getPackageManager();
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView text = (TextView) view.findViewById(R.id.api_apps_adapter_item_name);
ImageView icon = (ImageView) view.findViewById(R.id.api_apps_adapter_item_icon);
String packageName = cursor.getString(cursor.getColumnIndex(ApiApps.PACKAGE_NAME));
if (packageName != null) {
// get application name
try {
ApplicationInfo ai = mPM.getApplicationInfo(packageName, 0);
text.setText(mPM.getApplicationLabel(ai));
icon.setImageDrawable(mPM.getApplicationIcon(ai));
} catch (final PackageManager.NameNotFoundException e) {
// fallback
text.setText(packageName);
}
} else {
// fallback
text.setText(packageName);
}
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(R.layout.api_apps_adapter_list_item, null);
}
}
}

View File

@ -1,75 +0,0 @@
/*
* Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.remote.ui;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
public class RegisteredAppsAdapter extends CursorAdapter {
private LayoutInflater mInflater;
private PackageManager mPM;
public RegisteredAppsAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
mInflater = LayoutInflater.from(context);
mPM = context.getApplicationContext().getPackageManager();
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView text = (TextView) view.findViewById(R.id.api_apps_adapter_item_name);
ImageView icon = (ImageView) view.findViewById(R.id.api_apps_adapter_item_icon);
String packageName = cursor.getString(cursor.getColumnIndex(ApiApps.PACKAGE_NAME));
if (packageName != null) {
// get application name
try {
ApplicationInfo ai = mPM.getApplicationInfo(packageName, 0);
text.setText(mPM.getApplicationLabel(ai));
icon.setImageDrawable(mPM.getApplicationIcon(ai));
} catch (final NameNotFoundException e) {
// fallback
text.setText(packageName);
}
} else {
// fallback
text.setText(packageName);
}
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(R.layout.api_apps_adapter_list_item, null);
}
}

View File

@ -24,6 +24,7 @@ import android.os.Message;
import android.os.Messenger;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import org.openintents.openpgp.util.OpenPgpApi;
import org.sufficientlysecure.htmltextview.HtmlTextView;
import org.sufficientlysecure.keychain.Constants;
@ -31,7 +32,9 @@ import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
@ -42,6 +45,8 @@ import java.util.ArrayList;
public class RemoteServiceActivity extends ActionBarActivity {
public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER";
public static final String ACTION_REGISTER_ACCOUNT = Constants.INTENT_PREFIX
+ "API_ACTIVITY_REGISTER_ACCOUNT";
public static final String ACTION_CACHE_PASSPHRASE = Constants.INTENT_PREFIX
+ "API_ACTIVITY_CACHE_PASSPHRASE";
public static final String ACTION_SELECT_PUB_KEYS = Constants.INTENT_PREFIX
@ -58,6 +63,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
// register action
public static final String EXTRA_PACKAGE_NAME = "package_name";
public static final String EXTRA_PACKAGE_SIGNATURE = "package_signature";
// create acc action
public static final String EXTRA_ACC_NAME = "acc_name";
// select pub keys action
public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids";
public static final String EXTRA_MISSING_USER_IDS = "missing_user_ids";
@ -66,7 +73,9 @@ public class RemoteServiceActivity extends ActionBarActivity {
public static final String EXTRA_ERROR_MESSAGE = "error_message";
// register view
private AppSettingsFragment mSettingsFragment;
private AppSettingsFragment mAppSettingsFragment;
// create acc view
private AccountSettingsFragment mAccSettingsFragment;
// select pub keys view
private SelectPublicKeyFragment mSelectFragment;
@ -95,20 +104,14 @@ public class RemoteServiceActivity extends ActionBarActivity {
public void onClick(View v) {
// Allow
// user needs to select a key!
if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) {
mSettingsFragment.setErrorOnSelectKeyFragment(
getString(R.string.api_register_error_select_key));
} else {
ProviderHelper.insertApiApp(RemoteServiceActivity.this,
mSettingsFragment.getAppSettings());
mAppSettingsFragment.getAppSettings());
// give data through for new service call
Intent resultData = extras.getParcelable(EXTRA_DATA);
RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
RemoteServiceActivity.this.finish();
}
}
}, R.string.api_register_disallow, R.drawable.ic_action_cancel,
new View.OnClickListener() {
@Override
@ -122,11 +125,56 @@ public class RemoteServiceActivity extends ActionBarActivity {
setContentView(R.layout.api_app_register_activity);
mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
mAppSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
R.id.api_app_settings_fragment);
AppSettings settings = new AppSettings(packageName, packageSignature);
mSettingsFragment.setAppSettings(settings);
mAppSettingsFragment.setAppSettings(settings);
} else if (ACTION_REGISTER_ACCOUNT.equals(action)) {
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
final String accName = extras.getString(EXTRA_ACC_NAME);
// Inflate a "Done"/"Cancel" custom action bar view
ActionBarHelper.setTwoButtonView(getSupportActionBar(),
R.string.api_settings_save, R.drawable.ic_action_done,
new View.OnClickListener() {
@Override
public void onClick(View v) {
// Save
// user needs to select a key!
if (mAccSettingsFragment.getAccSettings().getKeyId() == Id.key.none) {
mAccSettingsFragment.setErrorOnSelectKeyFragment(
getString(R.string.api_register_error_select_key));
} else {
ProviderHelper.insertApiAccount(RemoteServiceActivity.this,
KeychainContract.ApiAccounts.buildBaseUri(packageName),
mAccSettingsFragment.getAccSettings());
// give data through for new service call
Intent resultData = extras.getParcelable(EXTRA_DATA);
RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
RemoteServiceActivity.this.finish();
}
}
}, R.string.api_settings_cancel, R.drawable.ic_action_cancel,
new View.OnClickListener() {
@Override
public void onClick(View v) {
// Cancel
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
RemoteServiceActivity.this.finish();
}
}
);
setContentView(R.layout.api_account_create_activity);
mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
R.id.api_account_settings_fragment);
AccountSettings settings = new AccountSettings(accName);
mAccSettingsFragment.setAccSettings(settings);
} else if (ACTION_CACHE_PASSPHRASE.equals(action)) {
long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
Intent resultData = extras.getParcelable(EXTRA_DATA);

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="vertical">
<fragment
android:id="@+id/api_account_settings_fragment"
android:name="org.sufficientlysecure.keychain.remote.ui.AccountSettingsFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="@layout/api_app_settings_fragment" />
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp"
android:gravity="center_horizontal"
android:orientation="horizontal">
<ImageView
android:id="@+id/api_account_settings_app_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentBottom="true"
android:layout_alignParentTop="true"
android:layout_marginRight="6dp"
android:src="@drawable/icon" />
<TextView
android:id="@+id/api_account_settings_app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/api_app_settings_app_icon"
android:gravity="center_vertical"
android:orientation="vertical"
android:text="Name (set in-code)"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
<fragment
android:id="@+id/api_account_settings_select_key_fragment"
android:name="org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="@layout/select_secret_key_layout_fragment" />
<org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
custom:foldedLabel="@string/btn_encryption_advanced_settings_show"
custom:unFoldedLabel="@string/btn_encryption_advanced_settings_hide"
custom:foldedIcon="fa-chevron-right"
custom:unFoldedIcon="fa-chevron-down">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/label_encryption_algorithm"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Spinner
android:id="@+id/api_account_settings_encryption_algorithm"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/label_hash_algorithm"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Spinner
android:id="@+id/api_account_settings_hash_algorithm"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/label_message_compression"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Spinner
android:id="@+id/api_account_settings_compression"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/api_settings_package_name"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/api_account_settings_package_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="com.example"
android:textAppearance="?android:attr/textAppearanceSmall" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/api_settings_package_signature"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/api_account_settings_package_signature"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Base64 encoded signature"
android:textAppearance="?android:attr/textAppearanceSmall" />
</org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout>
</LinearLayout>

View File

@ -20,7 +20,7 @@
<fragment
android:id="@+id/api_app_settings_fragment"
android:name="org.sufficientlysecure.keychain.service.remote.AppSettingsFragment"
android:name="org.sufficientlysecure.keychain.remote.ui.AppSettingsFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="@layout/api_app_settings_fragment" />

View File

@ -36,13 +36,6 @@
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
<fragment
android:id="@+id/api_app_settings_select_key_fragment"
android:name="org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="@layout/select_secret_key_layout_fragment" />
<org.sufficientlysecure.keychain.ui.widget.FoldableLinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -51,39 +44,6 @@
custom:foldedIcon="fa-chevron-right"
custom:unFoldedIcon="fa-chevron-down">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/label_encryption_algorithm"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Spinner
android:id="@+id/api_app_settings_encryption_algorithm"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/label_hash_algorithm"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Spinner
android:id="@+id/api_app_settings_hash_algorithm"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/label_message_compression"
android:textAppearance="?android:attr/textAppearanceMedium" />
<Spinner
android:id="@+id/api_app_settings_compression"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -8,7 +8,7 @@
<fragment
android:id="@+id/crypto_consumers_list_fragment"
android:name="org.sufficientlysecure.keychain.service.remote.RegisteredAppsListFragment"
android:name="org.sufficientlysecure.keychain.remote.ui.AppsListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>