ImportKeys: Get reference to canonicalized key without saving and refactoring (WIP)
This commit is contained in:
@@ -1,16 +1,9 @@
|
|||||||
package org.sufficientlysecure.keychain.keyimport.processing;
|
package org.sufficientlysecure.keychain.keyimport.processing;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
public interface ImportKeysListener extends ImportKeysResultListener {
|
||||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
|
||||||
|
|
||||||
public interface ImportKeysListener {
|
|
||||||
|
|
||||||
void loadKeys(LoaderState loaderState);
|
void loadKeys(LoaderState loaderState);
|
||||||
|
|
||||||
void importKey(ParcelableKeyRing keyRing);
|
|
||||||
|
|
||||||
void importKeys();
|
void importKeys();
|
||||||
|
|
||||||
void handleResult(ImportKeyResult result);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,26 @@
|
|||||||
package org.sufficientlysecure.keychain.keyimport.processing;
|
package org.sufficientlysecure.keychain.keyimport.processing;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
|
||||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||||
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
public class ImportKeysOperationCallback implements
|
public class ImportKeysOperationCallback implements
|
||||||
CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
|
CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
|
||||||
|
|
||||||
private ImportKeysListener mResultListener;
|
private ImportKeysResultListener mResultListener;
|
||||||
private String mKeyserver;
|
private ImportKeyringParcel mKeyringParcel;
|
||||||
private ArrayList<ParcelableKeyRing> mKeyList;
|
|
||||||
|
|
||||||
public ImportKeysOperationCallback(
|
public ImportKeysOperationCallback(
|
||||||
ImportKeysListener resultListener,
|
ImportKeysResultListener resultListener,
|
||||||
String keyserver,
|
ImportKeyringParcel inputParcel
|
||||||
ArrayList<ParcelableKeyRing> keyList
|
|
||||||
) {
|
) {
|
||||||
this.mResultListener = resultListener;
|
this.mResultListener = resultListener;
|
||||||
this.mKeyserver = keyserver;
|
this.mKeyringParcel = inputParcel;
|
||||||
this.mKeyList = keyList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImportKeyringParcel createOperationInput() {
|
public ImportKeyringParcel createOperationInput() {
|
||||||
return new ImportKeyringParcel(mKeyList, mKeyserver);
|
return mKeyringParcel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.sufficientlysecure.keychain.keyimport.processing;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||||
|
|
||||||
|
public interface ImportKeysResultListener {
|
||||||
|
|
||||||
|
void handleResult(ImportKeyResult result);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -35,6 +35,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
|||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.Progressable;
|
import org.sufficientlysecure.keychain.pgp.Progressable;
|
||||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
@@ -78,8 +79,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
* not include self certificates for user ids in the secret keyring. The import
|
* not include self certificates for user ids in the secret keyring. The import
|
||||||
* method here will generally import keyrings in the order given by the
|
* method here will generally import keyrings in the order given by the
|
||||||
* iterator, so this should be ensured beforehand.
|
* iterator, so this should be ensured beforehand.
|
||||||
*
|
|
||||||
* @see org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter#getSelectedEntries()
|
|
||||||
*/
|
*/
|
||||||
public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
||||||
|
|
||||||
@@ -99,20 +98,20 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
|
|
||||||
// Overloaded functions for using progressable supplied in constructor during import
|
// Overloaded functions for using progressable supplied in constructor during import
|
||||||
public ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num,
|
public ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num,
|
||||||
String keyServerUri, Proxy proxy) {
|
String keyServerUri, Proxy proxy, boolean skipSave) {
|
||||||
return serialKeyRingImport(entries, num, keyServerUri, mProgressable, proxy);
|
return serialKeyRingImport(entries, num, keyServerUri, mProgressable, proxy, skipSave);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private ImportKeyResult serialKeyRingImport(ParcelableFileCache<ParcelableKeyRing> cache,
|
private ImportKeyResult serialKeyRingImport(ParcelableFileCache<ParcelableKeyRing> cache,
|
||||||
String keyServerUri, Proxy proxy) {
|
String keyServerUri, Proxy proxy, boolean skipSave) {
|
||||||
|
|
||||||
// get entries from cached file
|
// get entries from cached file
|
||||||
try {
|
try {
|
||||||
IteratorWithSize<ParcelableKeyRing> it = cache.readCache();
|
IteratorWithSize<ParcelableKeyRing> it = cache.readCache();
|
||||||
int numEntries = it.getSize();
|
int numEntries = it.getSize();
|
||||||
|
|
||||||
return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable, proxy);
|
return serialKeyRingImport(it, numEntries, keyServerUri, mProgressable, proxy, skipSave);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
||||||
// Special treatment here, we need a lot
|
// Special treatment here, we need a lot
|
||||||
@@ -138,7 +137,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
@NonNull
|
@NonNull
|
||||||
private ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num,
|
private ImportKeyResult serialKeyRingImport(Iterator<ParcelableKeyRing> entries, int num,
|
||||||
String keyServerUri, Progressable progressable,
|
String keyServerUri, Progressable progressable,
|
||||||
@NonNull Proxy proxy) {
|
@NonNull Proxy proxy, boolean skipSave) {
|
||||||
if (progressable != null) {
|
if (progressable != null) {
|
||||||
progressable.setProgress(R.string.progress_importing, 0, 100);
|
progressable.setProgress(R.string.progress_importing, 0, 100);
|
||||||
}
|
}
|
||||||
@@ -154,6 +153,8 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
int newKeys = 0, updatedKeys = 0, badKeys = 0, secret = 0;
|
int newKeys = 0, updatedKeys = 0, badKeys = 0, secret = 0;
|
||||||
ArrayList<Long> importedMasterKeyIds = new ArrayList<>();
|
ArrayList<Long> importedMasterKeyIds = new ArrayList<>();
|
||||||
|
|
||||||
|
ArrayList<CanonicalizedKeyRing> canKeyRings = new ArrayList<>();
|
||||||
|
|
||||||
boolean cancelled = false;
|
boolean cancelled = false;
|
||||||
int position = 0;
|
int position = 0;
|
||||||
double progSteps = 100.0 / num;
|
double progSteps = 100.0 / num;
|
||||||
@@ -315,14 +316,14 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
// and https://github.com/open-keychain/open-keychain/issues/1480
|
// and https://github.com/open-keychain/open-keychain/issues/1480
|
||||||
synchronized (mProviderHelper) {
|
synchronized (mProviderHelper) {
|
||||||
mProviderHelper.clearLog();
|
mProviderHelper.clearLog();
|
||||||
|
ProgressScaler progressScaler = new ProgressScaler(progressable, (int) (position * progSteps),
|
||||||
|
(int) ((position + 1) * progSteps), 100);
|
||||||
if (key.isSecret()) {
|
if (key.isSecret()) {
|
||||||
result = mProviderHelper.saveSecretKeyRing(key,
|
result = mProviderHelper.saveSecretKeyRing(key, progressScaler,
|
||||||
new ProgressScaler(progressable, (int) (position * progSteps),
|
canKeyRings, skipSave);
|
||||||
(int) ((position + 1) * progSteps), 100));
|
|
||||||
} else {
|
} else {
|
||||||
result = mProviderHelper.savePublicKeyRing(key,
|
result = mProviderHelper.savePublicKeyRing(key, progressScaler,
|
||||||
new ProgressScaler(progressable, (int) (position * progSteps),
|
entry.mExpectedFingerprint, canKeyRings, skipSave);
|
||||||
(int) ((position + 1) * progSteps), 100), entry.mExpectedFingerprint);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!result.success()) {
|
if (!result.success()) {
|
||||||
@@ -361,7 +362,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
// synchronized on mProviderHelper to prevent
|
// synchronized on mProviderHelper to prevent
|
||||||
// https://github.com/open-keychain/open-keychain/issues/1221 since a consolidate deletes
|
// https://github.com/open-keychain/open-keychain/issues/1221 since a consolidate deletes
|
||||||
// and re-inserts keys, which could conflict with a parallel db key update
|
// and re-inserts keys, which could conflict with a parallel db key update
|
||||||
if (secret > 0) {
|
if (!skipSave && secret > 0) {
|
||||||
setPreventCancel();
|
setPreventCancel();
|
||||||
ConsolidateResult result;
|
ConsolidateResult result;
|
||||||
synchronized (mProviderHelper) {
|
synchronized (mProviderHelper) {
|
||||||
@@ -419,8 +420,11 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret,
|
ImportKeyResult result = new ImportKeyResult(resultType, log, newKeys, updatedKeys, badKeys, secret,
|
||||||
importedMasterKeyIdsArray);
|
importedMasterKeyIdsArray);
|
||||||
|
|
||||||
|
result.setCanonicalizedKeyRings(canKeyRings);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -428,13 +432,13 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
public ImportKeyResult execute(ImportKeyringParcel importInput, CryptoInputParcel cryptoInput) {
|
public ImportKeyResult execute(ImportKeyringParcel importInput, CryptoInputParcel cryptoInput) {
|
||||||
ArrayList<ParcelableKeyRing> keyList = importInput.mKeyList;
|
ArrayList<ParcelableKeyRing> keyList = importInput.mKeyList;
|
||||||
String keyServer = importInput.mKeyserver;
|
String keyServer = importInput.mKeyserver;
|
||||||
|
boolean skipSave = importInput.mSkipSave;
|
||||||
|
|
||||||
ImportKeyResult result;
|
ImportKeyResult result;
|
||||||
|
|
||||||
if (keyList == null) {// import from file, do serially
|
if (keyList == null) {// import from file, do serially
|
||||||
ParcelableFileCache<ParcelableKeyRing> cache =
|
ParcelableFileCache<ParcelableKeyRing> cache =
|
||||||
new ParcelableFileCache<>(mContext, CACHE_FILE_NAME);
|
new ParcelableFileCache<>(mContext, CACHE_FILE_NAME);
|
||||||
result = serialKeyRingImport(cache, null, null);
|
result = serialKeyRingImport(cache, null, null, skipSave);
|
||||||
} else {
|
} else {
|
||||||
Proxy proxy;
|
Proxy proxy;
|
||||||
if (cryptoInput.getParcelableProxy() == null) {
|
if (cryptoInput.getParcelableProxy() == null) {
|
||||||
@@ -449,7 +453,7 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
proxy = cryptoInput.getParcelableProxy().getProxy();
|
proxy = cryptoInput.getParcelableProxy().getProxy();
|
||||||
}
|
}
|
||||||
|
|
||||||
result = multiThreadedKeyImport(keyList.iterator(), keyList.size(), keyServer, proxy);
|
result = multiThreadedKeyImport(keyList, keyServer, proxy, skipSave);
|
||||||
}
|
}
|
||||||
|
|
||||||
ContactSyncAdapterService.requestContactsSync();
|
ContactSyncAdapterService.requestContactsSync();
|
||||||
@@ -457,44 +461,43 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private ImportKeyResult multiThreadedKeyImport(@NonNull Iterator<ParcelableKeyRing> keyListIterator,
|
private ImportKeyResult multiThreadedKeyImport(ArrayList<ParcelableKeyRing> keyList,
|
||||||
int totKeys, final String keyServer,
|
final String keyServer, final Proxy proxy,
|
||||||
final Proxy proxy) {
|
final boolean skipSave) {
|
||||||
Log.d(Constants.TAG, "Multi-threaded key import starting");
|
|
||||||
KeyImportAccumulator accumulator = new KeyImportAccumulator(totKeys, mProgressable);
|
|
||||||
|
|
||||||
final ProgressScaler ignoreProgressable = new ProgressScaler();
|
Log.d(Constants.TAG, "Multi-threaded key import starting");
|
||||||
|
|
||||||
|
final Iterator<ParcelableKeyRing> keyListIterator = keyList.iterator();
|
||||||
|
final int totKeys = keyList.size();
|
||||||
|
|
||||||
ExecutorService importExecutor = new ThreadPoolExecutor(0, MAX_THREADS, 30L, TimeUnit.SECONDS,
|
ExecutorService importExecutor = new ThreadPoolExecutor(0, MAX_THREADS, 30L, TimeUnit.SECONDS,
|
||||||
new LinkedBlockingQueue<Runnable>());
|
new LinkedBlockingQueue<Runnable>());
|
||||||
|
|
||||||
ExecutorCompletionService<ImportKeyResult> importCompletionService =
|
ExecutorCompletionService<ImportKeyResult> importCompletionService =
|
||||||
new ExecutorCompletionService<>(importExecutor);
|
new ExecutorCompletionService<>(importExecutor);
|
||||||
|
|
||||||
while (keyListIterator.hasNext()) { // submit all key rings to be imported
|
while (keyListIterator.hasNext()) { // submit all key rings to be imported
|
||||||
|
|
||||||
final ParcelableKeyRing pkRing = keyListIterator.next();
|
|
||||||
|
|
||||||
Callable<ImportKeyResult> importOperationCallable = new Callable<ImportKeyResult>
|
Callable<ImportKeyResult> importOperationCallable = new Callable<ImportKeyResult>
|
||||||
() {
|
() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImportKeyResult call() {
|
public ImportKeyResult call() {
|
||||||
|
|
||||||
if (checkCancelled()) {
|
if (checkCancelled()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayList<ParcelableKeyRing> list = new ArrayList<>();
|
ArrayList<ParcelableKeyRing> list = new ArrayList<>();
|
||||||
list.add(pkRing);
|
list.add(keyListIterator.next());
|
||||||
|
ProgressScaler ignoreProgressable = new ProgressScaler();
|
||||||
|
|
||||||
return serialKeyRingImport(list.iterator(), 1, keyServer, ignoreProgressable, proxy);
|
return serialKeyRingImport(list.iterator(), 1, keyServer, ignoreProgressable,
|
||||||
|
proxy, skipSave);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
importCompletionService.submit(importOperationCallable);
|
importCompletionService.submit(importOperationCallable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KeyImportAccumulator accumulator = new KeyImportAccumulator(totKeys, mProgressable);
|
||||||
while (!accumulator.isImportFinished()) { // accumulate the results of each import
|
while (!accumulator.isImportFinished()) { // accumulate the results of each import
|
||||||
try {
|
try {
|
||||||
accumulator.accumulateKeyImport(importCompletionService.take().get());
|
accumulator.accumulateKeyImport(importCompletionService.take().get());
|
||||||
@@ -511,7 +514,6 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return accumulator.getConsolidatedResult();
|
return accumulator.getConsolidatedResult();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -519,10 +521,10 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
*/
|
*/
|
||||||
public static class KeyImportAccumulator {
|
public static class KeyImportAccumulator {
|
||||||
private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog();
|
private OperationResult.OperationLog mImportLog = new OperationResult.OperationLog();
|
||||||
Progressable mProgressable;
|
private Progressable mProgressable;
|
||||||
private int mTotalKeys;
|
private int mTotalKeys;
|
||||||
private int mImportedKeys = 0;
|
private int mImportedKeys = 0;
|
||||||
ArrayList<Long> mImportedMasterKeyIds = new ArrayList<>();
|
private ArrayList<Long> mImportedMasterKeyIds = new ArrayList<>();
|
||||||
private int mBadKeys = 0;
|
private int mBadKeys = 0;
|
||||||
private int mNewKeys = 0;
|
private int mNewKeys = 0;
|
||||||
private int mUpdatedKeys = 0;
|
private int mUpdatedKeys = 0;
|
||||||
@@ -530,6 +532,8 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
private int mResultType = 0;
|
private int mResultType = 0;
|
||||||
private boolean mHasCancelledResult;
|
private boolean mHasCancelledResult;
|
||||||
|
|
||||||
|
public ArrayList<CanonicalizedKeyRing> mCanonicalizedKeyRings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accumulates keyring imports and updates the progressable whenever a new key is imported.
|
* Accumulates keyring imports and updates the progressable whenever a new key is imported.
|
||||||
* Also sets the progress to 0 on instantiation.
|
* Also sets the progress to 0 on instantiation.
|
||||||
@@ -544,6 +548,8 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
if (mProgressable != null) {
|
if (mProgressable != null) {
|
||||||
mProgressable.setProgress(0, totalKeys);
|
mProgressable.setProgress(0, totalKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mCanonicalizedKeyRings = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void accumulateKeyImport(ImportKeyResult result) {
|
public void accumulateKeyImport(ImportKeyResult result) {
|
||||||
@@ -575,6 +581,8 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
mImportedMasterKeyIds.add(masterKeyId);
|
mImportedMasterKeyIds.add(masterKeyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mCanonicalizedKeyRings.addAll(result.mCanonicalizedKeyRings);
|
||||||
|
|
||||||
// if any key import has been cancelled, set result type to cancelled
|
// if any key import has been cancelled, set result type to cancelled
|
||||||
// resultType is added to in getConsolidatedKayImport to account for remaining factors
|
// resultType is added to in getConsolidatedKayImport to account for remaining factors
|
||||||
mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED;
|
mResultType |= result.getResult() & ImportKeyResult.RESULT_CANCELLED;
|
||||||
@@ -614,8 +622,11 @@ public class ImportOperation extends BaseOperation<ImportKeyringParcel> {
|
|||||||
masterKeyIds[i] = mImportedMasterKeyIds.get(i);
|
masterKeyIds[i] = mImportedMasterKeyIds.get(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ImportKeyResult(mResultType, mImportLog, mNewKeys, mUpdatedKeys, mBadKeys,
|
ImportKeyResult result = new ImportKeyResult(mResultType, mImportLog, mNewKeys,
|
||||||
mSecret, masterKeyIds);
|
mUpdatedKeys, mBadKeys, mSecret, masterKeyIds);
|
||||||
|
|
||||||
|
result.setCanonicalizedKeyRings(mCanonicalizedKeyRings);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isImportFinished() {
|
public boolean isImportFinished() {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.content.Intent;
|
|||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
|
||||||
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.ui.LogDisplayActivity;
|
import org.sufficientlysecure.keychain.ui.LogDisplayActivity;
|
||||||
@@ -32,11 +33,16 @@ import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
|
|||||||
import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
|
import org.sufficientlysecure.keychain.ui.util.Notify.Showable;
|
||||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class ImportKeyResult extends InputPendingResult {
|
public class ImportKeyResult extends InputPendingResult {
|
||||||
|
|
||||||
public final int mNewKeys, mUpdatedKeys, mBadKeys, mSecret;
|
public final int mNewKeys, mUpdatedKeys, mBadKeys, mSecret;
|
||||||
public final long[] mImportedMasterKeyIds;
|
public final long[] mImportedMasterKeyIds;
|
||||||
|
|
||||||
|
// NOT PARCELED
|
||||||
|
public ArrayList<CanonicalizedKeyRing> mCanonicalizedKeyRings;
|
||||||
|
|
||||||
// At least one new key
|
// At least one new key
|
||||||
public static final int RESULT_OK_NEWKEYS = 8;
|
public static final int RESULT_OK_NEWKEYS = 8;
|
||||||
// At least one updated key
|
// At least one updated key
|
||||||
@@ -107,6 +113,10 @@ public class ImportKeyResult extends InputPendingResult {
|
|||||||
mImportedMasterKeyIds = new long[]{};
|
mImportedMasterKeyIds = new long[]{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCanonicalizedKeyRings(ArrayList<CanonicalizedKeyRing> canonicalizedKeyRings) {
|
||||||
|
this.mCanonicalizedKeyRings = canonicalizedKeyRings;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
super.writeToParcel(dest, flags);
|
super.writeToParcel(dest, flags);
|
||||||
@@ -128,7 +138,6 @@ public class ImportKeyResult extends InputPendingResult {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public Showable createNotify(final Activity activity) {
|
public Showable createNotify(final Activity activity) {
|
||||||
|
|
||||||
int resultType = getResult();
|
int resultType = getResult();
|
||||||
|
|
||||||
String str;
|
String str;
|
||||||
@@ -204,7 +213,6 @@ public class ImportKeyResult extends InputPendingResult {
|
|||||||
activity.startActivity(intent);
|
activity.startActivity(intent);
|
||||||
}
|
}
|
||||||
}, R.string.snackbar_details);
|
}, R.string.snackbar_details);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
|||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
|
||||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||||
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||||
@@ -904,7 +905,7 @@ public class ProviderHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing keyRing) {
|
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing keyRing) {
|
||||||
return savePublicKeyRing(keyRing, new ProgressScaler(), null);
|
return savePublicKeyRing(keyRing, new ProgressScaler(), null, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -913,7 +914,10 @@ public class ProviderHelper {
|
|||||||
* This is a high level method, which takes care of merging all new information into the old and
|
* This is a high level method, which takes care of merging all new information into the old and
|
||||||
* keep public and secret keyrings in sync.
|
* keep public and secret keyrings in sync.
|
||||||
*/
|
*/
|
||||||
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing publicRing, Progressable progress, String expectedFingerprint) {
|
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing publicRing, Progressable progress,
|
||||||
|
String expectedFingerprint,
|
||||||
|
ArrayList<CanonicalizedKeyRing> canKeyRings,
|
||||||
|
boolean skipSave) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
long masterKeyId = publicRing.getMasterKeyId();
|
long masterKeyId = publicRing.getMasterKeyId();
|
||||||
@@ -997,12 +1001,21 @@ public class ProviderHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, canSecretRing != null);
|
int result = SaveKeyringResult.SAVED_PUBLIC;
|
||||||
|
if (!skipSave) {
|
||||||
|
result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, canSecretRing != null);
|
||||||
|
}
|
||||||
|
if (canKeyRings != null) canKeyRings.add(canPublicRing);
|
||||||
|
|
||||||
// Save the saved keyring (if any)
|
// Save the saved keyring (if any)
|
||||||
if (canSecretRing != null) {
|
if (canSecretRing != null) {
|
||||||
progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100);
|
progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100);
|
||||||
int secretResult = saveCanonicalizedSecretKeyRing(canSecretRing);
|
|
||||||
|
int secretResult = SaveKeyringResult.SAVED_SECRET;
|
||||||
|
if (!skipSave) {
|
||||||
|
saveCanonicalizedSecretKeyRing(canSecretRing);
|
||||||
|
}
|
||||||
|
|
||||||
if ((secretResult & SaveKeyringResult.RESULT_ERROR) != SaveKeyringResult.RESULT_ERROR) {
|
if ((secretResult & SaveKeyringResult.RESULT_ERROR) != SaveKeyringResult.RESULT_ERROR) {
|
||||||
result |= SaveKeyringResult.SAVED_SECRET;
|
result |= SaveKeyringResult.SAVED_SECRET;
|
||||||
}
|
}
|
||||||
@@ -1020,6 +1033,12 @@ public class ProviderHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public SaveKeyringResult saveSecretKeyRing(UncachedKeyRing secretRing, Progressable progress) {
|
public SaveKeyringResult saveSecretKeyRing(UncachedKeyRing secretRing, Progressable progress) {
|
||||||
|
return saveSecretKeyRing(secretRing, progress, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SaveKeyringResult saveSecretKeyRing(UncachedKeyRing secretRing, Progressable progress,
|
||||||
|
ArrayList<CanonicalizedKeyRing> canKeyRings,
|
||||||
|
boolean skipSave) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
long masterKeyId = secretRing.getMasterKeyId();
|
long masterKeyId = secretRing.getMasterKeyId();
|
||||||
@@ -1109,15 +1128,22 @@ public class ProviderHelper {
|
|||||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
int result;
|
int publicResult = SaveKeyringResult.SAVED_PUBLIC;
|
||||||
|
if (!skipSave) {
|
||||||
|
publicResult = saveCanonicalizedPublicKeyRing(canPublicRing, progress, true);
|
||||||
|
}
|
||||||
|
|
||||||
result = saveCanonicalizedPublicKeyRing(canPublicRing, progress, true);
|
if ((publicResult & SaveKeyringResult.RESULT_ERROR) == SaveKeyringResult.RESULT_ERROR) {
|
||||||
if ((result & SaveKeyringResult.RESULT_ERROR) == SaveKeyringResult.RESULT_ERROR) {
|
|
||||||
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100);
|
progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100);
|
||||||
result = saveCanonicalizedSecretKeyRing(canSecretRing);
|
|
||||||
|
int result = SaveKeyringResult.SAVED_SECRET;
|
||||||
|
if (!skipSave) {
|
||||||
|
result = saveCanonicalizedSecretKeyRing(canSecretRing);
|
||||||
|
}
|
||||||
|
if (canKeyRings != null) canKeyRings.add(canSecretRing);
|
||||||
|
|
||||||
return new SaveKeyringResult(result, mLog, canSecretRing);
|
return new SaveKeyringResult(result, mLog, canSecretRing);
|
||||||
|
|
||||||
@@ -1350,7 +1376,7 @@ public class ProviderHelper {
|
|||||||
|
|
||||||
ImportKeyResult result = new ImportOperation(mContext, this,
|
ImportKeyResult result = new ImportOperation(mContext, this,
|
||||||
new ProgressFixedScaler(progress, 10, 25, 100, R.string.progress_con_reimport))
|
new ProgressFixedScaler(progress, 10, 25, 100, R.string.progress_con_reimport))
|
||||||
.serialKeyRingImport(itSecrets, numSecrets, null, null);
|
.serialKeyRingImport(itSecrets, numSecrets, null, null, false);
|
||||||
log.add(result, indent);
|
log.add(result, indent);
|
||||||
} else {
|
} else {
|
||||||
log.add(LogType.MSG_CON_REIMPORT_SECRET_SKIP, indent);
|
log.add(LogType.MSG_CON_REIMPORT_SECRET_SKIP, indent);
|
||||||
@@ -1378,7 +1404,7 @@ public class ProviderHelper {
|
|||||||
|
|
||||||
ImportKeyResult result = new ImportOperation(mContext, this,
|
ImportKeyResult result = new ImportOperation(mContext, this,
|
||||||
new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport))
|
new ProgressFixedScaler(progress, 25, 99, 100, R.string.progress_con_reimport))
|
||||||
.serialKeyRingImport(itPublics, numPublics, null, null);
|
.serialKeyRingImport(itPublics, numPublics, null, null, false);
|
||||||
log.add(result, indent);
|
log.add(result, indent);
|
||||||
// re-insert our backed up list of updated key times
|
// re-insert our backed up list of updated key times
|
||||||
// TODO: can this cause issues in case a public key re-import failed?
|
// TODO: can this cause issues in case a public key re-import failed?
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.service;
|
|||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -28,12 +29,18 @@ public class ImportKeyringParcel implements Parcelable {
|
|||||||
// if null, keys are expected to be read from a cache file in ImportExportOperations
|
// if null, keys are expected to be read from a cache file in ImportExportOperations
|
||||||
public ArrayList<ParcelableKeyRing> mKeyList;
|
public ArrayList<ParcelableKeyRing> mKeyList;
|
||||||
public String mKeyserver; // must be set if keys are to be imported from a keyserver
|
public String mKeyserver; // must be set if keys are to be imported from a keyserver
|
||||||
|
public boolean mSkipSave = false; // don't save the key, only return it as part of result
|
||||||
|
|
||||||
public ImportKeyringParcel (ArrayList<ParcelableKeyRing> keyList, String keyserver) {
|
public ImportKeyringParcel(ArrayList<ParcelableKeyRing> keyList, String keyserver) {
|
||||||
mKeyList = keyList;
|
mKeyList = keyList;
|
||||||
mKeyserver = keyserver;
|
mKeyserver = keyserver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ImportKeyringParcel(ArrayList<ParcelableKeyRing> keyList, String keyserver, boolean skipSave) {
|
||||||
|
this(keyList, keyserver);
|
||||||
|
mSkipSave = skipSave;
|
||||||
|
}
|
||||||
|
|
||||||
protected ImportKeyringParcel(Parcel in) {
|
protected ImportKeyringParcel(Parcel in) {
|
||||||
if (in.readByte() == 0x01) {
|
if (in.readByte() == 0x01) {
|
||||||
mKeyList = new ArrayList<>();
|
mKeyList = new ArrayList<>();
|
||||||
@@ -42,6 +49,7 @@ public class ImportKeyringParcel implements Parcelable {
|
|||||||
mKeyList = null;
|
mKeyList = null;
|
||||||
}
|
}
|
||||||
mKeyserver = in.readString();
|
mKeyserver = in.readString();
|
||||||
|
mSkipSave = in.readInt() != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -58,6 +66,7 @@ public class ImportKeyringParcel implements Parcelable {
|
|||||||
dest.writeList(mKeyList);
|
dest.writeList(mKeyList);
|
||||||
}
|
}
|
||||||
dest.writeString(mKeyserver);
|
dest.writeString(mKeyserver);
|
||||||
|
dest.writeInt(mSkipSave ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Parcelable.Creator<ImportKeyringParcel> CREATOR = new Parcelable.Creator<ImportKeyringParcel>() {
|
public static final Parcelable.Creator<ImportKeyringParcel> CREATOR = new Parcelable.Creator<ImportKeyringParcel>() {
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ import org.sufficientlysecure.keychain.R;
|
|||||||
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
|
import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
|
||||||
import org.sufficientlysecure.keychain.keyimport.FacebookKeyserver;
|
import org.sufficientlysecure.keychain.keyimport.FacebookKeyserver;
|
||||||
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
||||||
import org.sufficientlysecure.keychain.keyimport.processing.BytesLoaderState;
|
|
||||||
import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState;
|
|
||||||
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
|
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
|
||||||
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysOperationCallback;
|
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysOperationCallback;
|
||||||
import org.sufficientlysecure.keychain.keyimport.processing.LoaderState;
|
import org.sufficientlysecure.keychain.keyimport.processing.LoaderState;
|
||||||
@@ -49,7 +47,6 @@ import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
|||||||
import org.sufficientlysecure.keychain.util.Preferences;
|
import org.sufficientlysecure.keychain.util.Preferences;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
public class ImportKeysActivity extends BaseActivity implements ImportKeysListener {
|
public class ImportKeysActivity extends BaseActivity implements ImportKeysListener {
|
||||||
|
|
||||||
@@ -333,55 +330,11 @@ public class ImportKeysActivity extends BaseActivity implements ImportKeysListen
|
|||||||
listFragment.loadState(loaderState);
|
listFragment.loadState(loaderState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void importKey(ParcelableKeyRing keyRing) {
|
|
||||||
FragmentManager fM = getSupportFragmentManager();
|
|
||||||
ImportKeysListFragment listFragment = (ImportKeysListFragment) fM.findFragmentByTag(TAG_FRAG_LIST);
|
|
||||||
|
|
||||||
String keyserver = null;
|
|
||||||
ArrayList<ParcelableKeyRing> keyList = null;
|
|
||||||
|
|
||||||
Log.d(Constants.TAG, "importKey started");
|
|
||||||
|
|
||||||
LoaderState loaderState = listFragment.getState();
|
|
||||||
if (loaderState instanceof BytesLoaderState) {
|
|
||||||
// instead of giving the entries by Intent extra, cache them into a
|
|
||||||
// file to prevent Java Binder problems on heavy imports
|
|
||||||
// read FileImportCache for more info.
|
|
||||||
try {
|
|
||||||
// We parcel this iteratively into a file - anything we can
|
|
||||||
// display here, we should be able to import.
|
|
||||||
ParcelableFileCache<ParcelableKeyRing> cache =
|
|
||||||
new ParcelableFileCache<>(this, ImportOperation.CACHE_FILE_NAME);
|
|
||||||
cache.writeCache(keyRing);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(Constants.TAG, "Problem writing cache file", e);
|
|
||||||
Notify.create(this, "Problem writing cache file!", Notify.Style.ERROR).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (loaderState instanceof CloudLoaderState) {
|
|
||||||
ArrayList<ParcelableKeyRing> keys = new ArrayList<>();
|
|
||||||
keys.add(keyRing);
|
|
||||||
|
|
||||||
keyList = keys;
|
|
||||||
keyserver = ((CloudLoaderState) loaderState).mCloudPrefs.keyserver;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImportKeysOperationCallback cb = new ImportKeysOperationCallback(this, keyserver, keyList);
|
|
||||||
mOperationHelper = new CryptoOperationHelper(1, this, cb, R.string.progress_importing);
|
|
||||||
mOperationHelper.cryptoOperation();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void importKeys() {
|
public void importKeys() {
|
||||||
FragmentManager fM = getSupportFragmentManager();
|
FragmentManager fM = getSupportFragmentManager();
|
||||||
ImportKeysListFragment listFragment = (ImportKeysListFragment) fM.findFragmentByTag(TAG_FRAG_LIST);
|
ImportKeysListFragment listFragment = (ImportKeysListFragment) fM.findFragmentByTag(TAG_FRAG_LIST);
|
||||||
|
|
||||||
if (listFragment.getEntries().size() == 0) {
|
|
||||||
Notify.create(this, R.string.error_nothing_import_selected, Notify.Style.ERROR).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d(Constants.TAG, "importKeys started");
|
Log.d(Constants.TAG, "importKeys started");
|
||||||
// instead of giving the entries by Intent extra, cache them into a
|
// instead of giving the entries by Intent extra, cache them into a
|
||||||
// file to prevent Java Binder problems on heavy imports
|
// file to prevent Java Binder problems on heavy imports
|
||||||
@@ -398,8 +351,10 @@ public class ImportKeysActivity extends BaseActivity implements ImportKeysListen
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImportKeysOperationCallback callback = new ImportKeysOperationCallback(this, null, null);
|
ImportKeyringParcel inputParcel = new ImportKeyringParcel(null, null);
|
||||||
new CryptoOperationHelper(1, this, callback, R.string.progress_importing).cryptoOperation();
|
ImportKeysOperationCallback callback = new ImportKeysOperationCallback(this, inputParcel);
|
||||||
|
mOperationHelper = new CryptoOperationHelper(1, this, callback, R.string.progress_importing);
|
||||||
|
mOperationHelper.cryptoOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ public class ImportKeysListFragment extends Fragment implements
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ImportKeysListEntry> getEntries() {
|
private List<ImportKeysListEntry> getEntries() {
|
||||||
if (mAdapter != null) {
|
if (mAdapter != null) {
|
||||||
return mAdapter.getEntries();
|
return mAdapter.getEntries();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -17,10 +17,10 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui.adapter;
|
package org.sufficientlysecure.keychain.ui.adapter;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.databinding.DataBindingUtil;
|
import android.databinding.DataBindingUtil;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.support.v4.app.FragmentActivity;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -28,6 +28,7 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.openintents.openpgp.util.OpenPgpUtils;
|
import org.openintents.openpgp.util.OpenPgpUtils;
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.databinding.ImportKeysListItemBinding;
|
import org.sufficientlysecure.keychain.databinding.ImportKeysListItemBinding;
|
||||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||||
@@ -35,14 +36,24 @@ import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
|
|||||||
import org.sufficientlysecure.keychain.keyimport.processing.BytesLoaderState;
|
import org.sufficientlysecure.keychain.keyimport.processing.BytesLoaderState;
|
||||||
import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState;
|
import org.sufficientlysecure.keychain.keyimport.processing.CloudLoaderState;
|
||||||
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
|
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysListener;
|
||||||
|
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysOperationCallback;
|
||||||
|
import org.sufficientlysecure.keychain.keyimport.processing.ImportKeysResultListener;
|
||||||
import org.sufficientlysecure.keychain.keyimport.processing.LoaderState;
|
import org.sufficientlysecure.keychain.keyimport.processing.LoaderState;
|
||||||
import org.sufficientlysecure.keychain.operations.ImportOperation;
|
import org.sufficientlysecure.keychain.operations.ImportOperation;
|
||||||
|
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
|
||||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||||
|
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
||||||
|
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
|
||||||
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.ui.util.Highlighter;
|
import org.sufficientlysecure.keychain.ui.util.Highlighter;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||||
|
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
import org.sufficientlysecure.keychain.util.ParcelableFileCache;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -50,35 +61,21 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class ImportKeysAdapter extends RecyclerView.Adapter<ImportKeysAdapter.ViewHolder> {
|
public class ImportKeysAdapter extends RecyclerView.Adapter<ImportKeysAdapter.ViewHolder> implements ImportKeysResultListener {
|
||||||
|
|
||||||
private Context mContext;
|
private FragmentActivity mActivity;
|
||||||
private ImportKeysListener mListener;
|
private ImportKeysResultListener mListener;
|
||||||
private boolean mNonInteractive;
|
private boolean mNonInteractive;
|
||||||
|
|
||||||
private LoaderState mLoaderState;
|
private LoaderState mLoaderState;
|
||||||
private List<ImportKeysListEntry> mData;
|
private List<ImportKeysListEntry> mData;
|
||||||
|
|
||||||
public ImportKeysAdapter(Context mContext, ImportKeysListener listener, boolean mNonInteractive) {
|
public ImportKeysAdapter(FragmentActivity activity, ImportKeysListener listener, boolean mNonInteractive) {
|
||||||
this.mContext = mContext;
|
this.mActivity = activity;
|
||||||
this.mListener = listener;
|
this.mListener = listener;
|
||||||
this.mNonInteractive = mNonInteractive;
|
this.mNonInteractive = mNonInteractive;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
public ImportKeysListItemBinding binding;
|
|
||||||
|
|
||||||
public ViewHolder(View view) {
|
|
||||||
super(view);
|
|
||||||
binding = DataBindingUtil.bind(view);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearData() {
|
|
||||||
mData = null;
|
|
||||||
notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLoaderState(LoaderState loaderState) {
|
public void setLoaderState(LoaderState loaderState) {
|
||||||
this.mLoaderState = loaderState;
|
this.mLoaderState = loaderState;
|
||||||
}
|
}
|
||||||
@@ -88,6 +85,11 @@ public class ImportKeysAdapter extends RecyclerView.Adapter<ImportKeysAdapter.Vi
|
|||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clearData() {
|
||||||
|
mData = null;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns a list of all selected entries, with public keys sorted
|
* This method returns a list of all selected entries, with public keys sorted
|
||||||
* before secret keys, see ImportOperation for specifics.
|
* before secret keys, see ImportOperation for specifics.
|
||||||
@@ -109,22 +111,31 @@ public class ImportKeysAdapter extends RecyclerView.Adapter<ImportKeysAdapter.Vi
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
public ImportKeysListItemBinding binding;
|
||||||
|
|
||||||
|
public ViewHolder(View view) {
|
||||||
|
super(view);
|
||||||
|
binding = DataBindingUtil.bind(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
LayoutInflater inflater = LayoutInflater.from(mActivity);
|
||||||
View v = inflater.inflate(R.layout.import_keys_list_item, parent, false);
|
View v = inflater.inflate(R.layout.import_keys_list_item, parent, false);
|
||||||
ViewHolder vh = new ViewHolder(v);
|
ViewHolder vh = new ViewHolder(v);
|
||||||
return vh;
|
return vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(final ViewHolder holder, int position) {
|
public void onBindViewHolder(final ViewHolder holder, final int position) {
|
||||||
final ImportKeysListItemBinding b = holder.binding;
|
final ImportKeysListItemBinding b = holder.binding;
|
||||||
final ImportKeysListEntry entry = mData.get(position);
|
final ImportKeysListEntry entry = mData.get(position);
|
||||||
|
|
||||||
Resources resources = mContext.getResources();
|
Resources resources = mActivity.getResources();
|
||||||
Highlighter highlighter = new Highlighter(mContext, entry.getQuery());
|
Highlighter highlighter = new Highlighter(mActivity, entry.getQuery());
|
||||||
b.setStandardColor(FormattingUtils.getColorFromAttr(mContext, R.attr.colorText));
|
b.setStandardColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText));
|
||||||
b.setRevokedExpiredColor(resources.getColor(R.color.key_flag_gray));
|
b.setRevokedExpiredColor(resources.getColor(R.color.key_flag_gray));
|
||||||
b.setSecretColor(Color.RED);
|
b.setSecretColor(Color.RED);
|
||||||
b.setHighlighter(highlighter);
|
b.setHighlighter(highlighter);
|
||||||
@@ -139,94 +150,101 @@ public class ImportKeysAdapter extends RecyclerView.Adapter<ImportKeysAdapter.Vi
|
|||||||
b.setAlgorithm(entry.getAlgorithm());
|
b.setAlgorithm(entry.getAlgorithm());
|
||||||
b.setUserId(userIdSplit.name);
|
b.setUserId(userIdSplit.name);
|
||||||
b.setUserIdEmail(userIdSplit.email);
|
b.setUserIdEmail(userIdSplit.email);
|
||||||
b.setKeyId(KeyFormattingUtils.beautifyKeyIdWithPrefix(mContext, entry.getKeyIdHex()));
|
b.setKeyId(KeyFormattingUtils.beautifyKeyIdWithPrefix(mActivity, entry.getKeyIdHex()));
|
||||||
|
|
||||||
if (entry.isRevoked()) {
|
if (entry.isRevoked()) {
|
||||||
KeyFormattingUtils.setStatusImage(mContext, b.status, null, State.REVOKED, R.color.key_flag_gray);
|
KeyFormattingUtils.setStatusImage(mActivity, b.status, null,
|
||||||
|
State.REVOKED, R.color.key_flag_gray);
|
||||||
} else if (entry.isExpired()) {
|
} else if (entry.isExpired()) {
|
||||||
KeyFormattingUtils.setStatusImage(mContext, b.status, null, State.EXPIRED, R.color.key_flag_gray);
|
KeyFormattingUtils.setStatusImage(mActivity, b.status, null,
|
||||||
|
State.EXPIRED, R.color.key_flag_gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
b.importKey.setOnClickListener(new View.OnClickListener() {
|
b.importKey.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
if (mLoaderState instanceof BytesLoaderState) {
|
if (mLoaderState instanceof BytesLoaderState) {
|
||||||
mListener.importKey(new ParcelableKeyRing(entry.getEncodedRing()));
|
importKey(new ParcelableKeyRing(entry.getEncodedRing()));
|
||||||
} else if (mLoaderState instanceof CloudLoaderState) {
|
} else if (mLoaderState instanceof CloudLoaderState) {
|
||||||
mListener.importKey(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex(),
|
importKey(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex(),
|
||||||
entry.getKeybaseName(), entry.getFbUsername()));
|
entry.getKeybaseName(), entry.getFbUsername()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
b.expand.setOnClickListener(new View.OnClickListener() {
|
b.expand.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
boolean hidden = b.extraContainer.getVisibility() == View.GONE;
|
boolean hidden = b.extraContainer.getVisibility() == View.GONE;
|
||||||
b.extraContainer.setVisibility(hidden ? View.VISIBLE : View.GONE);
|
b.extraContainer.setVisibility(hidden ? View.VISIBLE : View.GONE);
|
||||||
b.expand.animate().rotation(hidden ? 180 : 0).start();
|
b.expand.animate().rotation(hidden ? 180 : 0).start();
|
||||||
|
|
||||||
|
if (hidden) {
|
||||||
|
if (mLoaderState instanceof BytesLoaderState) {
|
||||||
|
getKey(new ParcelableKeyRing(entry.getEncodedRing()));
|
||||||
|
} else if (mLoaderState instanceof CloudLoaderState) {
|
||||||
|
getKey(new ParcelableKeyRing(entry.getFingerprintHex(), entry.getKeyIdHex(),
|
||||||
|
entry.getKeybaseName(), entry.getFbUsername()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (entry.getUserIds().size() == 1) {
|
b.userIdsList.setVisibility(entry.getUserIds().size() == 1 ? View.GONE : View.VISIBLE);
|
||||||
b.userIdsList.setVisibility(View.GONE);
|
// destroyLoader view from holder
|
||||||
} else {
|
b.userIdsList.removeAllViews();
|
||||||
b.userIdsList.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
// destroyLoader view from holder
|
// we want conventional gpg UserIDs first, then Keybase ”proofs”
|
||||||
b.userIdsList.removeAllViews();
|
HashMap<String, HashSet<String>> mergedUserIds = entry.getMergedUserIds();
|
||||||
|
ArrayList<Map.Entry<String, HashSet<String>>> sortedIds = new ArrayList<Map.Entry<String, HashSet<String>>>(mergedUserIds.entrySet());
|
||||||
|
Collections.sort(sortedIds, new java.util.Comparator<Map.Entry<String, HashSet<String>>>() {
|
||||||
|
@Override
|
||||||
|
public int compare(Map.Entry<String, HashSet<String>> entry1, Map.Entry<String, HashSet<String>> entry2) {
|
||||||
|
|
||||||
// we want conventional gpg UserIDs first, then Keybase ”proofs”
|
// sort keybase UserIds after non-Keybase
|
||||||
HashMap<String, HashSet<String>> mergedUserIds = entry.getMergedUserIds();
|
boolean e1IsKeybase = entry1.getKey().contains(":");
|
||||||
ArrayList<Map.Entry<String, HashSet<String>>> sortedIds = new ArrayList<Map.Entry<String, HashSet<String>>>(mergedUserIds.entrySet());
|
boolean e2IsKeybase = entry2.getKey().contains(":");
|
||||||
Collections.sort(sortedIds, new java.util.Comparator<Map.Entry<String, HashSet<String>>>() {
|
if (e1IsKeybase != e2IsKeybase) {
|
||||||
@Override
|
return (e1IsKeybase) ? 1 : -1;
|
||||||
public int compare(Map.Entry<String, HashSet<String>> entry1, Map.Entry<String, HashSet<String>> entry2) {
|
|
||||||
|
|
||||||
// sort keybase UserIds after non-Keybase
|
|
||||||
boolean e1IsKeybase = entry1.getKey().contains(":");
|
|
||||||
boolean e2IsKeybase = entry2.getKey().contains(":");
|
|
||||||
if (e1IsKeybase != e2IsKeybase) {
|
|
||||||
return (e1IsKeybase) ? 1 : -1;
|
|
||||||
}
|
|
||||||
return entry1.getKey().compareTo(entry2.getKey());
|
|
||||||
}
|
}
|
||||||
});
|
return entry1.getKey().compareTo(entry2.getKey());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
for (Map.Entry<String, HashSet<String>> pair : sortedIds) {
|
for (Map.Entry<String, HashSet<String>> pair : sortedIds) {
|
||||||
String cUserId = pair.getKey();
|
String cUserId = pair.getKey();
|
||||||
HashSet<String> cEmails = pair.getValue();
|
HashSet<String> cEmails = pair.getValue();
|
||||||
|
|
||||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
LayoutInflater inflater = LayoutInflater.from(mActivity);
|
||||||
|
|
||||||
TextView uidView = (TextView) inflater.inflate(
|
TextView uidView = (TextView) inflater.inflate(
|
||||||
|
R.layout.import_keys_list_entry_user_id, null);
|
||||||
|
uidView.setText(highlighter.highlight(cUserId));
|
||||||
|
uidView.setPadding(0, 0, FormattingUtils.dpToPx(mActivity, 8), 0);
|
||||||
|
|
||||||
|
if (entry.isRevoked() || entry.isExpired()) {
|
||||||
|
uidView.setTextColor(mActivity.getResources().getColor(R.color.key_flag_gray));
|
||||||
|
} else {
|
||||||
|
uidView.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText));
|
||||||
|
}
|
||||||
|
|
||||||
|
b.userIdsList.addView(uidView);
|
||||||
|
|
||||||
|
for (String email : cEmails) {
|
||||||
|
TextView emailView = (TextView) inflater.inflate(
|
||||||
R.layout.import_keys_list_entry_user_id, null);
|
R.layout.import_keys_list_entry_user_id, null);
|
||||||
uidView.setText(highlighter.highlight(cUserId));
|
emailView.setPadding(
|
||||||
uidView.setPadding(0, 0, FormattingUtils.dpToPx(mContext, 8), 0);
|
FormattingUtils.dpToPx(mActivity, 16), 0,
|
||||||
|
FormattingUtils.dpToPx(mActivity, 8), 0);
|
||||||
|
emailView.setText(highlighter.highlight(email));
|
||||||
|
|
||||||
if (entry.isRevoked() || entry.isExpired()) {
|
if (entry.isRevoked() || entry.isExpired()) {
|
||||||
uidView.setTextColor(mContext.getResources().getColor(R.color.key_flag_gray));
|
emailView.setTextColor(mActivity.getResources().getColor(R.color.key_flag_gray));
|
||||||
} else {
|
} else {
|
||||||
uidView.setTextColor(FormattingUtils.getColorFromAttr(mContext, R.attr.colorText));
|
emailView.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText));
|
||||||
}
|
}
|
||||||
|
|
||||||
b.userIdsList.addView(uidView);
|
b.userIdsList.addView(emailView);
|
||||||
|
|
||||||
for (String email : cEmails) {
|
|
||||||
TextView emailView = (TextView) inflater.inflate(
|
|
||||||
R.layout.import_keys_list_entry_user_id, null);
|
|
||||||
emailView.setPadding(
|
|
||||||
FormattingUtils.dpToPx(mContext, 16), 0,
|
|
||||||
FormattingUtils.dpToPx(mContext, 8), 0);
|
|
||||||
emailView.setText(highlighter.highlight(email));
|
|
||||||
|
|
||||||
if (entry.isRevoked() || entry.isExpired()) {
|
|
||||||
emailView.setTextColor(mContext.getResources().getColor(R.color.key_flag_gray));
|
|
||||||
} else {
|
|
||||||
emailView.setTextColor(FormattingUtils.getColorFromAttr(mContext, R.attr.colorText));
|
|
||||||
}
|
|
||||||
|
|
||||||
b.userIdsList.addView(emailView);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,4 +254,68 @@ public class ImportKeysAdapter extends RecyclerView.Adapter<ImportKeysAdapter.Vi
|
|||||||
return mData != null ? mData.size() : 0;
|
return mData != null ? mData.size() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void importKey(ParcelableKeyRing keyRing) {
|
||||||
|
ImportKeyringParcel inputParcel = prepareKeyOperation(keyRing, false);
|
||||||
|
ImportKeysOperationCallback cb = new ImportKeysOperationCallback(mListener, inputParcel);
|
||||||
|
CryptoOperationHelper operationHelper = new CryptoOperationHelper(1, mActivity, cb, R.string.progress_importing);
|
||||||
|
operationHelper.cryptoOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getKey(ParcelableKeyRing keyRing) {
|
||||||
|
ImportKeyringParcel inputParcel = prepareKeyOperation(keyRing, true);
|
||||||
|
ImportKeysOperationCallback cb = new ImportKeysOperationCallback(this, inputParcel);
|
||||||
|
CryptoOperationHelper operationHelper = new CryptoOperationHelper(1, mActivity, cb, R.string.progress_downloading);
|
||||||
|
operationHelper.cryptoOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImportKeyringParcel prepareKeyOperation(ParcelableKeyRing keyRing, boolean skipSave) {
|
||||||
|
Log.d(Constants.TAG, "prepareKey started");
|
||||||
|
|
||||||
|
ArrayList<ParcelableKeyRing> keysList = null;
|
||||||
|
String keyserver = null;
|
||||||
|
|
||||||
|
if (mLoaderState instanceof BytesLoaderState) {
|
||||||
|
// instead of giving the entries by Intent extra, cache them into a
|
||||||
|
// file to prevent Java Binder problems on heavy imports
|
||||||
|
// read FileImportCache for more info.
|
||||||
|
try {
|
||||||
|
// We parcel this iteratively into a file - anything we can
|
||||||
|
// display here, we should be able to import.
|
||||||
|
ParcelableFileCache<ParcelableKeyRing> cache =
|
||||||
|
new ParcelableFileCache<>(mActivity, ImportOperation.CACHE_FILE_NAME);
|
||||||
|
cache.writeCache(keyRing);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(Constants.TAG, "Problem writing cache file", e);
|
||||||
|
Notify.create(mActivity, "Problem writing cache file!", Notify.Style.ERROR).show();
|
||||||
|
}
|
||||||
|
} else if (mLoaderState instanceof CloudLoaderState) {
|
||||||
|
ArrayList<ParcelableKeyRing> keys = new ArrayList<>();
|
||||||
|
keys.add(keyRing);
|
||||||
|
|
||||||
|
keysList = keys;
|
||||||
|
keyserver = ((CloudLoaderState) mLoaderState).mCloudPrefs.keyserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImportKeyringParcel keyringParcel = new ImportKeyringParcel(keysList, keyserver, skipSave);
|
||||||
|
return keyringParcel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleResult(ImportKeyResult result) {
|
||||||
|
boolean resultStatus = result.isOkBoth();
|
||||||
|
Log.e(Constants.TAG, "getKey result: " + resultStatus);
|
||||||
|
if (resultStatus) {
|
||||||
|
ArrayList<CanonicalizedKeyRing> canKeyRings = result.mCanonicalizedKeyRings;
|
||||||
|
int retrievedNumber = canKeyRings.size();
|
||||||
|
|
||||||
|
if (retrievedNumber == 1) {
|
||||||
|
CanonicalizedKeyRing keyRing = canKeyRings.get(0);
|
||||||
|
Log.e(Constants.TAG, "Key ID: " + keyRing.getMasterKeyId() +
|
||||||
|
"| isRev: " + keyRing.isRevoked() + "| isExp: " + keyRing.isExpired());
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("getKey retrieved more than one key.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text='@{userIdEmail != null ? "Key ID: " + userIdEmail : ""}'
|
android:text='@{keyId != null ? "Key ID: " + keyId : ""}'
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:textColor="@{revoked || expired ? revokedExpiredColor : standardColor}" />
|
android:textColor="@{revoked || expired ? revokedExpiredColor : standardColor}" />
|
||||||
|
|
||||||
|
|||||||
@@ -419,6 +419,7 @@
|
|||||||
<string name="progress_done">"Done."</string>
|
<string name="progress_done">"Done."</string>
|
||||||
<string name="progress_cancel">"Cancel"</string>
|
<string name="progress_cancel">"Cancel"</string>
|
||||||
<string name="progress_cancelling">"cancelling…"</string>
|
<string name="progress_cancelling">"cancelling…"</string>
|
||||||
|
<string name="progress_downloading">"downloading…"</string>
|
||||||
<string name="progress_saving">"saving…"</string>
|
<string name="progress_saving">"saving…"</string>
|
||||||
<string name="progress_importing">"importing…"</string>
|
<string name="progress_importing">"importing…"</string>
|
||||||
<string name="progress_benchmarking">"benchmarking…"</string>
|
<string name="progress_benchmarking">"benchmarking…"</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user