use SQLDelight, remove ApiApps access from KeychainProvider
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'witness'
|
apply plugin: 'witness'
|
||||||
apply plugin: 'jacoco'
|
apply plugin: 'jacoco'
|
||||||
|
apply plugin: 'com.squareup.sqldelight'
|
||||||
// apply plugin: 'com.github.kt3k.coveralls'
|
// apply plugin: 'com.github.kt3k.coveralls'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -98,6 +99,8 @@ dependencies {
|
|||||||
|
|
||||||
compile "android.arch.lifecycle:extensions:1.0.0"
|
compile "android.arch.lifecycle:extensions:1.0.0"
|
||||||
annotationProcessor "android.arch.lifecycle:compiler: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
|
// Output of ./gradlew -q calculateChecksums
|
||||||
|
|||||||
@@ -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<ApiAllowedKey> FACTORY = new Factory<ApiAllowedKey>(AutoValue_ApiAllowedKey::new);
|
||||||
|
}
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<ApiApp> FACTORY =
|
||||||
|
new ApiAppsModel.Factory<ApiApp>(AutoValue_ApiApp::new);
|
||||||
|
|
||||||
|
public static ApiApp create(String packageName, byte[] packageSignature) {
|
||||||
|
return new AutoValue_ApiApp(null, packageName, packageSignature);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,179 +20,97 @@ package org.sufficientlysecure.keychain.provider;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.OperationApplicationException;
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.RemoteException;
|
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAllowedKeys;
|
import com.squareup.sqldelight.SqlDelightQuery;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
import org.sufficientlysecure.keychain.ApiAllowedKeysModel.InsertAllowedKey;
|
||||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
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 {
|
public class ApiDataAccessObject {
|
||||||
|
|
||||||
private final SimpleContentResolverInterface mQueryInterface;
|
private final SupportSQLiteDatabase db;
|
||||||
|
|
||||||
public ApiDataAccessObject(Context context) {
|
public ApiDataAccessObject(Context context) {
|
||||||
final ContentResolver contentResolver = context.getContentResolver();
|
KeychainDatabase keychainDatabase = new KeychainDatabase(context);
|
||||||
mQueryInterface = new SimpleContentResolverInterface() {
|
db = keychainDatabase.getWritableDatabase();
|
||||||
@Override
|
|
||||||
public Cursor query(Uri contentUri, String[] projection, String selection, String[] selectionArgs,
|
|
||||||
String sortOrder) {
|
|
||||||
return contentResolver.query(contentUri, projection, selection, selectionArgs, sortOrder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public ApiApp getApiApp(String packageName) {
|
||||||
public Uri insert(Uri contentUri, ContentValues values) {
|
try (Cursor cursor = db.query(ApiApp.FACTORY.selectByPackageName(packageName))) {
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public ApiDataAccessObject(SimpleContentResolverInterface queryInterface) {
|
|
||||||
mQueryInterface = queryInterface;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<String> getRegisteredApiApps() {
|
|
||||||
Cursor cursor = mQueryInterface.query(ApiApps.CONTENT_URI, null, null, null, null);
|
|
||||||
|
|
||||||
ArrayList<String> packageNames = new ArrayList<>();
|
|
||||||
try {
|
|
||||||
if (cursor != null) {
|
|
||||||
int packageNameCol = cursor.getColumnIndex(ApiApps.PACKAGE_NAME);
|
|
||||||
if (cursor.moveToFirst()) {
|
if (cursor.moveToFirst()) {
|
||||||
do {
|
return ApiApp.FACTORY.selectByPackageNameMapper().map(cursor);
|
||||||
packageNames.add(cursor.getString(packageNameCol));
|
|
||||||
} while (cursor.moveToNext());
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Long> getAllowedKeyIdsForApp(Uri uri) {
|
|
||||||
HashSet<Long> 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<Long> 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) {
|
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};
|
|
||||||
|
|
||||||
Cursor cursor = mQueryInterface.query(queryUri, projection, null, null, null);
|
|
||||||
try {
|
|
||||||
byte[] signature = null;
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
|
||||||
int signatureCol = 0;
|
|
||||||
|
|
||||||
signature = cursor.getBlob(signatureCol);
|
|
||||||
}
|
}
|
||||||
return signature;
|
|
||||||
} finally {
|
public void insertApiApp(ApiApp apiApp) {
|
||||||
if (cursor != null) {
|
InsertApiApp statement = new ApiAppsModel.InsertApiApp(db);
|
||||||
cursor.close();
|
statement.bind(apiApp.package_name(), apiApp.package_signature());
|
||||||
|
statement.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void deleteApiApp(String packageName) {
|
||||||
|
DeleteByPackageName deleteByPackageName = new DeleteByPackageName(db);
|
||||||
|
deleteByPackageName.bind(packageName);
|
||||||
|
deleteByPackageName.executeUpdateDelete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashSet<Long> getAllowedKeyIdsForApp(String packageName) {
|
||||||
|
SqlDelightQuery allowedKeys = ApiAllowedKey.FACTORY.getAllowedKeys(packageName);
|
||||||
|
HashSet<Long> 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<Long> 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<ApiApp> getAllApiApps() {
|
||||||
|
SqlDelightQuery query = ApiApp.FACTORY.selectAll();
|
||||||
|
|
||||||
|
ArrayList<ApiApp> result = new ArrayList<>();
|
||||||
|
try (Cursor cursor = db.query(query)) {
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
ApiApp apiApp = ApiApp.FACTORY.selectAllMapper().map(cursor);
|
||||||
|
result.add(apiApp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -333,7 +333,7 @@ public class KeyRepository {
|
|||||||
try {
|
try {
|
||||||
return localSecretKeyStorage.readSecretKey(masterKeyId);
|
return localSecretKeyStorage.readSecretKey(masterKeyId);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Timber.e(e, "Error reading public key from storage!");
|
Timber.e(e, "Error reading secret key from storage!");
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,9 +141,6 @@ public class KeychainContract {
|
|||||||
public static final String PATH_KEYS = "keys";
|
public static final String PATH_KEYS = "keys";
|
||||||
public static final String PATH_CERTS = "certs";
|
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_PACKAGE_NAME = "by_package_name";
|
||||||
public static final String PATH_BY_KEY_ID = "by_key_id";
|
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 class ApiAutocryptPeer implements ApiAutocryptPeerColumns, BaseColumns {
|
||||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||||
.appendPath(BASE_AUTOCRYPT_PEERS).build();
|
.appendPath(BASE_AUTOCRYPT_PEERS).build();
|
||||||
|
|||||||
@@ -23,16 +23,18 @@ import java.io.FileInputStream;
|
|||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
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.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteException;
|
import android.database.sqlite.SQLiteException;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
|
||||||
import android.provider.BaseColumns;
|
import android.provider.BaseColumns;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAllowedKeysColumns;
|
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.ApiAutocryptPeerColumns;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
|
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).
|
* - 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.
|
* - 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 String DATABASE_NAME = "openkeychain.db";
|
||||||
private static final int DATABASE_VERSION = 26;
|
private static final int DATABASE_VERSION = 26;
|
||||||
private Context mContext;
|
private final SupportSQLiteOpenHelper supportSQLiteOpenHelper;
|
||||||
|
private Context context;
|
||||||
|
|
||||||
public interface Tables {
|
public interface Tables {
|
||||||
String KEY_RINGS_PUBLIC = "keyrings_public";
|
String KEY_RINGS_PUBLIC = "keyrings_public";
|
||||||
@@ -65,18 +68,11 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
String KEY_SIGNATURES = "key_signatures";
|
String KEY_SIGNATURES = "key_signatures";
|
||||||
String USER_PACKETS = "user_packets";
|
String USER_PACKETS = "user_packets";
|
||||||
String CERTS = "certs";
|
String CERTS = "certs";
|
||||||
String API_APPS = "api_apps";
|
|
||||||
String API_ALLOWED_KEYS = "api_allowed_keys";
|
String API_ALLOWED_KEYS = "api_allowed_keys";
|
||||||
String OVERRIDDEN_WARNINGS = "overridden_warnings";
|
String OVERRIDDEN_WARNINGS = "overridden_warnings";
|
||||||
String API_AUTOCRYPT_PEERS = "api_autocrypt_peers";
|
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 =
|
private static final String CREATE_KEYS =
|
||||||
"CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " ("
|
"CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " ("
|
||||||
+ KeysColumns.MASTER_KEY_ID + " INTEGER, "
|
+ 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"
|
+ 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 =
|
private static final String CREATE_KEY_SIGNATURES =
|
||||||
"CREATE TABLE IF NOT EXISTS " + Tables.KEY_SIGNATURES + " ("
|
"CREATE TABLE IF NOT EXISTS " + Tables.KEY_SIGNATURES + " ("
|
||||||
+ KeySignaturesColumns.MASTER_KEY_ID + " INTEGER NOT NULL, "
|
+ KeySignaturesColumns.MASTER_KEY_ID + " INTEGER NOT NULL, "
|
||||||
@@ -175,14 +162,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
+ "PRIMARY KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ", "
|
+ "PRIMARY KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ", "
|
||||||
+ ApiAutocryptPeerColumns.IDENTIFIER + "), "
|
+ ApiAutocryptPeerColumns.IDENTIFIER + "), "
|
||||||
+ "FOREIGN KEY(" + ApiAutocryptPeerColumns.PACKAGE_NAME + ") REFERENCES "
|
+ "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 =
|
private static final String CREATE_API_APPS_ALLOWED_KEYS =
|
||||||
@@ -194,7 +174,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
+ "UNIQUE(" + ApiAppsAllowedKeysColumns.KEY_ID + ", "
|
+ "UNIQUE(" + ApiAppsAllowedKeysColumns.KEY_ID + ", "
|
||||||
+ ApiAppsAllowedKeysColumns.PACKAGE_NAME + "), "
|
+ ApiAppsAllowedKeysColumns.PACKAGE_NAME + "), "
|
||||||
+ "FOREIGN KEY(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") REFERENCES "
|
+ "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 =
|
private static final String CREATE_OVERRIDDEN_WARNINGS =
|
||||||
@@ -204,12 +184,46 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
+ ")";
|
+ ")";
|
||||||
|
|
||||||
public KeychainDatabase(Context context) {
|
public KeychainDatabase(Context context) {
|
||||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
this.context = context;
|
||||||
mContext = 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
|
@Override
|
||||||
public void onCreate(SQLiteDatabase db) {
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
public SupportSQLiteDatabase getReadableDatabase() {
|
||||||
|
return supportSQLiteOpenHelper.getReadableDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SupportSQLiteDatabase getWritableDatabase() {
|
||||||
|
return supportSQLiteOpenHelper.getWritableDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCreate(SupportSQLiteDatabase db) {
|
||||||
Timber.w("Creating database...");
|
Timber.w("Creating database...");
|
||||||
|
|
||||||
db.execSQL(CREATE_KEYRINGS_PUBLIC);
|
db.execSQL(CREATE_KEYRINGS_PUBLIC);
|
||||||
@@ -218,7 +232,6 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
db.execSQL(CREATE_CERTS);
|
db.execSQL(CREATE_CERTS);
|
||||||
db.execSQL(CREATE_UPDATE_KEYS);
|
db.execSQL(CREATE_UPDATE_KEYS);
|
||||||
db.execSQL(CREATE_KEY_SIGNATURES);
|
db.execSQL(CREATE_KEY_SIGNATURES);
|
||||||
db.execSQL(CREATE_API_APPS);
|
|
||||||
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
|
db.execSQL(CREATE_API_APPS_ALLOWED_KEYS);
|
||||||
db.execSQL(CREATE_OVERRIDDEN_WARNINGS);
|
db.execSQL(CREATE_OVERRIDDEN_WARNINGS);
|
||||||
db.execSQL(CREATE_API_AUTOCRYPT_PEERS);
|
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 ("
|
db.execSQL("CREATE INDEX uids_by_email ON user_packets ("
|
||||||
+ UserPacketsColumns.EMAIL + ");");
|
+ UserPacketsColumns.EMAIL + ");");
|
||||||
|
|
||||||
Preferences.getPreferences(mContext).setKeySignaturesTableInitialized();
|
Preferences.getPreferences(context).setKeySignaturesTableInitialized();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
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) {
|
|
||||||
Timber.d("Upgrading db from " + oldVersion + " to " + newVersion);
|
Timber.d("Upgrading db from " + oldVersion + " to " + newVersion);
|
||||||
|
|
||||||
switch (oldVersion) {
|
switch (oldVersion) {
|
||||||
@@ -459,9 +462,9 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void migrateSecretKeysFromDbToLocalStorage(SQLiteDatabase db) throws IOException {
|
private void migrateSecretKeysFromDbToLocalStorage(SupportSQLiteDatabase db) throws IOException {
|
||||||
LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(mContext);
|
LocalSecretKeyStorage localSecretKeyStorage = LocalSecretKeyStorage.getInstance(context);
|
||||||
Cursor cursor = db.rawQuery("SELECT master_key_id, key_ring_data FROM keyrings_secret", null);
|
Cursor cursor = db.query("SELECT master_key_id, key_ring_data FROM keyrings_secret");
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
long masterKeyId = cursor.getLong(0);
|
long masterKeyId = cursor.getLong(0);
|
||||||
byte[] secretKeyBlob = cursor.getBlob(1);
|
byte[] secretKeyBlob = cursor.getBlob(1);
|
||||||
@@ -473,8 +476,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
// db.execSQL("DROP TABLE keyrings_secret");
|
// db.execSQL("DROP TABLE keyrings_secret");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
|
||||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
|
||||||
// Downgrade is ok for the debug version, makes it easier to work with branches
|
// Downgrade is ok for the debug version, makes it easier to work with branches
|
||||||
if (Constants.DEBUG) {
|
if (Constants.DEBUG) {
|
||||||
return;
|
return;
|
||||||
@@ -528,7 +530,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
public void clearDatabase() {
|
public void clearDatabase() {
|
||||||
getWritableDatabase().execSQL("delete from " + Tables.KEY_RINGS_PUBLIC);
|
getWritableDatabase().execSQL("delete from " + Tables.KEY_RINGS_PUBLIC);
|
||||||
getWritableDatabase().execSQL("delete from " + Tables.API_ALLOWED_KEYS);
|
getWritableDatabase().execSQL("delete from " + Tables.API_ALLOWED_KEYS);
|
||||||
getWritableDatabase().execSQL("delete from " + Tables.API_APPS);
|
getWritableDatabase().execSQL("delete from api_apps");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import java.util.Date;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||||
import android.content.ContentProvider;
|
import android.content.ContentProvider;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
@@ -38,8 +39,6 @@ import android.text.TextUtils;
|
|||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
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.ApiAutocryptPeer;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
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_IDS = 207;
|
||||||
private static final int KEY_RING_LINKED_ID_CERTS = 208;
|
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_EMAIL = 400;
|
||||||
private static final int KEY_RINGS_FIND_BY_SUBKEY = 401;
|
private static final int KEY_RINGS_FIND_BY_SUBKEY = 401;
|
||||||
private static final int KEY_RINGS_FIND_BY_USER_ID = 402;
|
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 + "/*/*",
|
+ KeychainContract.PATH_CERTS + "/*/*",
|
||||||
KEY_RING_CERTS_SPECIFIC);
|
KEY_RING_CERTS_SPECIFIC);
|
||||||
|
|
||||||
/*
|
|
||||||
* API apps
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* api_apps
|
|
||||||
* api_apps/_ (package name)
|
|
||||||
*
|
|
||||||
* api_apps/_/allowed_keys
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
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
|
* Trust Identity access
|
||||||
*
|
*
|
||||||
@@ -269,15 +248,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
|
|||||||
case KEY_SIGNATURES:
|
case KEY_SIGNATURES:
|
||||||
return KeySignatures.CONTENT_TYPE;
|
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:
|
default:
|
||||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||||
}
|
}
|
||||||
@@ -781,26 +751,6 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
|
|||||||
}
|
}
|
||||||
break;
|
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: {
|
default: {
|
||||||
throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")");
|
throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")");
|
||||||
}
|
}
|
||||||
@@ -815,9 +765,10 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
|
|||||||
orderBy = sortOrder;
|
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) {
|
if (cursor != null) {
|
||||||
// Tell the cursor what uri to watch, so it knows when its source data changes
|
// Tell the cursor what uri to watch, so it knows when its source data changes
|
||||||
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
||||||
@@ -844,7 +795,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
|
|||||||
public Uri insert(Uri uri, ContentValues values) {
|
public Uri insert(Uri uri, ContentValues values) {
|
||||||
Timber.d("insert(uri=" + uri + ", values=" + values.toString() + ")");
|
Timber.d("insert(uri=" + uri + ", values=" + values.toString() + ")");
|
||||||
|
|
||||||
final SQLiteDatabase db = getDb().getWritableDatabase();
|
final SupportSQLiteDatabase db = getDb().getWritableDatabase();
|
||||||
|
|
||||||
Uri rowUri = null;
|
Uri rowUri = null;
|
||||||
Long keyId = null;
|
Long keyId = null;
|
||||||
@@ -853,12 +804,12 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
|
|||||||
|
|
||||||
switch (match) {
|
switch (match) {
|
||||||
case KEY_RING_PUBLIC: {
|
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);
|
keyId = values.getAsLong(KeyRings.MASTER_KEY_ID);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case KEY_RING_KEYS: {
|
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);
|
keyId = values.getAsLong(Keys.MASTER_KEY_ID);
|
||||||
break;
|
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) {
|
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!");
|
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);
|
keyId = values.getAsLong(UserPackets.MASTER_KEY_ID);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case KEY_RING_CERTS: {
|
case KEY_RING_CERTS: {
|
||||||
// we replace here, keeping only the latest signature
|
// we replace here, keeping only the latest signature
|
||||||
// TODO this would be better handled in savePublicKeyRing directly!
|
// 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);
|
keyId = values.getAsLong(Certs.MASTER_KEY_ID);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case UPDATED_KEYS: {
|
case UPDATED_KEYS: {
|
||||||
keyId = values.getAsLong(UpdatedKeys.MASTER_KEY_ID);
|
keyId = values.getAsLong(UpdatedKeys.MASTER_KEY_ID);
|
||||||
try {
|
try {
|
||||||
db.insertOrThrow(Tables.UPDATED_KEYS, null, values);
|
db.insert(Tables.UPDATED_KEYS, SQLiteDatabase.CONFLICT_FAIL, values);
|
||||||
} catch (SQLiteConstraintException e) {
|
} 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) });
|
UpdatedKeys.MASTER_KEY_ID + " = ?", new String[] { Long.toString(keyId) });
|
||||||
}
|
}
|
||||||
rowUri = UpdatedKeys.CONTENT_URI;
|
rowUri = UpdatedKeys.CONTENT_URI;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case KEY_SIGNATURES: {
|
case KEY_SIGNATURES: {
|
||||||
db.insert(Tables.KEY_SIGNATURES, null, values);
|
db.insert(Tables.KEY_SIGNATURES, SQLiteDatabase.CONFLICT_FAIL, values);
|
||||||
rowUri = KeySignatures.CONTENT_URI;
|
rowUri = KeySignatures.CONTENT_URI;
|
||||||
break;
|
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: {
|
default: {
|
||||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
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) {
|
public int delete(Uri uri, String additionalSelection, String[] selectionArgs) {
|
||||||
Timber.v("delete(uri=" + uri + ")");
|
Timber.v("delete(uri=" + uri + ")");
|
||||||
|
|
||||||
final SQLiteDatabase db = getDb().getWritableDatabase();
|
final SupportSQLiteDatabase db = getDb().getWritableDatabase();
|
||||||
|
|
||||||
int count;
|
int count;
|
||||||
final int match = mUriMatcher.match(uri);
|
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);
|
count = db.delete(Tables.API_AUTOCRYPT_PEERS, selection, selectionArgs);
|
||||||
break;
|
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: {
|
default: {
|
||||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
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) {
|
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||||
Timber.v("update(uri=" + uri + ", values=" + values.toString() + ")");
|
Timber.v("update(uri=" + uri + ", values=" + values.toString() + ")");
|
||||||
|
|
||||||
final SQLiteDatabase db = getDb().getWritableDatabase();
|
final SupportSQLiteDatabase db = getDb().getWritableDatabase();
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
try {
|
try {
|
||||||
@@ -1022,12 +950,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
|
|||||||
if (!TextUtils.isEmpty(selection)) {
|
if (!TextUtils.isEmpty(selection)) {
|
||||||
actualSelection += " AND (" + selection + ")";
|
actualSelection += " AND (" + selection + ")";
|
||||||
}
|
}
|
||||||
count = db.update(Tables.KEYS, values, actualSelection, selectionArgs);
|
count = db.update(Tables.KEYS, SQLiteDatabase.CONFLICT_FAIL, values, actualSelection, selectionArgs);
|
||||||
break;
|
|
||||||
}
|
|
||||||
case API_APPS_BY_PACKAGE_NAME: {
|
|
||||||
count = db.update(Tables.API_APPS, values,
|
|
||||||
buildDefaultApiAppsSelection(uri, selection), selectionArgs);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case UPDATED_KEYS: {
|
case UPDATED_KEYS: {
|
||||||
@@ -1040,7 +963,7 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
|
|||||||
throw new UnsupportedOperationException("can only reset all keys");
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case AUTOCRYPT_PEERS_BY_PACKAGE_NAME_AND_TRUST_ID: {
|
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.PACKAGE_NAME, packageName);
|
||||||
values.put(ApiAutocryptPeer.IDENTIFIER, identifier);
|
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 + "=?",
|
ApiAutocryptPeer.PACKAGE_NAME + "=? AND " + ApiAutocryptPeer.IDENTIFIER + "=?",
|
||||||
new String[] { packageName, identifier });
|
new String[] { packageName, identifier });
|
||||||
if (updated == 0) {
|
if (updated == 0) {
|
||||||
db.insertOrThrow(Tables.API_AUTOCRYPT_PEERS, null, values);
|
db.insert(Tables.API_AUTOCRYPT_PEERS, SQLiteDatabase.CONFLICT_FAIL,values);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -1068,35 +991,4 @@ public class KeychainProvider extends ContentProvider implements SimpleContentRe
|
|||||||
|
|
||||||
return count;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,9 @@
|
|||||||
package org.sufficientlysecure.keychain.provider;
|
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.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
@@ -47,34 +50,31 @@ public class OverriddenWarningsRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWarningOverridden(String identifier) {
|
public boolean isWarningOverridden(String identifier) {
|
||||||
SQLiteDatabase db = getDb().getReadableDatabase();
|
SupportSQLiteDatabase db = getDb().getReadableDatabase();
|
||||||
Cursor cursor = db.query(
|
SupportSQLiteQuery query = SupportSQLiteQueryBuilder
|
||||||
Tables.OVERRIDDEN_WARNINGS,
|
.builder(Tables.OVERRIDDEN_WARNINGS)
|
||||||
new String[] { "COUNT(*)" },
|
.columns(new String[] { "COUNT(*) FROM " })
|
||||||
OverriddenWarnings.IDENTIFIER + " = ?",
|
.selection(OverriddenWarnings.IDENTIFIER + " = ?", new String[] { identifier })
|
||||||
new String[] { identifier },
|
.create();
|
||||||
null, null, null);
|
Cursor cursor = db.query(query);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
return cursor.getInt(0) > 0;
|
return cursor.getInt(0) > 0;
|
||||||
} finally {
|
} finally {
|
||||||
cursor.close();
|
cursor.close();
|
||||||
db.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void putOverride(String identifier) {
|
public void putOverride(String identifier) {
|
||||||
SQLiteDatabase db = getDb().getWritableDatabase();
|
SupportSQLiteDatabase db = getDb().getWritableDatabase();
|
||||||
ContentValues cv = new ContentValues();
|
ContentValues cv = new ContentValues();
|
||||||
cv.put(OverriddenWarnings.IDENTIFIER, identifier);
|
cv.put(OverriddenWarnings.IDENTIFIER, identifier);
|
||||||
db.replace(Tables.OVERRIDDEN_WARNINGS, null, cv);
|
db.insert(Tables.OVERRIDDEN_WARNINGS, SQLiteDatabase.CONFLICT_REPLACE, cv);
|
||||||
db.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteOverride(String identifier) {
|
public void deleteOverride(String identifier) {
|
||||||
SQLiteDatabase db = getDb().getWritableDatabase();
|
SupportSQLiteDatabase db = getDb().getWritableDatabase();
|
||||||
db.delete(Tables.OVERRIDDEN_WARNINGS, OverriddenWarnings.IDENTIFIER + " = ?", new String[] { identifier });
|
db.delete(Tables.OVERRIDDEN_WARNINGS, OverriddenWarnings.IDENTIFIER + " = ?", new String[] { identifier });
|
||||||
db.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ public class ApiPendingIntentFactory {
|
|||||||
|
|
||||||
PendingIntent createSelectSignKeyIdLegacyPendingIntent(Intent data, String packageName, String preferredUserId) {
|
PendingIntent createSelectSignKeyIdLegacyPendingIntent(Intent data, String packageName, String preferredUserId) {
|
||||||
Intent intent = new Intent(mContext, SelectSignKeyIdActivity.class);
|
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);
|
intent.putExtra(SelectSignKeyIdActivity.EXTRA_USER_ID, preferredUserId);
|
||||||
|
|
||||||
return createInternal(data, intent);
|
return createInternal(data, intent);
|
||||||
@@ -147,7 +147,6 @@ public class ApiPendingIntentFactory {
|
|||||||
PendingIntent createSelectSignKeyIdPendingIntent(Intent data, String packageName,
|
PendingIntent createSelectSignKeyIdPendingIntent(Intent data, String packageName,
|
||||||
byte[] packageSignature, String preferredUserId, boolean showAutocryptHint) {
|
byte[] packageSignature, String preferredUserId, boolean showAutocryptHint) {
|
||||||
Intent intent = new Intent(mContext, RemoteSelectIdKeyActivity.class);
|
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_NAME, packageName);
|
||||||
intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
|
intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
|
||||||
intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_USER_ID, preferredUserId);
|
intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_USER_ID, preferredUserId);
|
||||||
@@ -158,7 +157,6 @@ public class ApiPendingIntentFactory {
|
|||||||
|
|
||||||
PendingIntent createSelectAuthenticationKeyIdPendingIntent(Intent data, String packageName) {
|
PendingIntent createSelectAuthenticationKeyIdPendingIntent(Intent data, String packageName) {
|
||||||
Intent intent = new Intent(mContext, RemoteSelectAuthenticationKeyActivity.class);
|
Intent intent = new Intent(mContext, RemoteSelectAuthenticationKeyActivity.class);
|
||||||
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(packageName));
|
|
||||||
intent.putExtra(RemoteSelectAuthenticationKeyActivity.EXTRA_PACKAGE_NAME, packageName);
|
intent.putExtra(RemoteSelectAuthenticationKeyActivity.EXTRA_PACKAGE_NAME, packageName);
|
||||||
|
|
||||||
return createInternal(data, intent);
|
return createInternal(data, intent);
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -23,6 +23,7 @@ import java.util.Arrays;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||||
import android.content.ContentProvider;
|
import android.content.ContentProvider;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
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.AutocryptRecommendationResult;
|
||||||
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptState;
|
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptState;
|
||||||
import org.sufficientlysecure.keychain.provider.DatabaseNotifyManager;
|
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.Certs;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
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.KeychainExternalContract.EmailStatus;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainProvider;
|
import org.sufficientlysecure.keychain.provider.KeychainProvider;
|
||||||
import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface;
|
import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface;
|
||||||
import org.sufficientlysecure.keychain.util.CloseDatabaseCursorFactory;
|
|
||||||
import timber.log.Timber;
|
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 = 201;
|
||||||
private static final int AUTOCRYPT_STATUS_INTERNAL = 202;
|
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_QUERIED_ADDRESSES = "queried_addresses";
|
||||||
public static final String TEMP_TABLE_COLUMN_ADDRES = "address";
|
public static final String TEMP_TABLE_COLUMN_ADDRES = "address";
|
||||||
|
|
||||||
@@ -113,7 +109,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
|||||||
|
|
||||||
internalKeychainProvider = new KeychainProvider();
|
internalKeychainProvider = new KeychainProvider();
|
||||||
internalKeychainProvider.attachInfo(context, null);
|
internalKeychainProvider.attachInfo(context, null);
|
||||||
apiPermissionHelper = new ApiPermissionHelper(context, new ApiDataAccessObject(internalKeychainProvider));
|
apiPermissionHelper = new ApiPermissionHelper(context, new ApiDataAccessObject(getContext()));
|
||||||
databaseNotifyManager = DatabaseNotifyManager.create(context);
|
databaseNotifyManager = DatabaseNotifyManager.create(context);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -127,13 +123,6 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
|||||||
switch (match) {
|
switch (match) {
|
||||||
case EMAIL_STATUS:
|
case EMAIL_STATUS:
|
||||||
return EmailStatus.CONTENT_TYPE;
|
return EmailStatus.CONTENT_TYPE;
|
||||||
|
|
||||||
case API_APPS:
|
|
||||||
return ApiApps.CONTENT_TYPE;
|
|
||||||
|
|
||||||
case API_APPS_BY_PACKAGE_NAME:
|
|
||||||
return ApiApps.CONTENT_ITEM_TYPE;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||||
}
|
}
|
||||||
@@ -154,7 +143,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
|||||||
|
|
||||||
String groupBy = null;
|
String groupBy = null;
|
||||||
|
|
||||||
SQLiteDatabase db = new KeychainDatabase(getContext()).getReadableDatabase();
|
SupportSQLiteDatabase db = new KeychainDatabase(getContext()).getReadableDatabase();
|
||||||
|
|
||||||
String callingPackageName = apiPermissionHelper.getCurrentCallingPackage();
|
String callingPackageName = apiPermissionHelper.getCurrentCallingPackage();
|
||||||
|
|
||||||
@@ -169,7 +158,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
|||||||
ContentValues cv = new ContentValues();
|
ContentValues cv = new ContentValues();
|
||||||
for (String address : selectionArgs) {
|
for (String address : selectionArgs) {
|
||||||
cv.put(TEMP_TABLE_COLUMN_ADDRES, address);
|
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<String, String> projectionMap = new HashMap<>();
|
HashMap<String, String> projectionMap = new HashMap<>();
|
||||||
@@ -256,7 +245,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
|||||||
ContentValues cv = new ContentValues();
|
ContentValues cv = new ContentValues();
|
||||||
for (String address : selectionArgs) {
|
for (String address : selectionArgs) {
|
||||||
cv.put(TEMP_TABLE_COLUMN_ADDRES, address);
|
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("%");
|
boolean isWildcardSelector = selectionArgs.length == 1 && selectionArgs[0].contains("%");
|
||||||
@@ -321,8 +310,8 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
|||||||
}
|
}
|
||||||
|
|
||||||
qb.setStrict(true);
|
qb.setStrict(true);
|
||||||
qb.setCursorFactory(new CloseDatabaseCursorFactory());
|
String query = qb.buildQuery(projection, null, null, groupBy, null, orderBy);
|
||||||
Cursor cursor = qb.query(db, projection, null, null, groupBy, null, orderBy);
|
Cursor cursor = db.query(query);
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
// Tell the cursor what uri to watch, so it knows when its source data changes
|
// Tell the cursor what uri to watch, so it knows when its source data changes
|
||||||
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
||||||
@@ -337,7 +326,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
|||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillTempTableWithAutocryptRecommendations(SQLiteDatabase db,
|
private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db,
|
||||||
AutocryptPeerDataAccessObject autocryptPeerDao, String[] peerIds) {
|
AutocryptPeerDataAccessObject autocryptPeerDao, String[] peerIds) {
|
||||||
List<AutocryptRecommendationResult> autocryptStates =
|
List<AutocryptRecommendationResult> autocryptStates =
|
||||||
autocryptPeerDao.determineAutocryptRecommendations(peerIds);
|
autocryptPeerDao.determineAutocryptRecommendations(peerIds);
|
||||||
@@ -345,7 +334,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
|||||||
fillTempTableWithAutocryptRecommendations(db, autocryptStates);
|
fillTempTableWithAutocryptRecommendations(db, autocryptStates);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillTempTableWithAutocryptRecommendations(SQLiteDatabase db,
|
private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db,
|
||||||
List<AutocryptRecommendationResult> autocryptRecommendations) {
|
List<AutocryptRecommendationResult> autocryptRecommendations) {
|
||||||
ContentValues cv = new ContentValues();
|
ContentValues cv = new ContentValues();
|
||||||
for (AutocryptRecommendationResult peerResult : autocryptRecommendations) {
|
for (AutocryptRecommendationResult peerResult : autocryptRecommendations) {
|
||||||
@@ -359,12 +348,12 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
|||||||
KeychainExternalContract.KEY_STATUS_UNVERIFIED);
|
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 });
|
new String[] { peerResult.peerId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fillTempTableWithUidResult(SQLiteDatabase db, boolean isWildcardSelector) {
|
private void fillTempTableWithUidResult(SupportSQLiteDatabase db, boolean isWildcardSelector) {
|
||||||
String cmpOperator = isWildcardSelector ? " LIKE " : " = ";
|
String cmpOperator = isWildcardSelector ? " LIKE " : " = ";
|
||||||
long unixSeconds = System.currentTimeMillis() / 1000;
|
long unixSeconds = System.currentTimeMillis() / 1000;
|
||||||
db.execSQL("REPLACE INTO " + TEMP_TABLE_QUERIED_ADDRESSES +
|
db.execSQL("REPLACE INTO " + TEMP_TABLE_QUERIED_ADDRESSES +
|
||||||
|
|||||||
@@ -915,8 +915,7 @@ public class OpenPgpService extends Service {
|
|||||||
|
|
||||||
private HashSet<Long> getAllowedKeyIds() {
|
private HashSet<Long> getAllowedKeyIds() {
|
||||||
String currentPkg = mApiPermissionHelper.getCurrentCallingPackage();
|
String currentPkg = mApiPermissionHelper.getCurrentCallingPackage();
|
||||||
return mApiDao.getAllowedKeyIdsForApp(
|
return mApiDao.getAllowedKeyIdsForApp(currentPkg);
|
||||||
KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -17,12 +17,13 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.remote;
|
package org.sufficientlysecure.keychain.remote;
|
||||||
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||||
|
|
||||||
public class PackageUninstallReceiver extends BroadcastReceiver {
|
public class PackageUninstallReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
@@ -34,8 +35,9 @@ public class PackageUninstallReceiver extends BroadcastReceiver {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String packageName = uri.getEncodedSchemeSpecificPart();
|
String packageName = uri.getEncodedSchemeSpecificPart();
|
||||||
Uri appUri = KeychainContract.ApiApps.buildByPackageNameUri(packageName);
|
|
||||||
context.getContentResolver().delete(appUri, null, null);
|
ApiDataAccessObject apiDao = new ApiDataAccessObject(context);
|
||||||
|
apiDao.deleteApiApp(packageName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,19 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.remote;
|
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.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.util.Log;
|
|
||||||
import org.bouncycastle.bcpg.HashAlgorithmTags;
|
import org.bouncycastle.bcpg.HashAlgorithmTags;
|
||||||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
||||||
import org.openintents.ssh.authentication.ISshAuthenticationService;
|
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.ApiDataAccessObject;
|
||||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||||
import org.sufficientlysecure.keychain.provider.KeyRepository;
|
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.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||||
import org.sufficientlysecure.keychain.ssh.AuthenticationData;
|
import org.sufficientlysecure.keychain.ssh.AuthenticationData;
|
||||||
@@ -50,13 +57,6 @@ import org.sufficientlysecure.keychain.ssh.AuthenticationResult;
|
|||||||
import org.sufficientlysecure.keychain.ssh.signature.SshSignatureConverter;
|
import org.sufficientlysecure.keychain.ssh.signature.SshSignatureConverter;
|
||||||
import timber.log.Timber;
|
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 {
|
public class SshAuthenticationService extends Service {
|
||||||
private static final String TAG = "SshAuthService";
|
private static final String TAG = "SshAuthService";
|
||||||
@@ -394,7 +394,7 @@ public class SshAuthenticationService extends Service {
|
|||||||
|
|
||||||
private HashSet<Long> getAllowedKeyIds() {
|
private HashSet<Long> getAllowedKeyIds() {
|
||||||
String currentPkg = mApiPermissionHelper.getCurrentCallingPackage();
|
String currentPkg = mApiPermissionHelper.getCurrentCallingPackage();
|
||||||
return mApiDao.getAllowedKeyIdsForApp(KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));
|
return mApiDao.getAllowedKeyIdsForApp(currentPkg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import android.content.Intent;
|
|||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -36,24 +35,26 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.model.ApiApp;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
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.base.BaseActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
|
||||||
public class AppSettingsActivity extends BaseActivity {
|
public class AppSettingsActivity extends BaseActivity {
|
||||||
private Uri mAppUri;
|
public static final String EXTRA_PACKAGE_NAME = "package_name";
|
||||||
|
|
||||||
|
private String packageName;
|
||||||
|
|
||||||
private TextView mAppNameView;
|
private TextView mAppNameView;
|
||||||
private ImageView mAppIconView;
|
private ImageView mAppIconView;
|
||||||
|
|
||||||
|
|
||||||
// model
|
// model
|
||||||
AppSettings mAppSettings;
|
ApiApp mApiApp;
|
||||||
|
private ApiDataAccessObject apiDataAccessObject;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -68,15 +69,16 @@ public class AppSettingsActivity extends BaseActivity {
|
|||||||
setTitle(null);
|
setTitle(null);
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
mAppUri = intent.getData();
|
packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
|
||||||
if (mAppUri == null) {
|
if (packageName == null) {
|
||||||
Timber.e("Intent data missing. Should be Uri of app!");
|
Timber.e("Required extra package_name missing!");
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.d("uri: %s", mAppUri);
|
apiDataAccessObject = new ApiDataAccessObject(this);
|
||||||
loadData(savedInstanceState, mAppUri);
|
|
||||||
|
loadData(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void save() {
|
private void save() {
|
||||||
@@ -138,7 +140,7 @@ public class AppSettingsActivity extends BaseActivity {
|
|||||||
// advanced info: package certificate SHA-256
|
// advanced info: package certificate SHA-256
|
||||||
try {
|
try {
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
md.update(mAppSettings.getPackageCertificate());
|
md.update(mApiApp.package_signature());
|
||||||
byte[] digest = md.digest();
|
byte[] digest = md.digest();
|
||||||
certificate = new String(Hex.encode(digest));
|
certificate = new String(Hex.encode(digest));
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
@@ -146,7 +148,7 @@ public class AppSettingsActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AdvancedAppSettingsDialogFragment dialogFragment =
|
AdvancedAppSettingsDialogFragment dialogFragment =
|
||||||
AdvancedAppSettingsDialogFragment.newInstance(mAppSettings.getPackageName(), certificate);
|
AdvancedAppSettingsDialogFragment.newInstance(mApiApp.package_name(), certificate);
|
||||||
|
|
||||||
dialogFragment.show(getSupportFragmentManager(), "advancedDialog");
|
dialogFragment.show(getSupportFragmentManager(), "advancedDialog");
|
||||||
}
|
}
|
||||||
@@ -155,7 +157,7 @@ public class AppSettingsActivity extends BaseActivity {
|
|||||||
Intent i;
|
Intent i;
|
||||||
PackageManager manager = getPackageManager();
|
PackageManager manager = getPackageManager();
|
||||||
try {
|
try {
|
||||||
i = manager.getLaunchIntentForPackage(mAppSettings.getPackageName());
|
i = manager.getLaunchIntentForPackage(mApiApp.package_name());
|
||||||
if (i == null)
|
if (i == null)
|
||||||
throw new PackageManager.NameNotFoundException();
|
throw new PackageManager.NameNotFoundException();
|
||||||
// start like the Android launcher would do
|
// start like the Android launcher would do
|
||||||
@@ -167,31 +169,29 @@ public class AppSettingsActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadData(Bundle savedInstanceState, Uri appUri) {
|
private void loadData(Bundle savedInstanceState) {
|
||||||
mAppSettings = new ApiDataAccessObject(this).getApiAppSettings(appUri);
|
mApiApp = apiDataAccessObject.getApiApp(packageName);
|
||||||
|
|
||||||
// get application name and icon from package manager
|
// get application name and icon from package manager
|
||||||
String appName;
|
String appName;
|
||||||
Drawable appIcon = null;
|
Drawable appIcon = null;
|
||||||
PackageManager pm = getApplicationContext().getPackageManager();
|
PackageManager pm = getApplicationContext().getPackageManager();
|
||||||
try {
|
try {
|
||||||
ApplicationInfo ai = pm.getApplicationInfo(mAppSettings.getPackageName(), 0);
|
ApplicationInfo ai = pm.getApplicationInfo(mApiApp.package_name(), 0);
|
||||||
|
|
||||||
appName = (String) pm.getApplicationLabel(ai);
|
appName = (String) pm.getApplicationLabel(ai);
|
||||||
appIcon = pm.getApplicationIcon(ai);
|
appIcon = pm.getApplicationIcon(ai);
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
// fallback
|
// fallback
|
||||||
appName = mAppSettings.getPackageName();
|
appName = mApiApp.package_name();
|
||||||
}
|
}
|
||||||
mAppNameView.setText(appName);
|
mAppNameView.setText(appName);
|
||||||
mAppIconView.setImageDrawable(appIcon);
|
mAppIconView.setImageDrawable(appIcon);
|
||||||
|
|
||||||
Uri allowedKeysUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build();
|
startListFragments(savedInstanceState);
|
||||||
Timber.d("allowedKeysUri: " + allowedKeysUri);
|
|
||||||
startListFragments(savedInstanceState, allowedKeysUri);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startListFragments(Bundle savedInstanceState, Uri allowedKeysUri) {
|
private void startListFragments(Bundle savedInstanceState) {
|
||||||
// However, if we're being restored from a previous state,
|
// However, if we're being restored from a previous state,
|
||||||
// then we don't need to do anything and should return or else
|
// then we don't need to do anything and should return or else
|
||||||
// we could end up with overlapping fragments.
|
// we could end up with overlapping fragments.
|
||||||
@@ -199,7 +199,8 @@ public class AppSettingsActivity extends BaseActivity {
|
|||||||
return;
|
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
|
// Add the fragment to the 'fragment_container' FrameLayout
|
||||||
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
@@ -210,9 +211,7 @@ public class AppSettingsActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void revokeAccess() {
|
private void revokeAccess() {
|
||||||
if (getContentResolver().delete(mAppUri, null, null) <= 0) {
|
apiDataAccessObject.deleteApiApp(packageName);
|
||||||
throw new RuntimeException();
|
|
||||||
}
|
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,9 @@ package org.sufficientlysecure.keychain.remote.ui;
|
|||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import android.content.OperationApplicationException;
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.RemoteException;
|
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
import android.support.v4.content.CursorLoader;
|
import android.support.v4.content.CursorLoader;
|
||||||
import android.support.v4.content.Loader;
|
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.KeyAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.KeySelectableAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.KeySelectableAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.widget.FixedListView;
|
import org.sufficientlysecure.keychain.ui.widget.FixedListView;
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
|
|
||||||
public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround implements LoaderManager.LoaderCallbacks<Cursor> {
|
public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
private static final String ARG_DATA_URI = "uri";
|
private static final String ARG_PACKAGE_NAME = "package_name";
|
||||||
|
|
||||||
private KeySelectableAdapter mAdapter;
|
private KeySelectableAdapter mAdapter;
|
||||||
private ApiDataAccessObject mApiDao;
|
private ApiDataAccessObject mApiDao;
|
||||||
|
|
||||||
private Uri mDataUri;
|
private String packageName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new instance of this fragment
|
* Creates new instance of this fragment
|
||||||
*/
|
*/
|
||||||
public static AppSettingsAllowedKeysListFragment newInstance(Uri dataUri) {
|
public static AppSettingsAllowedKeysListFragment newInstance(String packageName) {
|
||||||
AppSettingsAllowedKeysListFragment frag = new AppSettingsAllowedKeysListFragment();
|
AppSettingsAllowedKeysListFragment frag = new AppSettingsAllowedKeysListFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
|
|
||||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
args.putString(ARG_PACKAGE_NAME, packageName);
|
||||||
|
|
||||||
frag.setArguments(args);
|
frag.setArguments(args);
|
||||||
|
|
||||||
@@ -101,13 +98,13 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
|
|||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(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
|
// Give some text to display if there is no data. In a real
|
||||||
// application this would come from a resource.
|
// application this would come from a resource.
|
||||||
setEmptyText(getString(R.string.list_empty));
|
setEmptyText(getString(R.string.list_empty));
|
||||||
|
|
||||||
Set<Long> checked = mApiDao.getAllowedKeyIdsForApp(mDataUri);
|
Set<Long> checked = mApiDao.getAllowedKeyIdsForApp(packageName);
|
||||||
mAdapter = new KeySelectableAdapter(getActivity(), null, 0, checked);
|
mAdapter = new KeySelectableAdapter(getActivity(), null, 0, checked);
|
||||||
setListAdapter(mAdapter);
|
setListAdapter(mAdapter);
|
||||||
getListView().setOnItemClickListener(mAdapter);
|
getListView().setOnItemClickListener(mAdapter);
|
||||||
@@ -140,11 +137,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
|
|||||||
} */
|
} */
|
||||||
|
|
||||||
public void saveAllowedKeys() {
|
public void saveAllowedKeys() {
|
||||||
try {
|
mApiDao.saveAllowedKeyIdsForApp(packageName, getSelectedMasterKeyIds());
|
||||||
mApiDao.saveAllowedKeyIdsForApp(mDataUri, getSelectedMasterKeyIds());
|
|
||||||
} catch (RemoteException | OperationApplicationException e) {
|
|
||||||
Timber.e(e, "Problem saving allowed key ids!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -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 <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.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!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -17,87 +17,77 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.remote.ui;
|
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.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.Resources;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.CursorJoiner;
|
|
||||||
import android.database.MatrixCursor;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.ListFragment;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.support.v4.content.CursorLoader;
|
import android.support.v7.widget.RecyclerView.Adapter;
|
||||||
import android.support.v4.content.Loader;
|
|
||||||
import android.support.v4.widget.CursorAdapter;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.model.ApiApp;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
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;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
|
||||||
public class AppsListFragment extends ListFragment implements
|
public class AppsListFragment extends RecyclerFragment<ApiAppAdapter> {
|
||||||
LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener {
|
private ApiAppAdapter adapter;
|
||||||
|
|
||||||
AppsAdapter mAdapter;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onActivityCreated(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);
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
// Create an empty adapter we will use to display the loaded data.
|
adapter = new ApiAppAdapter(getActivity());
|
||||||
mAdapter = new AppsAdapter(getActivity(), null, 0);
|
setAdapter(adapter);
|
||||||
setListAdapter(mAdapter);
|
setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
|
||||||
|
|
||||||
// NOTE: Loader is started in onResume!
|
new ApiAppsLiveData(getContext()).observe(this, this::onLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void onLoad(List<ListedApp> apiApps) {
|
||||||
public void onResume() {
|
if (apiApps == null) {
|
||||||
super.onResume();
|
hideList(false);
|
||||||
|
adapter.setData(null);
|
||||||
// Start out with a progress indicator.
|
return;
|
||||||
setListShown(false);
|
}
|
||||||
|
adapter.setData(apiApps);
|
||||||
// After coming back from Google Play -> reload
|
showList(true);
|
||||||
getLoaderManager().restartLoader(0, null, this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void onItemClick(int position) {
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
ListedApp listedApp = adapter.data.get(position);
|
||||||
String selectedPackageName = mAdapter.getItemPackageName(position);
|
|
||||||
boolean installed = mAdapter.getItemIsInstalled(position);
|
|
||||||
boolean registered = mAdapter.getItemIsRegistered(position);
|
|
||||||
|
|
||||||
if (installed) {
|
if (listedApp.isInstalled) {
|
||||||
if (registered) {
|
if (listedApp.isRegistered) {
|
||||||
// Edit app settings
|
// Edit app settings
|
||||||
Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
|
Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
|
||||||
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName));
|
intent.putExtra(AppSettingsActivity.EXTRA_PACKAGE_NAME, listedApp.packageName);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
} else {
|
} else {
|
||||||
Intent i;
|
Intent i;
|
||||||
PackageManager manager = getActivity().getPackageManager();
|
PackageManager manager = getActivity().getPackageManager();
|
||||||
try {
|
try {
|
||||||
i = manager.getLaunchIntentForPackage(selectedPackageName);
|
i = manager.getLaunchIntentForPackage(listedApp.packageName);
|
||||||
if (i == null) {
|
if (i == null) {
|
||||||
throw new PackageManager.NameNotFoundException();
|
throw new PackageManager.NameNotFoundException();
|
||||||
}
|
}
|
||||||
@@ -112,256 +102,163 @@ public class AppsListFragment extends ListFragment implements
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
startActivity(new Intent(Intent.ACTION_VIEW,
|
startActivity(new Intent(Intent.ACTION_VIEW,
|
||||||
Uri.parse("market://details?id=" + selectedPackageName)));
|
Uri.parse("market://details?id=" + listedApp.packageName)));
|
||||||
} catch (ActivityNotFoundException anfe) {
|
} catch (ActivityNotFoundException anfe) {
|
||||||
startActivity(new Intent(Intent.ACTION_VIEW,
|
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";
|
public class ApiAppAdapter extends Adapter<ApiAppViewHolder> {
|
||||||
private static final String TEMP_COLUMN_INSTALLED = "INSTALLED";
|
private final LayoutInflater inflater;
|
||||||
private static final String TEMP_COLUMN_REGISTERED = "REGISTERED";
|
|
||||||
private static final String TEMP_COLUMN_ICON_RES_ID = "ICON_RES_ID";
|
|
||||||
|
|
||||||
static final String[] PROJECTION = new String[]{
|
private List<ListedApp> data;
|
||||||
ApiApps._ID, // 0
|
|
||||||
ApiApps.PACKAGE_NAME, // 1
|
ApiAppAdapter(Context context) {
|
||||||
"null as " + TEMP_COLUMN_NAME, // installed apps can retrieve app name from Android OS
|
super();
|
||||||
"0 as " + TEMP_COLUMN_INSTALLED, // changed later in cursor joiner
|
|
||||||
"1 as " + TEMP_COLUMN_REGISTERED, // if it is in db it is registered
|
inflater = LayoutInflater.from(context);
|
||||||
"0 as " + TEMP_COLUMN_ICON_RES_ID // not used
|
}
|
||||||
|
|
||||||
|
@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<ListedApp> 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<List<ListedApp>> {
|
||||||
|
private final ApiDataAccessObject apiDao;
|
||||||
|
private final PackageManager packageManager;
|
||||||
|
|
||||||
|
ApiAppsLiveData(Context context) {
|
||||||
|
super(context, null);
|
||||||
|
|
||||||
|
packageManager = getContext().getPackageManager();
|
||||||
|
apiDao = new ApiDataAccessObject(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<ListedApp> asyncLoadData() {
|
||||||
|
ArrayList<ListedApp> result = new ArrayList<>();
|
||||||
|
|
||||||
|
loadRegisteredApps(result);
|
||||||
|
addPlaceholderApps(result);
|
||||||
|
|
||||||
|
Collections.sort(result, (o1, o2) -> o1.readableName.compareTo(o2.readableName));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadRegisteredApps(ArrayList<ListedApp> result) {
|
||||||
|
List<ApiApp> 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<ListedApp> 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<ListedApp> 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<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 = 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<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);
|
|
||||||
|
|
||||||
// The list should now be shown.
|
|
||||||
setListShown(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ import android.content.pm.PackageManager.NameNotFoundException;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.model.ApiApp;
|
||||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ class RemoteRegisterPresenter {
|
|||||||
|
|
||||||
private RemoteRegisterView view;
|
private RemoteRegisterView view;
|
||||||
private Intent resultData;
|
private Intent resultData;
|
||||||
private AppSettings appSettings;
|
private ApiApp apiApp;
|
||||||
|
|
||||||
|
|
||||||
RemoteRegisterPresenter(Context context) {
|
RemoteRegisterPresenter(Context context) {
|
||||||
@@ -54,7 +54,7 @@ class RemoteRegisterPresenter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setupFromIntentData(Intent resultData, String packageName, byte[] packageSignature) {
|
void setupFromIntentData(Intent resultData, String packageName, byte[] packageSignature) {
|
||||||
this.appSettings = new AppSettings(packageName, packageSignature);
|
this.apiApp = ApiApp.create(packageName, packageSignature);
|
||||||
this.resultData = resultData;
|
this.resultData = resultData;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -76,7 +76,7 @@ class RemoteRegisterPresenter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onClickAllow() {
|
void onClickAllow() {
|
||||||
apiDao.insertApiApp(appSettings);
|
apiDao.insertApiApp(apiApp);
|
||||||
view.finishWithResult(resultData);
|
view.finishWithResult(resultData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import timber.log.Timber;
|
|||||||
|
|
||||||
public class SelectSignKeyIdActivity extends BaseActivity {
|
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_USER_ID = OpenPgpApi.EXTRA_USER_ID;
|
||||||
public static final String EXTRA_DATA = "data";
|
public static final String EXTRA_DATA = "data";
|
||||||
|
|
||||||
@@ -68,19 +69,18 @@ public class SelectSignKeyIdActivity extends BaseActivity {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
Uri appUri = intent.getData();
|
String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
|
||||||
mPreferredUserId = intent.getStringExtra(EXTRA_USER_ID);
|
mPreferredUserId = intent.getStringExtra(EXTRA_USER_ID);
|
||||||
mData = intent.getParcelableExtra(EXTRA_DATA);
|
mData = intent.getParcelableExtra(EXTRA_DATA);
|
||||||
if (appUri == null) {
|
if (packageName == null) {
|
||||||
Timber.e("Intent data missing. Should be Uri of app!");
|
Timber.e("Intent data missing. Should be Uri of app!");
|
||||||
finish();
|
finish();
|
||||||
} else {
|
} else {
|
||||||
Timber.d("uri: " + appUri);
|
startListFragments(savedInstanceState, packageName, mData, mPreferredUserId);
|
||||||
startListFragments(savedInstanceState, appUri, 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,
|
// However, if we're being restored from a previous state,
|
||||||
// then we don't need to do anything and should return or else
|
// then we don't need to do anything and should return or else
|
||||||
// we could end up with overlapping fragments.
|
// we could end up with overlapping fragments.
|
||||||
@@ -90,7 +90,7 @@ public class SelectSignKeyIdActivity extends BaseActivity {
|
|||||||
|
|
||||||
// Create an instance of the fragments
|
// Create an instance of the fragments
|
||||||
SelectSignKeyIdListFragment listFragment = SelectSignKeyIdListFragment
|
SelectSignKeyIdListFragment listFragment = SelectSignKeyIdListFragment
|
||||||
.newInstance(dataUri, data, preferredUserId);
|
.newInstance(packageName, data, preferredUserId);
|
||||||
// Add the fragment to the 'fragment_container' FrameLayout
|
// Add the fragment to the 'fragment_container' FrameLayout
|
||||||
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
|||||||
@@ -33,35 +33,33 @@ import org.openintents.openpgp.util.OpenPgpUtils;
|
|||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.remote.ui.adapter.SelectSignKeyAdapter;
|
import org.sufficientlysecure.keychain.remote.ui.adapter.SelectSignKeyAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
||||||
import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter;
|
|
||||||
import org.sufficientlysecure.keychain.ui.base.RecyclerFragment;
|
import org.sufficientlysecure.keychain.ui.base.RecyclerFragment;
|
||||||
import timber.log.Timber;
|
import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter;
|
||||||
|
|
||||||
|
|
||||||
public class SelectSignKeyIdListFragment extends RecyclerFragment<SelectSignKeyAdapter>
|
public class SelectSignKeyIdListFragment extends RecyclerFragment<SelectSignKeyAdapter>
|
||||||
implements SelectSignKeyAdapter.SelectSignKeyListener, LoaderManager.LoaderCallbacks<Cursor> {
|
implements SelectSignKeyAdapter.SelectSignKeyListener, LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
|
|
||||||
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";
|
private static final String ARG_PREF_UID = "pref_uid";
|
||||||
public static final String ARG_DATA = "data";
|
public static final String ARG_DATA = "data";
|
||||||
|
|
||||||
private Uri mDataUri;
|
|
||||||
private Intent mResult;
|
private Intent mResult;
|
||||||
private String mPrefUid;
|
private String mPrefUid;
|
||||||
private ApiDataAccessObject mApiDao;
|
private ApiDataAccessObject mApiDao;
|
||||||
|
private String mPackageName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new instance of this fragment
|
* 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();
|
SelectSignKeyIdListFragment frag = new SelectSignKeyIdListFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
|
|
||||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
args.putString(ARG_PACKAGE_NAME, packageName);
|
||||||
args.putParcelable(ARG_DATA, data);
|
args.putParcelable(ARG_DATA, data);
|
||||||
args.putString(ARG_PREF_UID, preferredUserId);
|
args.putString(ARG_PREF_UID, preferredUserId);
|
||||||
|
|
||||||
@@ -85,7 +83,7 @@ public class SelectSignKeyIdListFragment extends RecyclerFragment<SelectSignKeyA
|
|||||||
|
|
||||||
mResult = getArguments().getParcelable(ARG_DATA);
|
mResult = getArguments().getParcelable(ARG_DATA);
|
||||||
mPrefUid = getArguments().getString(ARG_PREF_UID);
|
mPrefUid = getArguments().getString(ARG_PREF_UID);
|
||||||
mDataUri = getArguments().getParcelable(ARG_DATA_URI);
|
mPackageName = getArguments().getString(ARG_PACKAGE_NAME);
|
||||||
|
|
||||||
// Give some text to display if there is no data. In a real
|
// Give some text to display if there is no data. In a real
|
||||||
// application this would come from a resource.
|
// application this would come from a resource.
|
||||||
@@ -175,16 +173,9 @@ public class SelectSignKeyIdListFragment extends RecyclerFragment<SelectSignKeyA
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSelectKeyItemClicked(long masterKeyId) {
|
public void onSelectKeyItemClicked(long masterKeyId) {
|
||||||
Uri allowedKeysUri = mDataUri.buildUpon()
|
mApiDao.addAllowedKeyIdForApp(mPackageName, masterKeyId);
|
||||||
.appendPath(KeychainContract.PATH_ALLOWED_KEYS)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
mApiDao.addAllowedKeyIdForApp(allowedKeysUri, masterKeyId);
|
|
||||||
mResult.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, masterKeyId);
|
mResult.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, masterKeyId);
|
||||||
|
|
||||||
Timber.d("allowedKeyId: " + masterKeyId);
|
|
||||||
Timber.d("allowedKeysUri: " + allowedKeysUri);
|
|
||||||
|
|
||||||
getActivity().setResult(Activity.RESULT_OK, mResult);
|
getActivity().setResult(Activity.RESULT_OK, mResult);
|
||||||
getActivity().finish();
|
getActivity().finish();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
package org.sufficientlysecure.keychain.remote.ui.dialog;
|
package org.sufficientlysecure.keychain.remote.ui.dialog;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
@@ -27,7 +29,6 @@ import android.content.Intent;
|
|||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.Drawable.ConstantState;
|
import android.graphics.drawable.Drawable.ConstantState;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.app.DialogFragment;
|
import android.support.v4.app.DialogFragment;
|
||||||
@@ -48,21 +49,17 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.mikepenz.materialdrawer.util.KeyboardUtil;
|
import com.mikepenz.materialdrawer.util.KeyboardUtil;
|
||||||
|
|
||||||
import org.openintents.ssh.authentication.SshAuthenticationApi;
|
import org.openintents.ssh.authentication.SshAuthenticationApi;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
|
||||||
import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityTokenOperationActivity;
|
|
||||||
import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo;
|
import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||||
|
import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityTokenOperationActivity;
|
||||||
import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteSelectAuthenticationKeyPresenter.RemoteSelectAuthenticationKeyView;
|
import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteSelectAuthenticationKeyPresenter.RemoteSelectAuthenticationKeyView;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
|
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
|
||||||
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
||||||
import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration;
|
import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration;
|
||||||
import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener;
|
import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
|
||||||
public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity {
|
public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity {
|
||||||
public static final String EXTRA_PACKAGE_NAME = "package_name";
|
public static final String EXTRA_PACKAGE_NAME = "package_name";
|
||||||
@@ -71,6 +68,7 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity {
|
|||||||
|
|
||||||
|
|
||||||
private RemoteSelectAuthenticationKeyPresenter presenter;
|
private RemoteSelectAuthenticationKeyPresenter presenter;
|
||||||
|
private String packageName;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -92,8 +90,7 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity {
|
|||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
|
packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
|
||||||
|
|
||||||
|
|
||||||
presenter.setupFromIntentData(packageName);
|
presenter.setupFromIntentData(packageName);
|
||||||
presenter.startLoaders(getSupportLoaderManager());
|
presenter.startLoaders(getSupportLoaderManager());
|
||||||
@@ -104,14 +101,8 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity {
|
|||||||
Intent originalIntent = callingIntent.getParcelableExtra(
|
Intent originalIntent = callingIntent.getParcelableExtra(
|
||||||
RemoteSecurityTokenOperationActivity.EXTRA_DATA);
|
RemoteSecurityTokenOperationActivity.EXTRA_DATA);
|
||||||
|
|
||||||
Uri appUri = callingIntent.getData();
|
|
||||||
|
|
||||||
Uri allowedKeysUri = appUri.buildUpon()
|
|
||||||
.appendPath(KeychainContract.PATH_ALLOWED_KEYS)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
ApiDataAccessObject apiDao = new ApiDataAccessObject(getBaseContext());
|
ApiDataAccessObject apiDao = new ApiDataAccessObject(getBaseContext());
|
||||||
apiDao.addAllowedKeyIdForApp(allowedKeysUri, masterKeyId);
|
apiDao.addAllowedKeyIdForApp(packageName, masterKeyId);
|
||||||
|
|
||||||
originalIntent.putExtra(SshAuthenticationApi.EXTRA_KEY_ID, String.valueOf(masterKeyId));
|
originalIntent.putExtra(SshAuthenticationApi.EXTRA_KEY_ID, String.valueOf(masterKeyId));
|
||||||
|
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ import org.openintents.openpgp.util.OpenPgpUtils.UserId;
|
|||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo;
|
import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo;
|
||||||
import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeySelector;
|
import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeySelector;
|
||||||
|
import org.sufficientlysecure.keychain.model.ApiApp;
|
||||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
|
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
|
||||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
|
||||||
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
@@ -58,7 +58,7 @@ class RemoteSelectIdentityKeyPresenter {
|
|||||||
private long selectedMasterKeyId;
|
private long selectedMasterKeyId;
|
||||||
private byte[] generatedKeyData;
|
private byte[] generatedKeyData;
|
||||||
private ApiDataAccessObject apiDao;
|
private ApiDataAccessObject apiDao;
|
||||||
private AppSettings appSettings;
|
private ApiApp apiApp;
|
||||||
|
|
||||||
|
|
||||||
RemoteSelectIdentityKeyPresenter(Context context, RemoteSelectIdViewModel viewModel, LifecycleOwner lifecycleOwner) {
|
RemoteSelectIdentityKeyPresenter(Context context, RemoteSelectIdViewModel viewModel, LifecycleOwner lifecycleOwner) {
|
||||||
@@ -103,7 +103,7 @@ class RemoteSelectIdentityKeyPresenter {
|
|||||||
Drawable appIcon = packageManager.getApplicationIcon(applicationInfo);
|
Drawable appIcon = packageManager.getApplicationIcon(applicationInfo);
|
||||||
CharSequence appLabel = packageManager.getApplicationLabel(applicationInfo);
|
CharSequence appLabel = packageManager.getApplicationLabel(applicationInfo);
|
||||||
|
|
||||||
appSettings = new AppSettings(packageName, packageSignature);
|
apiApp = ApiApp.create(packageName, packageSignature);
|
||||||
|
|
||||||
view.setTitleClientIconAndName(appIcon, appLabel);
|
view.setTitleClientIconAndName(appIcon, appLabel);
|
||||||
}
|
}
|
||||||
@@ -200,15 +200,15 @@ class RemoteSelectIdentityKeyPresenter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onHighlightFinished() {
|
void onHighlightFinished() {
|
||||||
apiDao.insertApiApp(appSettings);
|
apiDao.insertApiApp(apiApp);
|
||||||
apiDao.addAllowedKeyIdForApp(appSettings.getPackageName(), selectedMasterKeyId);
|
apiDao.addAllowedKeyIdForApp(apiApp.package_name(), selectedMasterKeyId);
|
||||||
view.finishAndReturn(selectedMasterKeyId);
|
view.finishAndReturn(selectedMasterKeyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onImportOpSuccess(ImportKeyResult result) {
|
void onImportOpSuccess(ImportKeyResult result) {
|
||||||
long importedMasterKeyId = result.getImportedMasterKeyIds()[0];
|
long importedMasterKeyId = result.getImportedMasterKeyIds()[0];
|
||||||
apiDao.insertApiApp(appSettings);
|
apiDao.insertApiApp(apiApp);
|
||||||
apiDao.addAllowedKeyIdForApp(appSettings.getPackageName(), selectedMasterKeyId);
|
apiDao.addAllowedKeyIdForApp(apiApp.package_name(), selectedMasterKeyId);
|
||||||
view.finishAndReturn(importedMasterKeyId);
|
view.finishAndReturn(importedMasterKeyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,9 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.util;
|
package org.sufficientlysecure.keychain.util;
|
||||||
|
|
||||||
|
|
||||||
|
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
@@ -56,8 +57,8 @@ public class DatabaseUtil {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void explainQuery(SQLiteDatabase db, String sql) {
|
public static void explainQuery(SupportSQLiteDatabase db, String sql) {
|
||||||
Cursor explainCursor = db.rawQuery("EXPLAIN QUERY PLAN " + sql, new String[0]);
|
Cursor explainCursor = db.query("EXPLAIN QUERY PLAN " + sql, new String[0]);
|
||||||
|
|
||||||
// this is a debugging feature, we can be a little careless
|
// this is a debugging feature, we can be a little careless
|
||||||
explainCursor.moveToFirst();
|
explainCursor.moveToFirst();
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS api_allowed_keys (
|
||||||
|
_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
key_id INTEGER,
|
||||||
|
package_name TEXT NOT NULL,
|
||||||
|
UNIQUE (key_id, package_name),
|
||||||
|
FOREIGN KEY (package_name) REFERENCES api_apps (package_name) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
insertAllowedKey:
|
||||||
|
INSERT INTO api_allowed_keys (package_name, key_id) VALUES (?, ?);
|
||||||
|
|
||||||
|
deleteByPackageName:
|
||||||
|
DELETE FROM api_allowed_keys
|
||||||
|
WHERE package_name = ?;
|
||||||
|
|
||||||
|
getAllowedKeys:
|
||||||
|
SELECT key_id
|
||||||
|
FROM api_allowed_keys
|
||||||
|
WHERE package_name = ?;
|
||||||
@@ -4,5 +4,23 @@ CREATE TABLE IF NOT EXISTS api_apps (
|
|||||||
package_signature BLOB
|
package_signature BLOB
|
||||||
);
|
);
|
||||||
|
|
||||||
getAllowedKeys:
|
insertApiApp:
|
||||||
SELECT
|
INSERT INTO api_apps (package_name, package_signature) VALUES (?, ?);
|
||||||
|
|
||||||
|
selectAll:
|
||||||
|
SELECT *
|
||||||
|
FROM api_apps;
|
||||||
|
|
||||||
|
selectByPackageName:
|
||||||
|
SELECT *
|
||||||
|
FROM api_apps
|
||||||
|
WHERE package_name = ?;
|
||||||
|
|
||||||
|
deleteByPackageName:
|
||||||
|
DELETE FROM api_apps
|
||||||
|
WHERE package_name = ?;
|
||||||
|
|
||||||
|
getCertificate:
|
||||||
|
SELECT package_signature
|
||||||
|
FROM api_apps
|
||||||
|
WHERE package_name = ?;
|
||||||
@@ -18,6 +18,7 @@ import org.robolectric.shadows.ShadowBinder;
|
|||||||
import org.robolectric.shadows.ShadowLog;
|
import org.robolectric.shadows.ShadowLog;
|
||||||
import org.robolectric.shadows.ShadowPackageManager;
|
import org.robolectric.shadows.ShadowPackageManager;
|
||||||
import org.sufficientlysecure.keychain.KeychainTestRunner;
|
import org.sufficientlysecure.keychain.KeychainTestRunner;
|
||||||
|
import org.sufficientlysecure.keychain.model.ApiApp;
|
||||||
import org.sufficientlysecure.keychain.operations.CertifyOperation;
|
import org.sufficientlysecure.keychain.operations.CertifyOperation;
|
||||||
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
|
import org.sufficientlysecure.keychain.operations.results.CertifyResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
||||||
@@ -82,7 +83,7 @@ public class KeychainExternalProviderTest {
|
|||||||
apiPermissionHelper = new ApiPermissionHelper(RuntimeEnvironment.application, apiDao);
|
apiPermissionHelper = new ApiPermissionHelper(RuntimeEnvironment.application, apiDao);
|
||||||
autocryptPeerDao = new AutocryptPeerDataAccessObject(RuntimeEnvironment.application, PACKAGE_NAME);
|
autocryptPeerDao = new AutocryptPeerDataAccessObject(RuntimeEnvironment.application, PACKAGE_NAME);
|
||||||
|
|
||||||
apiDao.insertApiApp(new AppSettings(PACKAGE_NAME, PACKAGE_SIGNATURE));
|
apiDao.insertApiApp(new ApiApp(PACKAGE_NAME, PACKAGE_SIGNATURE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = AccessControlException.class)
|
@Test(expected = AccessControlException.class)
|
||||||
@@ -99,7 +100,7 @@ public class KeychainExternalProviderTest {
|
|||||||
@Test(expected = AccessControlException.class)
|
@Test(expected = AccessControlException.class)
|
||||||
public void testPermission__withWrongPackageCert() throws Exception {
|
public void testPermission__withWrongPackageCert() throws Exception {
|
||||||
apiDao.deleteApiApp(PACKAGE_NAME);
|
apiDao.deleteApiApp(PACKAGE_NAME);
|
||||||
apiDao.insertApiApp(new AppSettings(PACKAGE_NAME, new byte[] { 1, 2, 4 }));
|
apiDao.insertApiApp(new ApiApp(PACKAGE_NAME, new byte[] { 1, 2, 4 }));
|
||||||
|
|
||||||
contentResolver.query(
|
contentResolver.query(
|
||||||
EmailStatus.CONTENT_URI,
|
EmailStatus.CONTENT_URI,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ buildscript {
|
|||||||
classpath files('gradle-witness.jar')
|
classpath files('gradle-witness.jar')
|
||||||
// bintray dependency to satisfy dependency of openpgp-api lib
|
// bintray dependency to satisfy dependency of openpgp-api lib
|
||||||
classpath 'com.novoda:bintray-release:0.8.0'
|
classpath 'com.novoda:bintray-release:0.8.0'
|
||||||
|
classpath 'com.squareup.sqldelight:gradle-plugin:0.7.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user