Merge pull request #1993 from open-keychain/key-extractor
Move key extraction logic into its own class
This commit is contained in:
commit
74ff0fdd71
|
@ -22,9 +22,7 @@ package org.sufficientlysecure.keychain.remote;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
|
@ -33,8 +31,6 @@ import java.util.List;
|
|||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
|
@ -51,7 +47,6 @@ import org.openintents.openpgp.OpenPgpError;
|
|||
import org.openintents.openpgp.OpenPgpMetadata;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.openintents.openpgp.util.OpenPgpUtils;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.operations.BackupOperation;
|
||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||
|
@ -59,7 +54,6 @@ import org.sufficientlysecure.keychain.operations.results.ExportResult;
|
|||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSecurityConstants;
|
||||
|
@ -72,8 +66,8 @@ import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
|||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult;
|
||||
import org.sufficientlysecure.keychain.service.BackupKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
|
@ -93,20 +87,11 @@ public class OpenPgpService extends Service {
|
|||
public static final List<Integer> SUPPORTED_VERSIONS =
|
||||
Collections.unmodifiableList(Arrays.asList(3, 4, 5, 6, 7, 8, 9, 10, 11));
|
||||
|
||||
static final String[] KEY_SEARCH_PROJECTION = new String[]{
|
||||
KeyRings._ID,
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
KeyRings.IS_EXPIRED,
|
||||
KeyRings.IS_REVOKED,
|
||||
};
|
||||
|
||||
// do not pre-select revoked or expired keys
|
||||
static final String KEY_SEARCH_WHERE = Tables.KEYS + "." + KeychainContract.KeyRings.IS_REVOKED
|
||||
+ " = 0 AND " + KeychainContract.KeyRings.IS_EXPIRED + " = 0";
|
||||
|
||||
private ApiPermissionHelper mApiPermissionHelper;
|
||||
private ProviderHelper mProviderHelper;
|
||||
private ApiDataAccessObject mApiDao;
|
||||
private OpenPgpServiceKeyIdExtractor mKeyIdExtractor;
|
||||
private ApiPendingIntentFactory mApiPendingIntentFactory;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
|
@ -114,95 +99,9 @@ public class OpenPgpService extends Service {
|
|||
mApiPermissionHelper = new ApiPermissionHelper(this, new ApiDataAccessObject(this));
|
||||
mProviderHelper = new ProviderHelper(this);
|
||||
mApiDao = new ApiDataAccessObject(this);
|
||||
}
|
||||
|
||||
private static class KeyIdResult {
|
||||
final Intent mResultIntent;
|
||||
final HashSet<Long> mKeyIds;
|
||||
|
||||
KeyIdResult(Intent resultIntent) {
|
||||
mResultIntent = resultIntent;
|
||||
mKeyIds = null;
|
||||
}
|
||||
KeyIdResult(HashSet<Long> keyIds) {
|
||||
mResultIntent = null;
|
||||
mKeyIds = keyIds;
|
||||
}
|
||||
}
|
||||
|
||||
private KeyIdResult returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds, boolean isOpportunistic) {
|
||||
boolean hasUserIds = (encryptionUserIds != null && encryptionUserIds.length > 0);
|
||||
|
||||
HashSet<Long> keyIds = new HashSet<>();
|
||||
ArrayList<String> missingEmails = new ArrayList<>();
|
||||
ArrayList<String> duplicateEmails = new ArrayList<>();
|
||||
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 = getContentResolver().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(cursor.getColumnIndex(KeyRings.MASTER_KEY_ID));
|
||||
keyIds.add(id);
|
||||
|
||||
// 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);
|
||||
|
||||
// also pre-select
|
||||
while (cursor.moveToNext()) {
|
||||
long duplicateId = cursor.getLong(cursor.getColumnIndex(KeyRings.MASTER_KEY_ID));
|
||||
keyIds.add(duplicateId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
missingEmails.add(email);
|
||||
Log.d(Constants.TAG, "user id missing");
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (!hasUserIds || hasMissingUserIds || hasDuplicateUserIds) {
|
||||
// convert ArrayList<Long> to long[]
|
||||
long[] keyIdsArray = getUnboxedLongArray(keyIds);
|
||||
ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(getBaseContext());
|
||||
PendingIntent pi = piFactory.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);
|
||||
}
|
||||
|
||||
// everything was easy, we have exactly one key for every email
|
||||
if (keyIds.isEmpty()) {
|
||||
throw new AssertionError("keyIdsArray.length == 0, should never happen!");
|
||||
}
|
||||
|
||||
return new KeyIdResult(keyIds);
|
||||
mApiPendingIntentFactory = new ApiPendingIntentFactory(getBaseContext());
|
||||
mKeyIdExtractor = OpenPgpServiceKeyIdExtractor.getInstance(getContentResolver(), mApiPendingIntentFactory);
|
||||
}
|
||||
|
||||
private Intent signImpl(Intent data, InputStream inputStream,
|
||||
|
@ -269,10 +168,8 @@ public class OpenPgpService extends Service {
|
|||
PgpSignEncryptResult pgpResult = pse.execute(pseInput, inputParcel, inputData, outputStream);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(getBaseContext());
|
||||
|
||||
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
|
||||
PendingIntent pIntent = piFactory.requiredInputPi(data,
|
||||
PendingIntent pIntent = mApiPendingIntentFactory.requiredInputPi(data,
|
||||
requiredInput, pgpResult.mCryptoInputParcel);
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
|
@ -320,35 +217,11 @@ public class OpenPgpService extends Service {
|
|||
compressionId = PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.UNCOMPRESSED;
|
||||
}
|
||||
|
||||
long[] keyIds;
|
||||
{
|
||||
HashSet<Long> encryptKeyIds = new HashSet<>();
|
||||
boolean hasKeysFromSelectPubkeyActivity = data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS_SELECTED);
|
||||
if (hasKeysFromSelectPubkeyActivity) {
|
||||
for (long keyId : data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS_SELECTED)) {
|
||||
encryptKeyIds.add(keyId);
|
||||
}
|
||||
} else if (data.hasExtra(OpenPgpApi.EXTRA_USER_IDS)) {
|
||||
String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS);
|
||||
boolean isOpportunistic = data.getBooleanExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, false);
|
||||
// give params through to activity...
|
||||
KeyIdResult result = returnKeyIdsFromEmails(data, userIds, isOpportunistic);
|
||||
|
||||
if (result.mResultIntent != null) {
|
||||
return result.mResultIntent;
|
||||
}
|
||||
encryptKeyIds.addAll(result.mKeyIds);
|
||||
}
|
||||
|
||||
// add key ids from non-ambiguous key id extra
|
||||
if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
|
||||
for (long keyId : data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
|
||||
encryptKeyIds.add(keyId);
|
||||
}
|
||||
}
|
||||
|
||||
keyIds = getUnboxedLongArray(encryptKeyIds);
|
||||
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();
|
||||
|
@ -422,10 +295,8 @@ public class OpenPgpService extends Service {
|
|||
PgpSignEncryptResult pgpResult = op.execute(pseInput, inputParcel, inputData, outputStream);
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(getBaseContext());
|
||||
|
||||
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
|
||||
PendingIntent pIntent = piFactory.requiredInputPi(data,
|
||||
PendingIntent pIntent = mApiPendingIntentFactory.requiredInputPi(data,
|
||||
requiredInput, pgpResult.mCryptoInputParcel);
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
|
@ -504,12 +375,10 @@ public class OpenPgpService extends Service {
|
|||
|
||||
DecryptVerifyResult pgpResult = op.execute(input, cryptoInput, inputData, outputStream);
|
||||
|
||||
ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(getBaseContext());
|
||||
|
||||
if (pgpResult.isPending()) {
|
||||
// prepare and return PendingIntent to be executed by client
|
||||
RequiredInputParcel requiredInput = pgpResult.getRequiredInputParcel();
|
||||
PendingIntent pIntent = piFactory.requiredInputPi(data,
|
||||
PendingIntent pIntent = mApiPendingIntentFactory.requiredInputPi(data,
|
||||
requiredInput, pgpResult.mCryptoInputParcel);
|
||||
|
||||
Intent result = new Intent();
|
||||
|
@ -522,7 +391,7 @@ public class OpenPgpService extends Service {
|
|||
|
||||
processDecryptionResultForResultIntent(targetApiVersion, result, pgpResult.getDecryptionResult());
|
||||
processMetadataForResultIntent(targetApiVersion, result, pgpResult.getDecryptionMetadata());
|
||||
processSignatureResultForResultIntent(targetApiVersion, data, piFactory, result, pgpResult);
|
||||
processSignatureResultForResultIntent(targetApiVersion, data, result, pgpResult);
|
||||
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
return result;
|
||||
|
@ -533,7 +402,7 @@ public class OpenPgpService extends Service {
|
|||
Intent result = new Intent();
|
||||
String packageName = mApiPermissionHelper.getCurrentCallingPackage();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
||||
piFactory.createSelectAllowedKeysPendingIntent(data, packageName));
|
||||
mApiPendingIntentFactory.createSelectAllowedKeysPendingIntent(data, packageName));
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
}
|
||||
|
@ -612,7 +481,7 @@ public class OpenPgpService extends Service {
|
|||
}
|
||||
|
||||
private void processSignatureResultForResultIntent(int targetApiVersion, Intent data,
|
||||
ApiPendingIntentFactory piFactory, Intent result, DecryptVerifyResult pgpResult) {
|
||||
Intent result, DecryptVerifyResult pgpResult) {
|
||||
OpenPgpSignatureResult signatureResult =
|
||||
getSignatureResultWithApiCompatibilityFallbacks(targetApiVersion, pgpResult);
|
||||
|
||||
|
@ -620,7 +489,7 @@ public class OpenPgpService extends Service {
|
|||
case OpenPgpSignatureResult.RESULT_KEY_MISSING: {
|
||||
// If signature key is missing we return a PendingIntent to retrieve the key
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
||||
piFactory.createImportFromKeyserverPendingIntent(data,
|
||||
mApiPendingIntentFactory.createImportFromKeyserverPendingIntent(data,
|
||||
signatureResult.getKeyId()));
|
||||
break;
|
||||
}
|
||||
|
@ -631,7 +500,7 @@ public class OpenPgpService extends Service {
|
|||
case OpenPgpSignatureResult.RESULT_INVALID_KEY_INSECURE: {
|
||||
// If signature key is known, return PendingIntent to show key
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
||||
piFactory.createShowKeyPendingIntent(data, signatureResult.getKeyId()));
|
||||
mApiPendingIntentFactory.createShowKeyPendingIntent(data, signatureResult.getKeyId()));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -653,8 +522,6 @@ public class OpenPgpService extends Service {
|
|||
|
||||
private Intent getKeyImpl(Intent data, OutputStream outputStream) {
|
||||
try {
|
||||
ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(getBaseContext());
|
||||
|
||||
long masterKeyId = data.getLongExtra(OpenPgpApi.EXTRA_KEY_ID, 0);
|
||||
|
||||
try {
|
||||
|
@ -686,7 +553,7 @@ public class OpenPgpService extends Service {
|
|||
|
||||
// also return PendingIntent that opens the key view activity
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
||||
piFactory.createShowKeyPendingIntent(data, masterKeyId));
|
||||
mApiPendingIntentFactory.createShowKeyPendingIntent(data, masterKeyId));
|
||||
|
||||
return result;
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
|
@ -694,7 +561,7 @@ public class OpenPgpService extends Service {
|
|||
// to retrieve the missing key
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT,
|
||||
piFactory.createImportFromKeyserverPendingIntent(data, masterKeyId));
|
||||
mApiPendingIntentFactory.createImportFromKeyserverPendingIntent(data, masterKeyId));
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
}
|
||||
|
@ -723,8 +590,7 @@ public class OpenPgpService extends Service {
|
|||
String currentPkg = mApiPermissionHelper.getCurrentCallingPackage();
|
||||
String preferredUserId = data.getStringExtra(OpenPgpApi.EXTRA_USER_ID);
|
||||
|
||||
ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(getBaseContext());
|
||||
PendingIntent pi = piFactory.createSelectSignKeyIdPendingIntent(data, currentPkg, preferredUserId);
|
||||
PendingIntent pi = mApiPendingIntentFactory.createSelectSignKeyIdPendingIntent(data, currentPkg, preferredUserId);
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
|
@ -736,34 +602,16 @@ public class OpenPgpService extends Service {
|
|||
}
|
||||
|
||||
private Intent getKeyIdsImpl(Intent data) {
|
||||
// if data already contains EXTRA_KEY_IDS, it has been executed again
|
||||
// after user interaction. Then, we just need to return the array again!
|
||||
if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
|
||||
long[] keyIdsArray = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
|
||||
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_KEY_IDS, keyIdsArray);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
return result;
|
||||
} else {
|
||||
// get key ids based on given user ids
|
||||
String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS);
|
||||
KeyIdResult keyResult = returnKeyIdsFromEmails(data, userIds, false);
|
||||
if (keyResult.mResultIntent != null) {
|
||||
return keyResult.mResultIntent;
|
||||
}
|
||||
|
||||
if (keyResult.mKeyIds == null) {
|
||||
throw new AssertionError("one of requiredUserInteraction and keyIds must be non-null, this is a bug!");
|
||||
}
|
||||
|
||||
long[] keyIds = getUnboxedLongArray(keyResult.mKeyIds);
|
||||
|
||||
Intent resultIntent = new Intent();
|
||||
resultIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
resultIntent.putExtra(OpenPgpApi.RESULT_KEY_IDS, keyIds);
|
||||
return resultIntent;
|
||||
KeyIdResult keyIdResult = mKeyIdExtractor.returnKeyIdsFromIntent(data, true);
|
||||
if (keyIdResult.hasResultIntent()) {
|
||||
return keyIdResult.getResultIntent();
|
||||
}
|
||||
long[] keyIds = keyIdResult.getKeyIds();
|
||||
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_KEY_IDS, keyIds);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Intent backupImpl(Intent data, OutputStream outputStream) {
|
||||
|
@ -771,12 +619,10 @@ public class OpenPgpService extends Service {
|
|||
long[] masterKeyIds = data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS);
|
||||
boolean backupSecret = data.getBooleanExtra(OpenPgpApi.EXTRA_BACKUP_SECRET, false);
|
||||
|
||||
ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(getBaseContext());
|
||||
|
||||
CryptoInputParcel inputParcel = CryptoInputParcelCacheService.getCryptoInputParcel(this, data);
|
||||
if (inputParcel == null) {
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, piFactory.createBackupPendingIntent(data, masterKeyIds, backupSecret));
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, mApiPendingIntentFactory.createBackupPendingIntent(data, masterKeyIds, backupSecret));
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
return result;
|
||||
}
|
||||
|
@ -809,16 +655,6 @@ public class OpenPgpService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static long[] getUnboxedLongArray(@NonNull Collection<Long> arrayList) {
|
||||
long[] result = new long[arrayList.size()];
|
||||
int i = 0;
|
||||
for (Long e : arrayList) {
|
||||
result[i++] = e;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Intent checkPermissionImpl(@NonNull Intent data) {
|
||||
Intent permissionIntent = mApiPermissionHelper.isAllowedOrReturnIntent(data);
|
||||
if (permissionIntent != null) {
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
package org.sufficientlysecure.keychain.remote;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
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.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!
|
||||
};
|
||||
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 final ApiPendingIntentFactory apiPendingIntentFactory;
|
||||
private final ContentResolver contentResolver;
|
||||
|
||||
|
||||
static OpenPgpServiceKeyIdExtractor getInstance(ContentResolver contentResolver, ApiPendingIntentFactory apiPendingIntentFactory) {
|
||||
return new OpenPgpServiceKeyIdExtractor(contentResolver, apiPendingIntentFactory);
|
||||
}
|
||||
|
||||
private OpenPgpServiceKeyIdExtractor(ContentResolver contentResolver,
|
||||
ApiPendingIntentFactory apiPendingIntentFactory) {
|
||||
this.contentResolver = contentResolver;
|
||||
this.apiPendingIntentFactory = apiPendingIntentFactory;
|
||||
}
|
||||
|
||||
|
||||
KeyIdResult returnKeyIdsFromIntent(Intent data, boolean askIfNoUserIdsProvided) {
|
||||
HashSet<Long> encryptKeyIds = new HashSet<>();
|
||||
|
||||
boolean hasKeysFromSelectPubkeyActivity = data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS_SELECTED);
|
||||
if (hasKeysFromSelectPubkeyActivity) {
|
||||
for (long keyId : data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS_SELECTED)) {
|
||||
encryptKeyIds.add(keyId);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
|
||||
// add key ids from non-ambiguous key id extra
|
||||
if (data.hasExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
|
||||
for (long keyId : data.getLongArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)) {
|
||||
encryptKeyIds.add(keyId);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private KeyIdResult returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds, boolean isOpportunistic) {
|
||||
boolean hasUserIds = (encryptionUserIds != null && encryptionUserIds.length > 0);
|
||||
|
||||
HashSet<Long> keyIds = new HashSet<>();
|
||||
ArrayList<String> missingEmails = new ArrayList<>();
|
||||
ArrayList<String> duplicateEmails = new ArrayList<>();
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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");
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (!hasUserIds || hasMissingUserIds || hasDuplicateUserIds) {
|
||||
long[] keyIdsArray = 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 (keyIds.isEmpty()) {
|
||||
throw new AssertionError("keyIdsArray.length == 0, should never happen!");
|
||||
}
|
||||
|
||||
return new KeyIdResult(keyIds);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static long[] getUnboxedLongArray(@NonNull Collection<Long> arrayList) {
|
||||
long[] result = new long[arrayList.size()];
|
||||
int i = 0;
|
||||
for (Long e : arrayList) {
|
||||
result[i++] = e;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static class KeyIdResult {
|
||||
private final Intent mResultIntent;
|
||||
private final HashSet<Long> mKeyIds;
|
||||
|
||||
private KeyIdResult(Intent resultIntent) {
|
||||
mResultIntent = resultIntent;
|
||||
mKeyIds = null;
|
||||
}
|
||||
private KeyIdResult(HashSet<Long> keyIds) {
|
||||
mResultIntent = null;
|
||||
mKeyIds = keyIds;
|
||||
}
|
||||
|
||||
boolean hasResultIntent() {
|
||||
return mResultIntent != null;
|
||||
}
|
||||
Intent getResultIntent() {
|
||||
if (mResultIntent == null) {
|
||||
throw new AssertionError("result intent must not be null when getResultIntent is called!");
|
||||
}
|
||||
if (mKeyIds != null) {
|
||||
throw new AssertionError("key ids must be null when getKeyIds is called!");
|
||||
}
|
||||
return mResultIntent;
|
||||
}
|
||||
long[] getKeyIds() {
|
||||
if (mResultIntent != 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!");
|
||||
}
|
||||
return getUnboxedLongArray(mKeyIds);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,274 @@
|
|||
package org.sufficientlysecure.keychain.remote;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.sufficientlysecure.keychain.WorkaroundBuildConfig;
|
||||
import org.sufficientlysecure.keychain.remote.OpenPgpServiceKeyIdExtractor.KeyIdResult;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = WorkaroundBuildConfig.class, sdk = 23, manifest = "src/main/AndroidManifest.xml")
|
||||
public class OpenPgpServiceKeyIdExtractorTest {
|
||||
|
||||
private static final long[] KEY_IDS = new long[] { 123L, 234L };
|
||||
private static final String[] USER_IDS = new String[] { "user1@example.org", "User 2 <user2@example.org>" };
|
||||
private OpenPgpServiceKeyIdExtractor openPgpServiceKeyIdExtractor;
|
||||
private ContentResolver contentResolver;
|
||||
private ApiPendingIntentFactory apiPendingIntentFactory;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
contentResolver = mock(ContentResolver.class);
|
||||
apiPendingIntentFactory = mock(ApiPendingIntentFactory.class);
|
||||
|
||||
openPgpServiceKeyIdExtractor = OpenPgpServiceKeyIdExtractor.getInstance(contentResolver,
|
||||
apiPendingIntentFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnKeyIdsFromIntent__withKeyIdsExtra() throws Exception {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, KEY_IDS);
|
||||
|
||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
||||
|
||||
assertFalse(keyIdResult.hasResultIntent());
|
||||
assertArrayEqualsSorted(KEY_IDS, keyIdResult.getKeyIds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnKeyIdsFromIntent__withKeyIdsSelectedExtra() throws Exception {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS_SELECTED, KEY_IDS);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS); // should be ignored
|
||||
|
||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
||||
|
||||
assertFalse(keyIdResult.hasResultIntent());
|
||||
assertArrayEqualsSorted(KEY_IDS, keyIdResult.getKeyIds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnKeyIdsFromIntent__withUserIds__withEmptyQueryResult() throws Exception {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS);
|
||||
|
||||
setupContentResolverResult();
|
||||
|
||||
PendingIntent pendingIntent = mock(PendingIntent.class);
|
||||
setupPendingIntentFactoryResult(pendingIntent);
|
||||
|
||||
|
||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
||||
|
||||
|
||||
assertTrue(keyIdResult.hasResultIntent());
|
||||
Intent resultIntent = keyIdResult.getResultIntent();
|
||||
assertEquals(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED,
|
||||
resultIntent.getIntExtra(OpenPgpApi.RESULT_CODE, Integer.MAX_VALUE));
|
||||
assertEquals(pendingIntent, resultIntent.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnKeyIdsFromIntent__withNoData() throws Exception {
|
||||
Intent intent = new Intent();
|
||||
|
||||
|
||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
||||
|
||||
|
||||
assertTrue(keyIdResult.hasResultIntent());
|
||||
Intent resultIntent = keyIdResult.getResultIntent();
|
||||
assertEquals(OpenPgpApi.RESULT_CODE_ERROR,
|
||||
resultIntent.getIntExtra(OpenPgpApi.RESULT_CODE, Integer.MAX_VALUE));
|
||||
assertEquals(OpenPgpError.NO_USER_IDS,
|
||||
resultIntent.<OpenPgpError>getParcelableExtra(OpenPgpApi.RESULT_ERROR).getErrorId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnKeyIdsFromIntent__withEmptyUserId() throws Exception {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, new String[0]);
|
||||
|
||||
PendingIntent pendingIntent = mock(PendingIntent.class);
|
||||
setupPendingIntentFactoryResult(pendingIntent);
|
||||
|
||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
||||
|
||||
|
||||
assertTrue(keyIdResult.hasResultIntent());
|
||||
Intent resultIntent = keyIdResult.getResultIntent();
|
||||
assertEquals(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED,
|
||||
resultIntent.getIntExtra(OpenPgpApi.RESULT_CODE, Integer.MAX_VALUE));
|
||||
assertEquals(pendingIntent, resultIntent.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnKeyIdsFromIntent__withNoData__askIfNoData() throws Exception {
|
||||
Intent intent = new Intent();
|
||||
|
||||
setupContentResolverResult();
|
||||
|
||||
PendingIntent pendingIntent = mock(PendingIntent.class);
|
||||
setupPendingIntentFactoryResult(pendingIntent);
|
||||
|
||||
|
||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, true);
|
||||
|
||||
|
||||
assertTrue(keyIdResult.hasResultIntent());
|
||||
Intent resultIntent = keyIdResult.getResultIntent();
|
||||
assertEquals(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED,
|
||||
resultIntent.getIntExtra(OpenPgpApi.RESULT_CODE, Integer.MAX_VALUE));
|
||||
assertEquals(pendingIntent, resultIntent.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnKeyIdsFromIntent__withUserIds__withEmptyQueryResult__inOpportunisticMode() throws Exception {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, true);
|
||||
|
||||
setupContentResolverResult();
|
||||
|
||||
|
||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
||||
|
||||
|
||||
assertTrue(keyIdResult.hasResultIntent());
|
||||
Intent resultIntent = keyIdResult.getResultIntent();
|
||||
assertEquals(OpenPgpApi.RESULT_CODE_ERROR, resultIntent.getIntExtra(OpenPgpApi.RESULT_CODE, Integer.MAX_VALUE));
|
||||
assertEquals(OpenPgpError.OPPORTUNISTIC_MISSING_KEYS,
|
||||
resultIntent.<OpenPgpError>getParcelableExtra(OpenPgpApi.RESULT_ERROR).getErrorId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnKeyIdsFromIntent__withUserIds() throws Exception {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS);
|
||||
|
||||
setupContentResolverResult(new long[][] {
|
||||
new long[] { 123L },
|
||||
new long[] { 234L }
|
||||
});
|
||||
|
||||
|
||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
||||
|
||||
|
||||
assertFalse(keyIdResult.hasResultIntent());
|
||||
assertArrayEqualsSorted(KEY_IDS, keyIdResult.getKeyIds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnKeyIdsFromIntent__withUserIds__withDuplicate() throws Exception {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS);
|
||||
|
||||
setupContentResolverResult(new long[][] {
|
||||
new long[] { 123L, 345L },
|
||||
new long[] { 234L }
|
||||
});
|
||||
|
||||
PendingIntent pendingIntent = mock(PendingIntent.class);
|
||||
setupPendingIntentFactoryResult(pendingIntent);
|
||||
|
||||
|
||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
||||
|
||||
|
||||
assertTrue(keyIdResult.hasResultIntent());
|
||||
Intent resultIntent = keyIdResult.getResultIntent();
|
||||
assertEquals(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED,
|
||||
resultIntent.getIntExtra(OpenPgpApi.RESULT_CODE, Integer.MAX_VALUE));
|
||||
assertEquals(pendingIntent, resultIntent.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnKeyIdsFromIntent__withUserIds__withMissing() throws Exception {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, USER_IDS);
|
||||
|
||||
setupContentResolverResult(new long[][] {
|
||||
new long[] { },
|
||||
new long[] { 234L }
|
||||
});
|
||||
|
||||
PendingIntent pendingIntent = mock(PendingIntent.class);
|
||||
setupPendingIntentFactoryResult(pendingIntent);
|
||||
|
||||
|
||||
KeyIdResult keyIdResult = openPgpServiceKeyIdExtractor.returnKeyIdsFromIntent(intent, false);
|
||||
|
||||
|
||||
assertTrue(keyIdResult.hasResultIntent());
|
||||
Intent resultIntent = keyIdResult.getResultIntent();
|
||||
assertEquals(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED,
|
||||
resultIntent.getIntExtra(OpenPgpApi.RESULT_CODE, Integer.MAX_VALUE));
|
||||
assertEquals(pendingIntent, resultIntent.getParcelableExtra(OpenPgpApi.RESULT_INTENT));
|
||||
}
|
||||
|
||||
private void setupContentResolverResult() {
|
||||
MatrixCursor resultCursor = new MatrixCursor(OpenPgpServiceKeyIdExtractor.KEY_SEARCH_PROJECTION);
|
||||
when(contentResolver.query(
|
||||
any(Uri.class), any(String[].class), any(String.class), any(String[].class), any(String.class)))
|
||||
.thenReturn(resultCursor);
|
||||
}
|
||||
|
||||
private void setupContentResolverResult(long[][] resultKeyIds) {
|
||||
Cursor[] resultCursors = new MatrixCursor[resultKeyIds.length];
|
||||
for (int i = 0; i < resultKeyIds.length; i++) {
|
||||
MatrixCursor resultCursor = new MatrixCursor(OpenPgpServiceKeyIdExtractor.KEY_SEARCH_PROJECTION);
|
||||
for (long keyId : resultKeyIds[i]) {
|
||||
resultCursor.addRow(new Object[] { keyId, keyId, 0L, 0L });
|
||||
}
|
||||
|
||||
resultCursors[i] = resultCursor;
|
||||
}
|
||||
|
||||
when(contentResolver.query(
|
||||
any(Uri.class), any(String[].class), any(String.class), any(String[].class), any(String.class)))
|
||||
.thenReturn(resultCursors[0], Arrays.copyOfRange(resultCursors, 1, resultCursors.length));
|
||||
}
|
||||
|
||||
private void setupPendingIntentFactoryResult(PendingIntent pendingIntent) {
|
||||
when(apiPendingIntentFactory.createSelectPublicKeyPendingIntent(
|
||||
any(Intent.class), any(long[].class), any(ArrayList.class), any(ArrayList.class), any(Boolean.class)))
|
||||
.thenReturn(pendingIntent);
|
||||
}
|
||||
|
||||
|
||||
private static void assertArrayEqualsSorted(long[] a, long[] b) {
|
||||
long[] tmpA = Arrays.copyOf(a, a.length);
|
||||
long[] tmpB = Arrays.copyOf(b, b.length);
|
||||
Arrays.sort(tmpA);
|
||||
Arrays.sort(tmpB);
|
||||
|
||||
assertArrayEquals(tmpA, tmpB);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue