use external provider for resolving encryption keys in api

This commit is contained in:
Vincent Breitmoser
2017-04-04 16:37:34 +02:00
parent 8738862f09
commit 16a0045f9d
6 changed files with 312 additions and 240 deletions

View File

@@ -37,8 +37,9 @@ public class KeychainExternalContract {
public static class EmailStatus implements BaseColumns {
public static final String EMAIL_ADDRESS = "email_address";
public static final String EMAIL_STATUS = "email_status";
public static final String USER_ID = "user_id";
public static final String USER_ID_STATUS = "email_status";
public static final String MASTER_KEY_ID = "master_key_id";
public static final Uri CONTENT_URI = BASE_CONTENT_URI_EXTERNAL.buildUpon()
.appendPath(BASE_EMAIL_STATUS).build();

View File

@@ -34,6 +34,7 @@ import android.os.Binder;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import org.sufficientlysecure.keychain.BuildConfig;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
import org.sufficientlysecure.keychain.provider.KeychainContract;
@@ -50,6 +51,7 @@ import org.sufficientlysecure.keychain.util.Log;
public class KeychainExternalProvider extends ContentProvider implements SimpleContentResolverInterface {
private static final int EMAIL_STATUS = 101;
private static final int EMAIL_STATUS_INTERNAL = 102;
private static final int API_APPS = 301;
private static final int API_APPS_BY_PACKAGE_NAME = 302;
@@ -78,8 +80,9 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
* </pre>
*/
matcher.addURI(authority, KeychainExternalContract.BASE_EMAIL_STATUS, EMAIL_STATUS);
matcher.addURI(authority, KeychainExternalContract.BASE_EMAIL_STATUS + "/*", EMAIL_STATUS_INTERNAL);
matcher.addURI(KeychainContract.CONTENT_AUTHORITY, KeychainContract.BASE_API_APPS, API_APPS);
// can only query status of calling app - for internal use only!
matcher.addURI(KeychainContract.CONTENT_AUTHORITY, KeychainContract.BASE_API_APPS + "/*", API_APPS_BY_PACKAGE_NAME);
return matcher;
@@ -134,9 +137,19 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
SQLiteDatabase db = getDb().getReadableDatabase();
String callingPackageName = mApiPermissionHelper.getCurrentCallingPackage();
switch (match) {
case EMAIL_STATUS_INTERNAL:
if (!BuildConfig.APPLICATION_ID.equals(callingPackageName)) {
throw new AccessControlException("This URI can only be called internally!");
}
// override package name to use any external
// callingPackageName = uri.getLastPathSegment();
case EMAIL_STATUS: {
boolean callerIsAllowed = mApiPermissionHelper.isAllowedIgnoreErrors();
boolean callerIsAllowed = (match == EMAIL_STATUS_INTERNAL) || mApiPermissionHelper.isAllowedIgnoreErrors();
if (!callerIsAllowed) {
throw new AccessControlException("An application must register before use of KeychainExternalProvider!");
}
@@ -152,14 +165,19 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
projectionMap.put(EmailStatus._ID, "email AS _id");
projectionMap.put(EmailStatus.EMAIL_ADDRESS, // this is actually the queried address
TEMP_TABLE_QUERIED_ADDRESSES + "." + TEMP_TABLE_COLUMN_ADDRES + " AS " + EmailStatus.EMAIL_ADDRESS);
projectionMap.put(EmailStatus.USER_ID,
Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.USER_ID);
// we take the minimum (>0) here, where "1" is "verified by known secret key", "2" is "self-certified"
projectionMap.put(EmailStatus.EMAIL_STATUS, "CASE ( MIN (" + Certs.VERIFIED + " ) ) "
projectionMap.put(EmailStatus.USER_ID_STATUS, "CASE ( MIN (" + Certs.VERIFIED + " ) ) "
// remap to keep this provider contract independent from our internal representation
+ " WHEN NULL THEN 1"
+ " WHEN " + Certs.VERIFIED_SELF + " THEN 1"
+ " WHEN " + Certs.VERIFIED_SECRET + " THEN 2"
+ " END AS " + EmailStatus.EMAIL_STATUS);
+ " WHEN NULL THEN NULL"
+ " END AS " + EmailStatus.USER_ID_STATUS);
projectionMap.put(EmailStatus.USER_ID, Tables.USER_PACKETS + "." + UserPackets.USER_ID + " AS " + EmailStatus.USER_ID);
projectionMap.put(EmailStatus.MASTER_KEY_ID,
Tables.USER_PACKETS + "." + UserPackets.MASTER_KEY_ID + " AS " + EmailStatus.MASTER_KEY_ID);
qb.setProjectionMap(projectionMap);
if (projection == null) {

View File

@@ -66,6 +66,7 @@ import org.sufficientlysecure.keychain.provider.KeyRepository;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult;
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResultStatus;
import org.sufficientlysecure.keychain.service.BackupKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
@@ -213,51 +214,52 @@ public class OpenPgpService extends Service {
compressionId = PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.UNCOMPRESSED;
}
KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromIntent(data, false);
if (keyIdResult.hasResultIntent()) {
return keyIdResult.getResultIntent();
}
long[] keyIds = keyIdResult.getKeyIds();
// TODO this is not correct!
long inputLength = inputStream.available();
InputData inputData = new InputData(inputStream, inputLength, originalFilename);
PgpSignEncryptData pgpData = new PgpSignEncryptData();
pgpData.setEnableAsciiArmorOutput(asciiArmor)
.setVersionHeader(null)
.setCompressionAlgorithm(compressionId)
.setSymmetricEncryptionAlgorithm(PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT)
.setEncryptionMasterKeyIds(keyIds);
.setCompressionAlgorithm(compressionId);
if (sign) {
Intent signKeyIdIntent = getSignKeyMasterId(data);
// NOTE: Fallback to return account settings (Old API)
if (signKeyIdIntent.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)
== OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED) {
!= OpenPgpApi.RESULT_CODE_SUCCESS) {
return signKeyIdIntent;
}
long signKeyId = signKeyIdIntent.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
if (signKeyId == Constants.key.none) {
throw new Exception("No signing key given");
} else {
pgpData.setSignatureMasterKeyId(signKeyId);
}
long signSubKeyId = mKeyRepository.getCachedPublicKeyRing(signKeyId).getSecretSignId();
// get first usable subkey capable of signing
try {
long signSubKeyId = mKeyRepository.getCachedPublicKeyRing(
pgpData.getSignatureMasterKeyId()).getSecretSignId();
pgpData.setSignatureSubKeyId(signSubKeyId);
} catch (PgpKeyNotFoundException e) {
throw new Exception("signing subkey not found!", e);
}
pgpData.setSignatureMasterKeyId(signKeyId)
.setSignatureSubKeyId(signSubKeyId)
.setAdditionalEncryptId(signKeyId);
}
KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromIntent(data, false,
mApiPermissionHelper.getCurrentCallingPackage());
boolean isOpportunistic = data.getBooleanExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, false);
KeyIdResultStatus keyIdResultStatus = keyIdResult.getStatus();
if (keyIdResult.hasKeySelectionPendingIntent()) {
if ((keyIdResultStatus == KeyIdResultStatus.MISSING || keyIdResultStatus == KeyIdResultStatus.NO_KEYS ||
keyIdResultStatus == KeyIdResultStatus.NO_KEYS_ERROR) && isOpportunistic) {
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_ERROR,
new OpenPgpError(OpenPgpError.OPPORTUNISTIC_MISSING_KEYS, "missing keys in opportunistic mode"));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return result;
}
// sign and encrypt
pgpData.setSignatureHashAlgorithm(PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT)
.setAdditionalEncryptId(signKeyId); // add sign key for encryption
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
result.putExtra(OpenPgpApi.RESULT_INTENT, keyIdResult.getKeySelectionPendingIntent());
return result;
}
pgpData.setEncryptionMasterKeyIds(keyIdResult.getKeyIds());
PgpSignEncryptInputParcel pseInput = new PgpSignEncryptInputParcel(pgpData);
pseInput.setAllowedKeyIds(getAllowedKeyIds());
@@ -268,13 +270,15 @@ public class OpenPgpService extends Service {
}
// override passphrase in input parcel if given by API call
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
inputParcel.mPassphrase =
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
inputParcel.mPassphrase = new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE));
}
PgpSignEncryptOperation op = new PgpSignEncryptOperation(this, mKeyRepository, null);
// TODO this is not correct!
long inputLength = inputStream.available();
InputData inputData = new InputData(inputStream, inputLength, originalFilename);
// execute PGP operation!
PgpSignEncryptOperation op = new PgpSignEncryptOperation(this, mKeyRepository, null);
PgpSignEncryptResult pgpResult = op.execute(pseInput, inputParcel, inputData, outputStream);
if (pgpResult.isPending()) {
@@ -544,8 +548,7 @@ public class OpenPgpService extends Service {
// if data already contains EXTRA_SIGN_KEY_ID, it has been executed again
// after user interaction. Then, we just need to return the long again!
if (data.hasExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID)) {
long signKeyId = data.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID,
Constants.key.none);
long signKeyId = data.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, Constants.key.none);
Intent result = new Intent();
result.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, signKeyId);
@@ -567,9 +570,13 @@ public class OpenPgpService extends Service {
}
private Intent getKeyIdsImpl(Intent data) {
KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromIntent(data, true);
if (keyIdResult.hasResultIntent()) {
return keyIdResult.getResultIntent();
KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromIntent(data, true,
mApiPermissionHelper.getCurrentCallingPackage());
if (keyIdResult.hasKeySelectionPendingIntent()) {
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_INTENT, keyIdResult.getKeySelectionPendingIntent());
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
return result;
}
long[] keyIds = keyIdResult.getKeyIds();

View File

@@ -2,7 +2,9 @@ package org.sufficientlysecure.keychain.remote;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import android.app.PendingIntent;
import android.content.ContentResolver;
@@ -11,31 +13,23 @@ import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.VisibleForTesting;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpUtils;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
class OpenPgpServiceKeyIdExtractor {
@VisibleForTesting
static final String[] KEY_SEARCH_PROJECTION = new String[]{
KeyRings._ID,
KeyRings.MASTER_KEY_ID,
KeyRings.IS_EXPIRED, // referenced in where clause!
KeyRings.IS_REVOKED, // referenced in where clause!
static final String[] PROJECTION_KEY_SEARCH = {
"email_address",
"master_key_id",
"email_status",
};
private static final int INDEX_EMAIL_ADDRESS = 0;
private static final int INDEX_MASTER_KEY_ID = 1;
// do not pre-select revoked or expired keys
private static final String KEY_SEARCH_WHERE = Tables.KEYS + "." + KeychainContract.KeyRings.IS_REVOKED
+ " = 0 AND " + KeychainContract.KeyRings.IS_EXPIRED + " = 0";
private static final int INDEX_EMAIL_STATUS = 2;
private final ApiPendingIntentFactory apiPendingIntentFactory;
@@ -53,150 +47,223 @@ class OpenPgpServiceKeyIdExtractor {
}
KeyIdResult returnKeyIdsFromIntent(Intent data, boolean askIfNoUserIdsProvided) {
HashSet<Long> encryptKeyIds = new HashSet<>();
KeyIdResult returnKeyIdsFromIntent(Intent data, boolean askIfNoUserIdsProvided, String callingPackageName) {
boolean hasKeysFromSelectPubkeyActivity = data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS_SELECTED);
KeyIdResult result;
if (hasKeysFromSelectPubkeyActivity) {
HashSet<Long> encryptKeyIds = new HashSet<>();
for (long keyId : data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS_SELECTED)) {
encryptKeyIds.add(keyId);
}
result = createKeysOkResult(encryptKeyIds, false);
} else if (data.hasExtra(OpenPgpApi.EXTRA_USER_IDS) || askIfNoUserIdsProvided) {
String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS);
boolean isOpportunistic = data.getBooleanExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, false);
KeyIdResult result = returnKeyIdsFromEmails(data, userIds, isOpportunistic);
if (result.mResultIntent != null) {
return result;
}
encryptKeyIds.addAll(result.mKeyIds);
result = returnKeyIdsFromEmails(data, userIds, callingPackageName);
} else {
result = createNoKeysResult();
}
// add key ids from non-ambiguous key id extra
if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
HashSet<Long> explicitKeyIds = new HashSet<>();
for (long keyId : data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
encryptKeyIds.add(keyId);
explicitKeyIds.add(keyId);
}
result = result.withExplicitKeyIds(explicitKeyIds);
}
if (encryptKeyIds.isEmpty()) {
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_ERROR,
new OpenPgpError(OpenPgpError.NO_USER_IDS, "No encryption keys or user ids specified!" +
"(pass empty user id array to get dialog without preselection)"));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return new KeyIdResult(result);
}
return new KeyIdResult(encryptKeyIds);
return result;
}
private KeyIdResult returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds, boolean isOpportunistic) {
private KeyIdResult returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds, String callingPackageName) {
boolean hasUserIds = (encryptionUserIds != null && encryptionUserIds.length > 0);
boolean anyKeyNotVerified = false;
HashSet<Long> keyIds = new HashSet<>();
ArrayList<String> missingEmails = new ArrayList<>();
ArrayList<String> duplicateEmails = new ArrayList<>();
HashMap<String,KeyRow> keyRows = new HashMap<>();
if (hasUserIds) {
for (String rawUserId : encryptionUserIds) {
OpenPgpUtils.UserId userId = KeyRing.splitUserId(rawUserId);
String email = userId.email != null ? userId.email : rawUserId;
// try to find the key for this specific email
Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email);
Cursor cursor = contentResolver.query(uri, KEY_SEARCH_PROJECTION, KEY_SEARCH_WHERE, null, null);
if (cursor == null) {
throw new IllegalStateException("Internal error, received null cursor!");
}
try {
// result should be one entry containing the key id
if (cursor.moveToFirst()) {
long id = cursor.getLong(INDEX_MASTER_KEY_ID);
keyIds.add(id);
Uri queryUri = EmailStatus.CONTENT_URI.buildUpon().appendPath(callingPackageName).build();
Cursor cursor = contentResolver.query(queryUri, PROJECTION_KEY_SEARCH, null, encryptionUserIds, null);
if (cursor == null) {
throw new IllegalStateException("Internal error, received null cursor!");
}
// another entry for this email -> two keys with the same email inside user id
if (!cursor.isLast()) {
Log.d(Constants.TAG, "more than one user id with the same email");
duplicateEmails.add(email);
try {
while (cursor.moveToNext()) {
String queryAddress = cursor.getString(INDEX_EMAIL_ADDRESS);
Long masterKeyId = cursor.isNull(INDEX_MASTER_KEY_ID) ? null : cursor.getLong(INDEX_MASTER_KEY_ID);
int verified = cursor.getInt(INDEX_EMAIL_STATUS);
// also pre-select
while (cursor.moveToNext()) {
long duplicateId = cursor.getLong(INDEX_MASTER_KEY_ID);
keyIds.add(duplicateId);
}
}
} else {
missingEmails.add(email);
Log.d(Constants.TAG, "user id missing");
KeyRow row = new KeyRow(masterKeyId, verified == 2);
if (!keyRows.containsKey(queryAddress)) {
keyRows.put(queryAddress, row);
continue;
}
KeyRow previousRow = keyRows.get(queryAddress);
if (previousRow.masterKeyId == null) {
keyRows.put(queryAddress, row);
} else if (!previousRow.verified && row.verified) {
keyRows.put(queryAddress, row);
} else if (previousRow.verified == row.verified) {
previousRow.hasDuplicate = true;
}
} finally {
cursor.close();
}
} finally {
cursor.close();
}
for (Entry<String, KeyRow> entry : keyRows.entrySet()) {
String queriedAddress = entry.getKey();
KeyRow keyRow = entry.getValue();
if (keyRow.masterKeyId == null) {
missingEmails.add(queriedAddress);
continue;
}
keyIds.add(keyRow.masterKeyId);
if (keyRow.hasDuplicate) {
duplicateEmails.add(queriedAddress);
}
if (!keyRow.verified) {
anyKeyNotVerified = true;
}
}
if (keyRows.size() != encryptionUserIds.length) {
Log.e(Constants.TAG, "Number of rows doesn't match number of retrieved rows! Probably a bug?");
}
}
boolean hasMissingUserIds = !missingEmails.isEmpty();
boolean hasDuplicateUserIds = !duplicateEmails.isEmpty();
if (isOpportunistic && (!hasUserIds || hasMissingUserIds)) {
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_ERROR,
new OpenPgpError(OpenPgpError.OPPORTUNISTIC_MISSING_KEYS, "missing keys in opportunistic mode"));
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
return new KeyIdResult(result);
long[] keyIdsArray = KeyFormattingUtils.getUnboxedLongArray(keyIds);
PendingIntent pi = apiPendingIntentFactory.createSelectPublicKeyPendingIntent(data, keyIdsArray,
missingEmails, duplicateEmails, false);
if (!missingEmails.isEmpty()) {
return createMissingKeysResult(pi);
}
if (!hasUserIds || hasMissingUserIds || hasDuplicateUserIds) {
long[] keyIdsArray = KeyFormattingUtils.getUnboxedLongArray(keyIds);
PendingIntent pi = apiPendingIntentFactory.createSelectPublicKeyPendingIntent(data, keyIdsArray,
missingEmails, duplicateEmails, hasUserIds);
// return PendingIntent to be executed by client
Intent result = new Intent();
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
return new KeyIdResult(result);
if (!duplicateEmails.isEmpty()) {
return createDuplicateKeysResult(pi);
}
if (keyIds.isEmpty()) {
throw new AssertionError("keyIdsArray.length == 0, should never happen!");
return createNoKeysResult(pi);
}
return new KeyIdResult(keyIds);
boolean allKeysConfirmed = !anyKeyNotVerified;
return createKeysOkResult(keyIds, allKeysConfirmed);
}
private static class KeyRow {
private final Long masterKeyId;
private final boolean verified;
private boolean hasDuplicate;
KeyRow(Long masterKeyId, boolean verified) {
this.masterKeyId = masterKeyId;
this.verified = verified;
}
}
static class KeyIdResult {
private final Intent mResultIntent;
private final HashSet<Long> mKeyIds;
private final PendingIntent mKeySelectionPendingIntent;
private final HashSet<Long> mUserKeyIds;
private final HashSet<Long> mExplicitKeyIds;
private final KeyIdResultStatus mStatus;
private final boolean mAllKeysConfirmed;
private KeyIdResult(Intent resultIntent) {
mResultIntent = resultIntent;
mKeyIds = null;
private KeyIdResult(PendingIntent keySelectionPendingIntent, KeyIdResultStatus keyIdResultStatus) {
mKeySelectionPendingIntent = keySelectionPendingIntent;
mUserKeyIds = null;
mAllKeysConfirmed = false;
mStatus = keyIdResultStatus;
mExplicitKeyIds = null;
}
private KeyIdResult(HashSet<Long> keyIds) {
mResultIntent = null;
mKeyIds = keyIds;
private KeyIdResult(HashSet<Long> keyIds, boolean allKeysConfirmed, KeyIdResultStatus keyIdResultStatus) {
mKeySelectionPendingIntent = null;
mUserKeyIds = keyIds;
mAllKeysConfirmed = allKeysConfirmed;
mStatus = keyIdResultStatus;
mExplicitKeyIds = null;
}
boolean hasResultIntent() {
return mResultIntent != null;
private KeyIdResult(KeyIdResult keyIdResult, HashSet<Long> explicitKeyIds) {
mKeySelectionPendingIntent = keyIdResult.mKeySelectionPendingIntent;
mUserKeyIds = keyIdResult.mUserKeyIds;
mAllKeysConfirmed = keyIdResult.mAllKeysConfirmed;
mStatus = keyIdResult.mStatus;
mExplicitKeyIds = explicitKeyIds;
}
Intent getResultIntent() {
if (mResultIntent == null) {
boolean hasKeySelectionPendingIntent() {
return mKeySelectionPendingIntent != null;
}
PendingIntent getKeySelectionPendingIntent() {
if (mKeySelectionPendingIntent == null) {
throw new AssertionError("result intent must not be null when getResultIntent is called!");
}
if (mKeyIds != null) {
if (mUserKeyIds != null) {
throw new AssertionError("key ids must be null when getKeyIds is called!");
}
return mResultIntent;
return mKeySelectionPendingIntent;
}
long[] getKeyIds() {
if (mResultIntent != null) {
if (mKeySelectionPendingIntent != null) {
throw new AssertionError("result intent must be null when getKeyIds is called!");
}
if (mKeyIds == null) {
throw new AssertionError("key ids must not be null when getKeyIds is called!");
HashSet<Long> allKeyIds = new HashSet<>();
if (mUserKeyIds != null) {
allKeyIds.addAll(mUserKeyIds);
}
return KeyFormattingUtils.getUnboxedLongArray(mKeyIds);
if (mExplicitKeyIds != null) {
allKeyIds.addAll(mExplicitKeyIds);
}
return KeyFormattingUtils.getUnboxedLongArray(allKeyIds);
}
boolean isAllKeysConfirmed() {
return mAllKeysConfirmed;
}
private KeyIdResult withExplicitKeyIds(HashSet<Long> explicitKeyIds) {
return new KeyIdResult(this, explicitKeyIds);
}
KeyIdResultStatus getStatus() {
return mStatus;
}
}
enum KeyIdResultStatus {
OK, MISSING, DUPLICATE, NO_KEYS, NO_KEYS_ERROR
}
private KeyIdResult createKeysOkResult(HashSet<Long> encryptKeyIds, boolean allKeysConfirmed) {
return new KeyIdResult(encryptKeyIds, allKeysConfirmed, KeyIdResultStatus.OK);
}
private static KeyIdResult createNoKeysResult(PendingIntent pendingIntent) {
return new KeyIdResult(pendingIntent, KeyIdResultStatus.NO_KEYS);
}
private static KeyIdResult createNoKeysResult() {
return new KeyIdResult(null, KeyIdResultStatus.NO_KEYS_ERROR);
}
private static KeyIdResult createDuplicateKeysResult(PendingIntent pendingIntent) {
return new KeyIdResult(pendingIntent, KeyIdResultStatus.DUPLICATE);
}
private static KeyIdResult createMissingKeysResult(PendingIntent pendingIntent) {
return new KeyIdResult(pendingIntent, KeyIdResultStatus.MISSING);
}
}