Merge branch 'v/instrument' into v/multi-decrypt
Conflicts: .travis.yml OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/CreateKeyActivityTest.java OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
This commit is contained in:
@@ -74,6 +74,10 @@ public class ClipboardReflection {
|
||||
Method methodGetPrimaryClip = clipboard.getClass().getMethod("getPrimaryClip");
|
||||
Object clipData = methodGetPrimaryClip.invoke(clipboard);
|
||||
|
||||
if (clipData == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ClipData.Item clipDataItem = clipData.getItemAt(0);
|
||||
Method methodGetItemAt = clipData.getClass().getMethod("getItemAt", int.class);
|
||||
Object clipDataItem = methodGetItemAt.invoke(clipData, 0);
|
||||
|
||||
@@ -82,7 +82,7 @@ public class EditKeyOperation extends BaseOperation<SaveKeyringParcel> {
|
||||
CanonicalizedSecretKeyRing secRing =
|
||||
mProviderHelper.getCanonicalizedSecretKeyRing(saveParcel.mMasterKeyId);
|
||||
|
||||
modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel, log, 2);
|
||||
modifyResult = keyOperations.modifySecretKeyRing(secRing, cryptoInput, saveParcel);
|
||||
if (modifyResult.isPending()) {
|
||||
return modifyResult;
|
||||
}
|
||||
|
||||
@@ -854,7 +854,11 @@ public abstract class OperationResult implements Parcelable {
|
||||
if (mParcels.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return mParcels.get(mParcels.size() -1);
|
||||
LogEntryParcel last = mParcels.get(mParcels.size() -1);
|
||||
if (last instanceof SubLogEntryParcel) {
|
||||
return ((SubLogEntryParcel) last).getSubResult().getLog().getLast();
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -148,7 +148,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
|
||||
return verifySignedLiteralData(input, aIn, outputStream, 0);
|
||||
} else if (aIn.isClearText()) {
|
||||
// a cleartext signature, verify it with the other method
|
||||
return verifyCleartextSignature(aIn, 0);
|
||||
return verifyCleartextSignature(aIn, outputStream, 0);
|
||||
} else {
|
||||
// else: ascii armored encryption! go on...
|
||||
return decryptVerify(input, cryptoInput, in, outputStream, 0);
|
||||
@@ -767,20 +767,11 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
|
||||
// TODO: slow annealing to fake a progress?
|
||||
}
|
||||
|
||||
// after going through the stream, size should be available
|
||||
Long originalSize = literalData.getDataLengthIfAvailable();
|
||||
if (originalSize != null) {
|
||||
log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1,
|
||||
Long.toString(originalSize));
|
||||
} else {
|
||||
log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1);
|
||||
}
|
||||
|
||||
metadata = new OpenPgpMetadata(
|
||||
originalFilename,
|
||||
mimeType,
|
||||
literalData.getModificationTime().getTime(),
|
||||
originalSize == null ? 0 : originalSize);
|
||||
alreadyWritten);
|
||||
|
||||
if (signature != null) {
|
||||
updateProgress(R.string.progress_verifying_signature, 90, 100);
|
||||
@@ -859,7 +850,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
|
||||
* pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
|
||||
*/
|
||||
private DecryptVerifyResult verifyCleartextSignature(
|
||||
ArmoredInputStream aIn, int indent) throws IOException, PGPException {
|
||||
ArmoredInputStream aIn, OutputStream outputStream, int indent) throws IOException, PGPException {
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
|
||||
@@ -889,8 +880,9 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
|
||||
out.close();
|
||||
|
||||
byte[] clearText = out.toByteArray();
|
||||
if (out != null) {
|
||||
out.write(clearText);
|
||||
if (outputStream != null) {
|
||||
outputStream.write(clearText);
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_processing_signature, 60, 100);
|
||||
|
||||
@@ -365,14 +365,9 @@ public class PgpKeyOperation {
|
||||
public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR,
|
||||
CryptoInputParcel cryptoInput,
|
||||
SaveKeyringParcel saveParcel) {
|
||||
return modifySecretKeyRing(wsKR, cryptoInput, saveParcel, new OperationLog(), 0);
|
||||
}
|
||||
|
||||
public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR,
|
||||
CryptoInputParcel cryptoInput,
|
||||
SaveKeyringParcel saveParcel,
|
||||
OperationLog log,
|
||||
int indent) {
|
||||
OperationLog log = new OperationLog();
|
||||
int indent = 0;
|
||||
|
||||
/*
|
||||
* 1. Unlock private key
|
||||
|
||||
@@ -179,7 +179,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
+ Tables.API_APPS + "(" + ApiAppsAllowedKeysColumns.PACKAGE_NAME + ") ON DELETE CASCADE"
|
||||
+ ")";
|
||||
|
||||
KeychainDatabase(Context context) {
|
||||
public KeychainDatabase(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
mContext = context;
|
||||
|
||||
|
||||
@@ -149,6 +149,14 @@ public class PassphraseCacheService extends Service {
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
public static void clearCachedPassphrases(Context context) {
|
||||
Log.d(Constants.TAG, "PassphraseCacheService.clearCachedPassphrase()");
|
||||
|
||||
Intent intent = new Intent(context, PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
|
||||
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a cached passphrase from memory by sending an intent to the service. This method is
|
||||
|
||||
@@ -153,7 +153,9 @@ public class DecryptTextFragment extends DecryptFragment {
|
||||
|
||||
@Override
|
||||
protected PgpDecryptVerifyInputParcel createOperationInput() {
|
||||
return new PgpDecryptVerifyInputParcel(mCiphertext.getBytes());
|
||||
PgpDecryptVerifyInputParcel input = new PgpDecryptVerifyInputParcel(mCiphertext.getBytes());
|
||||
input.setAllowSymmetricDecryption(true);
|
||||
return input;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
@@ -26,8 +25,6 @@ import android.graphics.Point;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v7.widget.DefaultItemAnimator;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@@ -49,10 +46,6 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConstants;
|
||||
import org.sufficientlysecure.keychain.pgp.SignEncryptParcel;
|
||||
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
||||
import org.sufficientlysecure.keychain.service.KeychainNewService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration;
|
||||
import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
|
||||
@@ -75,7 +75,6 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false);
|
||||
|
||||
|
||||
mSignKeySpinner = (KeySpinner) view.findViewById(R.id.sign);
|
||||
mEncryptKeyView = (EncryptKeyCompletionView) view.findViewById(R.id.recipient_list);
|
||||
mEncryptKeyView.setThreshold(1); // Start working from first character
|
||||
@@ -168,7 +167,7 @@ public class EncryptModeAsymmetricFragment extends EncryptModeFragment {
|
||||
|
||||
@Override
|
||||
public long getAsymmetricSigningKeyId() {
|
||||
return mSignKeySpinner.getSelectedItemId();
|
||||
return mSignKeySpinner.getSelectedKeyId();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -240,7 +240,7 @@ public class EncryptTextFragment
|
||||
boolean gotEncryptionKeys = (encryptionKeyIds != null
|
||||
&& encryptionKeyIds.length > 0);
|
||||
|
||||
if (!gotEncryptionKeys && signingKeyId == 0L) {
|
||||
if (!gotEncryptionKeys && signingKeyId == Constants.key.none) {
|
||||
Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
|
||||
.show(this);
|
||||
return null;
|
||||
|
||||
@@ -51,6 +51,8 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
|
||||
private static final int ID_SETTINGS = 4;
|
||||
private static final int ID_HELP = 5;
|
||||
|
||||
public static final String EXTRA_SKIP_FIRST_TIME = "skip_first_time";
|
||||
|
||||
public Drawer.Result mDrawerResult;
|
||||
private Toolbar mToolbar;
|
||||
|
||||
@@ -114,7 +116,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
|
||||
|
||||
// if this is the first time show first time activity
|
||||
Preferences prefs = Preferences.getPreferences(this);
|
||||
if (prefs.isFirstTime()) {
|
||||
if (!getIntent().getBooleanExtra(EXTRA_SKIP_FIRST_TIME, false) && prefs.isFirstTime()) {
|
||||
Intent intent = new Intent(this, CreateKeyActivity.class);
|
||||
intent.putExtra(CreateKeyActivity.EXTRA_FIRST_TIME, true);
|
||||
startActivity(intent);
|
||||
|
||||
@@ -10,7 +10,6 @@ import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.spongycastle.util.Arrays;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||
@@ -28,6 +27,7 @@ import org.sufficientlysecure.keychain.util.Preferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant
|
||||
@@ -47,6 +47,8 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
||||
private RequiredInputParcel mRequiredInput;
|
||||
private Intent mServiceIntent;
|
||||
|
||||
private static final byte[] BLANK_FINGERPRINT = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -126,17 +128,29 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
||||
}
|
||||
|
||||
if (key.canSign() || key.canCertify()) {
|
||||
nfcPutKey(0xB6, key, passphrase);
|
||||
nfcPutData(0xCE, timestampBytes);
|
||||
nfcPutData(0xC7, key.getFingerprint());
|
||||
if (shouldPutKey(key.getFingerprint(), 0)) {
|
||||
nfcPutKey(0xB6, key, passphrase);
|
||||
nfcPutData(0xCE, timestampBytes);
|
||||
nfcPutData(0xC7, key.getFingerprint());
|
||||
} else {
|
||||
throw new IOException("Key slot occupied; card must be reset to put new signature key.");
|
||||
}
|
||||
} else if (key.canEncrypt()) {
|
||||
nfcPutKey(0xB8, key, passphrase);
|
||||
nfcPutData(0xCF, timestampBytes);
|
||||
nfcPutData(0xC8, key.getFingerprint());
|
||||
if (shouldPutKey(key.getFingerprint(), 1)) {
|
||||
nfcPutKey(0xB8, key, passphrase);
|
||||
nfcPutData(0xCF, timestampBytes);
|
||||
nfcPutData(0xC8, key.getFingerprint());
|
||||
} else {
|
||||
throw new IOException("Key slot occupied; card must be reset to put new decryption key.");
|
||||
}
|
||||
} else if (key.canAuthenticate()) {
|
||||
nfcPutKey(0xA4, key, passphrase);
|
||||
nfcPutData(0xD0, timestampBytes);
|
||||
nfcPutData(0xC9, key.getFingerprint());
|
||||
if (shouldPutKey(key.getFingerprint(), 2)) {
|
||||
nfcPutKey(0xA4, key, passphrase);
|
||||
nfcPutData(0xD0, timestampBytes);
|
||||
nfcPutData(0xC9, key.getFingerprint());
|
||||
} else {
|
||||
throw new IOException("Key slot occupied; card must be reset to put new authentication key.");
|
||||
}
|
||||
} else {
|
||||
throw new IOException("Inappropriate key flags for smart card key.");
|
||||
}
|
||||
@@ -158,6 +172,18 @@ public class NfcOperationActivity extends BaseNfcActivity {
|
||||
finish();
|
||||
}
|
||||
|
||||
private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException {
|
||||
byte[] cardFingerprint = nfcGetFingerprint(idx);
|
||||
// Slot is empty, or contains this key already. PUT KEY operation is safe
|
||||
if (Arrays.equals(cardFingerprint, BLANK_FINGERPRINT) ||
|
||||
Arrays.equals(cardFingerprint, fingerprint)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Slot already contains a different key; don't overwrite it.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePinError() {
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
@@ -43,14 +44,12 @@ import android.widget.Toast;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
|
||||
@@ -75,8 +74,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
|
||||
// special extra for OpenPgpService
|
||||
public static final String EXTRA_SERVICE_INTENT = "data";
|
||||
|
||||
private static final int REQUEST_CODE_ENTER_PATTERN = 2;
|
||||
private long mSubKeyId;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -93,14 +91,13 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
|
||||
// this activity itself has no content view (see manifest)
|
||||
|
||||
long keyId;
|
||||
if (getIntent().hasExtra(EXTRA_SUBKEY_ID)) {
|
||||
keyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0);
|
||||
mSubKeyId = getIntent().getLongExtra(EXTRA_SUBKEY_ID, 0);
|
||||
} else {
|
||||
RequiredInputParcel requiredInput = getIntent().getParcelableExtra(EXTRA_REQUIRED_INPUT);
|
||||
switch (requiredInput.mType) {
|
||||
case PASSPHRASE_SYMMETRIC: {
|
||||
keyId = Constants.key.symmetric;
|
||||
mSubKeyId = Constants.key.symmetric;
|
||||
break;
|
||||
}
|
||||
case PASSPHRASE: {
|
||||
@@ -127,7 +124,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
keyId = requiredInput.getSubKeyId();
|
||||
mSubKeyId = requiredInput.getSubKeyId();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -136,62 +133,35 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
}
|
||||
}
|
||||
|
||||
Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_SERVICE_INTENT);
|
||||
|
||||
show(this, keyId, serviceIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_ENTER_PATTERN: {
|
||||
/*
|
||||
* NOTE that there are 4 possible result codes!!!
|
||||
*/
|
||||
switch (resultCode) {
|
||||
case RESULT_OK:
|
||||
// The user passed
|
||||
break;
|
||||
case RESULT_CANCELED:
|
||||
// The user cancelled the task
|
||||
break;
|
||||
// case LockPatternActivity.RESULT_FAILED:
|
||||
// // The user failed to enter the pattern
|
||||
// break;
|
||||
// case LockPatternActivity.RESULT_FORGOT_PATTERN:
|
||||
// // The user forgot the pattern and invoked your recovery Activity.
|
||||
// break;
|
||||
}
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
/*
|
||||
* In any case, there's always a key EXTRA_RETRY_COUNT, which holds
|
||||
* the number of tries that the user did.
|
||||
*/
|
||||
// int retryCount = data.getIntExtra(
|
||||
// LockPatternActivity.EXTRA_RETRY_COUNT, 0);
|
||||
/* Show passphrase dialog to cache a new passphrase the user enters for using it later for
|
||||
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
|
||||
* for a symmetric passphrase
|
||||
*/
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
Intent serviceIntent = getIntent().getParcelableExtra(EXTRA_SERVICE_INTENT);
|
||||
|
||||
PassphraseDialogFragment frag = new PassphraseDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong(EXTRA_SUBKEY_ID, mSubKeyId);
|
||||
args.putParcelable(EXTRA_SERVICE_INTENT, serviceIntent);
|
||||
frag.setArguments(args);
|
||||
frag.show(getSupportFragmentManager(), "passphraseDialog");
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows passphrase dialog to cache a new passphrase the user enters for using it later for
|
||||
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
|
||||
* for a symmetric passphrase
|
||||
*/
|
||||
public static void show(final FragmentActivity context, final long keyId, final Intent serviceIntent) {
|
||||
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
|
||||
public void run() {
|
||||
// do NOT check if the key even needs a passphrase. that's not our job here.
|
||||
PassphraseDialogFragment frag = new PassphraseDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong(EXTRA_SUBKEY_ID, keyId);
|
||||
args.putParcelable(EXTRA_SERVICE_INTENT, serviceIntent);
|
||||
frag.setArguments(args);
|
||||
frag.show(context.getSupportFragmentManager(), "passphraseDialog");
|
||||
}
|
||||
});
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag("passphraseDialog");
|
||||
if (dialog != null) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
public static class PassphraseDialogFragment extends DialogFragment implements TextView.OnEditorActionListener {
|
||||
@@ -205,9 +175,6 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
|
||||
private Intent mServiceIntent;
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final Activity activity = getActivity();
|
||||
@@ -268,12 +235,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
userId = null;
|
||||
}
|
||||
|
||||
/* Get key type for message */
|
||||
// find a master key id for our key
|
||||
long masterKeyId = new ProviderHelper(activity).getMasterKeyId(mSubKeyId);
|
||||
CachedPublicKeyRing keyRing = new ProviderHelper(activity).getCachedPublicKeyRing(masterKeyId);
|
||||
// get the type of key (from the database)
|
||||
keyType = keyRing.getSecretKeyType(mSubKeyId);
|
||||
keyType = mSecretRing.getSecretKey(mSubKeyId).getSecretKeyType();
|
||||
switch (keyType) {
|
||||
case PASSPHRASE:
|
||||
message = getString(R.string.passphrase_for, userId);
|
||||
@@ -468,20 +430,16 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
|
||||
// note we need no synchronization here, this variable is only accessed in the ui thread
|
||||
mIsCancelled = true;
|
||||
|
||||
getActivity().setResult(RESULT_CANCELED);
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
|
||||
if (getActivity() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
hideKeyboard();
|
||||
|
||||
getActivity().setResult(RESULT_CANCELED);
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
private void hideKeyboard() {
|
||||
@@ -495,11 +453,9 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
||||
inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the "done" button on the soft keyboard with the okay button in the view
|
||||
*/
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
// Associate the "done" button on the soft keyboard with the okay button in the view
|
||||
if (EditorInfo.IME_ACTION_DONE == actionId) {
|
||||
AlertDialog dialog = ((AlertDialog) getDialog());
|
||||
Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
|
||||
@@ -300,7 +300,7 @@ public class ViewKeyFragment extends LoaderFragment implements
|
||||
* because the notification triggers faster than the activity closes.
|
||||
*/
|
||||
// Avoid NullPointerExceptions...
|
||||
if (data.getCount() == 0) {
|
||||
if (data == null || data.getCount() == 0) {
|
||||
return;
|
||||
}
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
|
||||
@@ -19,15 +19,16 @@ package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Calendar;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.text.format.DateFormat;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -60,7 +61,6 @@ public class KeyAdapter extends CursorAdapter {
|
||||
KeyRings.VERIFIED,
|
||||
KeyRings.HAS_ANY_SECRET,
|
||||
KeyRings.HAS_DUPLICATE_USER_ID,
|
||||
KeyRings.HAS_ENCRYPT,
|
||||
KeyRings.FINGERPRINT,
|
||||
KeyRings.CREATION,
|
||||
};
|
||||
@@ -72,9 +72,8 @@ public class KeyAdapter extends CursorAdapter {
|
||||
public static final int INDEX_VERIFIED = 5;
|
||||
public static final int INDEX_HAS_ANY_SECRET = 6;
|
||||
public static final int INDEX_HAS_DUPLICATE_USER_ID = 7;
|
||||
public static final int INDEX_HAS_ENCRYPT = 8;
|
||||
public static final int INDEX_FINGERPRINT = 9;
|
||||
public static final int INDEX_CREATION = 10;
|
||||
public static final int INDEX_FINGERPRINT = 8;
|
||||
public static final int INDEX_CREATION = 9;
|
||||
|
||||
public KeyAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
@@ -87,6 +86,7 @@ public class KeyAdapter extends CursorAdapter {
|
||||
}
|
||||
|
||||
public static class KeyItemViewHolder {
|
||||
public View mView;
|
||||
public Long mMasterKeyId;
|
||||
public TextView mMainUserId;
|
||||
public TextView mMainUserIdRest;
|
||||
@@ -96,6 +96,7 @@ public class KeyAdapter extends CursorAdapter {
|
||||
public ImageButton mSlingerButton;
|
||||
|
||||
public KeyItemViewHolder(View view) {
|
||||
mView = view;
|
||||
mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name);
|
||||
mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email);
|
||||
mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon);
|
||||
@@ -104,11 +105,10 @@ public class KeyAdapter extends CursorAdapter {
|
||||
mCreationDate = (TextView) view.findViewById(R.id.key_list_item_creation);
|
||||
}
|
||||
|
||||
public void setData(Context context, Cursor cursor, Highlighter highlighter) {
|
||||
public void setData(Context context, KeyItem item, Highlighter highlighter) {
|
||||
|
||||
{ // set name and stuff, common to both key types
|
||||
String userId = cursor.getString(INDEX_USER_ID);
|
||||
KeyRing.UserId userIdSplit = KeyRing.splitUserId(userId);
|
||||
KeyRing.UserId userIdSplit = item.mUserId;
|
||||
if (userIdSplit.name != null) {
|
||||
mMainUserId.setText(highlighter.highlight(userIdSplit.name));
|
||||
} else {
|
||||
@@ -124,30 +124,23 @@ public class KeyAdapter extends CursorAdapter {
|
||||
|
||||
{ // set edit button and status, specific by key type
|
||||
|
||||
long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
|
||||
boolean isSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
|
||||
boolean isExpired = cursor.getInt(INDEX_IS_EXPIRED) != 0;
|
||||
boolean isVerified = cursor.getInt(INDEX_VERIFIED) > 0;
|
||||
boolean hasDuplicate = cursor.getInt(INDEX_HAS_DUPLICATE_USER_ID) != 0;
|
||||
|
||||
mMasterKeyId = masterKeyId;
|
||||
mMasterKeyId = item.mKeyId;
|
||||
|
||||
// Note: order is important!
|
||||
if (isRevoked) {
|
||||
if (item.mIsRevoked) {
|
||||
KeyFormattingUtils
|
||||
.setStatusImage(context, mStatus, null, State.REVOKED, R.color.bg_gray);
|
||||
mStatus.setVisibility(View.VISIBLE);
|
||||
mSlinger.setVisibility(View.GONE);
|
||||
mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
||||
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
||||
} else if (isExpired) {
|
||||
} else if (item.mIsExpired) {
|
||||
KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.bg_gray);
|
||||
mStatus.setVisibility(View.VISIBLE);
|
||||
mSlinger.setVisibility(View.GONE);
|
||||
mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
||||
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
|
||||
} else if (isSecret) {
|
||||
} else if (item.mIsSecret) {
|
||||
mStatus.setVisibility(View.GONE);
|
||||
if (mSlingerButton.hasOnClickListeners()) {
|
||||
mSlinger.setVisibility(View.VISIBLE);
|
||||
@@ -158,7 +151,7 @@ public class KeyAdapter extends CursorAdapter {
|
||||
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
|
||||
} else {
|
||||
// this is a public key - show if it's verified
|
||||
if (isVerified) {
|
||||
if (item.mIsVerified) {
|
||||
KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED);
|
||||
mStatus.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
@@ -170,9 +163,9 @@ public class KeyAdapter extends CursorAdapter {
|
||||
mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black));
|
||||
}
|
||||
|
||||
if (hasDuplicate) {
|
||||
if (item.mHasDuplicate) {
|
||||
String dateTime = DateUtils.formatDateTime(context,
|
||||
cursor.getLong(INDEX_CREATION) * 1000,
|
||||
item.mCreation.getTime(),
|
||||
DateUtils.FORMAT_SHOW_DATE
|
||||
| DateUtils.FORMAT_SHOW_YEAR
|
||||
| DateUtils.FORMAT_ABBREV_MONTH);
|
||||
@@ -190,6 +183,10 @@ public class KeyAdapter extends CursorAdapter {
|
||||
|
||||
}
|
||||
|
||||
public boolean isEnabled(Cursor cursor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
View view = mInflater.inflate(R.layout.key_list_item, parent, false);
|
||||
@@ -204,7 +201,8 @@ public class KeyAdapter extends CursorAdapter {
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
Highlighter highlighter = new Highlighter(context, mQuery);
|
||||
KeyItemViewHolder h = (KeyItemViewHolder) view.getTag();
|
||||
h.setData(context, cursor, highlighter);
|
||||
KeyItem item = new KeyItem(cursor);
|
||||
h.setData(context, item, highlighter);
|
||||
}
|
||||
|
||||
public boolean isSecretAvailable(int id) {
|
||||
@@ -234,8 +232,9 @@ public class KeyAdapter extends CursorAdapter {
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
Cursor cursor = getCursor();
|
||||
// prevent a crash on rapid cursor changes
|
||||
if (getCursor().isClosed()) {
|
||||
if (cursor != null && getCursor().isClosed()) {
|
||||
return 0L;
|
||||
}
|
||||
return super.getItemId(position);
|
||||
@@ -250,6 +249,7 @@ public class KeyAdapter extends CursorAdapter {
|
||||
public final boolean mHasDuplicate;
|
||||
public final Date mCreation;
|
||||
public final String mFingerprint;
|
||||
public final boolean mIsSecret, mIsRevoked, mIsExpired, mIsVerified;
|
||||
|
||||
private KeyItem(Cursor cursor) {
|
||||
String userId = cursor.getString(INDEX_USER_ID);
|
||||
@@ -260,6 +260,10 @@ public class KeyAdapter extends CursorAdapter {
|
||||
mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000);
|
||||
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
|
||||
cursor.getBlob(INDEX_FINGERPRINT));
|
||||
mIsSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
|
||||
mIsRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
|
||||
mIsExpired = cursor.getInt(INDEX_IS_EXPIRED) > 0;
|
||||
mIsVerified = cursor.getInt(INDEX_VERIFIED) > 0;
|
||||
}
|
||||
|
||||
public KeyItem(CanonicalizedPublicKeyRing ring) {
|
||||
@@ -272,6 +276,12 @@ public class KeyAdapter extends CursorAdapter {
|
||||
mCreation = key.getCreationTime();
|
||||
mFingerprint = KeyFormattingUtils.convertFingerprintToHex(
|
||||
ring.getFingerprint());
|
||||
mIsRevoked = key.isRevoked();
|
||||
mIsExpired = key.isExpired();
|
||||
|
||||
// these two are actually "don't know"s
|
||||
mIsSecret = false;
|
||||
mIsVerified = false;
|
||||
}
|
||||
|
||||
public String getReadableName() {
|
||||
@@ -284,4 +294,11 @@ public class KeyAdapter extends CursorAdapter {
|
||||
|
||||
}
|
||||
|
||||
public static String[] getProjectionWith(String[] projection) {
|
||||
List<String> list = new ArrayList<>();
|
||||
list.addAll(Arrays.asList(PROJECTION));
|
||||
list.addAll(Arrays.asList(projection));
|
||||
return list.toArray(new String[list.size()]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -95,6 +95,8 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
||||
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
|
||||
try {
|
||||
handleNdefDiscoveredIntent(intent);
|
||||
} catch (CardException e) {
|
||||
handleNfcError(e);
|
||||
} catch (IOException e) {
|
||||
handleNfcError(e);
|
||||
}
|
||||
@@ -105,6 +107,81 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
||||
|
||||
Log.e(Constants.TAG, "nfc error", e);
|
||||
Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show();
|
||||
}
|
||||
|
||||
public void handleNfcError(CardException e) {
|
||||
Log.e(Constants.TAG, "card error", e);
|
||||
|
||||
short status = e.getResponseCode();
|
||||
// When entering a PIN, a status of 63CX indicates X attempts remaining.
|
||||
if ((status & (short)0xFFF0) == 0x63C0) {
|
||||
Notify.create(this, getString(R.string.error_pin, status & 0x000F), Style.WARN).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, all status codes are fixed values.
|
||||
switch (status) {
|
||||
// These errors should not occur in everyday use; if they are returned, it means we
|
||||
// made a mistake sending data to the card, or the card is misbehaving.
|
||||
case 0x6A80: {
|
||||
Notify.create(this, getString(R.string.error_nfc_bad_data), Style.WARN).show();
|
||||
break;
|
||||
}
|
||||
case 0x6883: {
|
||||
Notify.create(this, getString(R.string.error_nfc_chaining_error), Style.WARN).show();
|
||||
break;
|
||||
}
|
||||
case 0x6B00: {
|
||||
Notify.create(this, getString(R.string.error_nfc_header, "P1/P2"), Style.WARN).show();
|
||||
break;
|
||||
}
|
||||
case 0x6D00: {
|
||||
Notify.create(this, getString(R.string.error_nfc_header, "INS"), Style.WARN).show();
|
||||
break;
|
||||
}
|
||||
case 0x6E00: {
|
||||
Notify.create(this, getString(R.string.error_nfc_header, "CLA"), Style.WARN).show();
|
||||
break;
|
||||
}
|
||||
// These error conditions are more likely to be experienced by an end user.
|
||||
case 0x6285: {
|
||||
Notify.create(this, getString(R.string.error_nfc_terminated), Style.WARN).show();
|
||||
break;
|
||||
}
|
||||
case 0x6700: {
|
||||
Notify.create(this, getString(R.string.error_nfc_wrong_length), Style.WARN).show();
|
||||
break;
|
||||
}
|
||||
case 0x6982: {
|
||||
Notify.create(this, getString(R.string.error_nfc_security_not_satisfied),
|
||||
Style.WARN).show();
|
||||
break;
|
||||
}
|
||||
case 0x6983: {
|
||||
Notify.create(this, getString(R.string.error_nfc_authentication_blocked),
|
||||
Style.WARN).show();
|
||||
break;
|
||||
}
|
||||
case 0x6985: {
|
||||
Notify.create(this, getString(R.string.error_nfc_conditions_not_satisfied),
|
||||
Style.WARN).show();
|
||||
break;
|
||||
}
|
||||
// 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases.
|
||||
case 0x6A88:
|
||||
case 0x6A83: {
|
||||
Notify.create(this, getString(R.string.error_nfc_data_not_found), Style.WARN).show();
|
||||
break;
|
||||
}
|
||||
// 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an
|
||||
// unhandled exception on the smart card.
|
||||
case 0x6F00: {
|
||||
Notify.create(this, getString(R.string.error_nfc_unknown), Style.WARN).show();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -223,8 +300,9 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
||||
+ "06" // Lc (number of bytes)
|
||||
+ "D27600012401" // Data (6 bytes)
|
||||
+ "00"; // Le
|
||||
if ( ! nfcCommunicate(opening).endsWith(accepted)) { // activate connection
|
||||
throw new IOException("Initialization failed!");
|
||||
String response = nfcCommunicate(opening); // activate connection
|
||||
if ( ! response.endsWith(accepted) ) {
|
||||
throw new CardException("Initialization failed!", parseCardStatus(response));
|
||||
}
|
||||
|
||||
byte[] pwStatusBytes = nfcGetPwStatusBytes();
|
||||
@@ -439,7 +517,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
if ( ! "9000".equals(status)) {
|
||||
throw new IOException("Bad NFC response code: " + status);
|
||||
throw new CardException("Bad NFC response code: " + status, parseCardStatus(response));
|
||||
}
|
||||
|
||||
// Make sure the signature we received is actually the expected number of bytes long!
|
||||
@@ -511,9 +589,10 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
||||
+ String.format("%02x", mode) // P2
|
||||
+ String.format("%02x", pin.length) // Lc
|
||||
+ Hex.toHexString(pin);
|
||||
if (!nfcCommunicate(login).equals(accepted)) { // login
|
||||
String response = nfcCommunicate(login); // login
|
||||
if (!response.equals(accepted)) {
|
||||
handlePinError();
|
||||
throw new IOException("Bad PIN!");
|
||||
throw new CardException("Bad PIN!", parseCardStatus(response));
|
||||
}
|
||||
|
||||
if (mode == 0x81) {
|
||||
@@ -567,9 +646,10 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
||||
+ String.format("%02x", pin.length + newPin.length) // Lc
|
||||
+ getHex(pin)
|
||||
+ getHex(newPin);
|
||||
if (!nfcCommunicate(changeReferenceDataApdu).equals("9000")) { // Change reference data
|
||||
String response = nfcCommunicate(changeReferenceDataApdu); // change PIN
|
||||
if (!response.equals("9000")) {
|
||||
handlePinError();
|
||||
throw new IOException("Failed to change PIN");
|
||||
throw new CardException("Failed to change PIN", parseCardStatus(response));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -600,12 +680,9 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
||||
+ String.format("%02x", data.length) // Lc
|
||||
+ getHex(data);
|
||||
|
||||
String response = nfcCommunicate(putDataApdu);
|
||||
String response = nfcCommunicate(putDataApdu); // put data
|
||||
if (!response.equals("9000")) {
|
||||
throw new IOException("Failed to put data for tag "
|
||||
+ String.format("%02x", (dataObject & 0xFF00) >> 8)
|
||||
+ String.format("%02x", dataObject & 0xFF)
|
||||
+ ": " + response);
|
||||
throw new CardException("Failed to put data.", parseCardStatus(response));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -713,7 +790,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
if (!response.endsWith("9000")) {
|
||||
throw new IOException("Key export to card failed");
|
||||
throw new CardException("Key export to card failed", parseCardStatus(response));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -721,6 +798,24 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
||||
Arrays.fill(dataToSend, (byte) 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses out the status word from a JavaCard response string.
|
||||
*
|
||||
* @param response A hex string with the response from the card
|
||||
* @return A short indicating the SW1/SW2, or 0 if a status could not be determined.
|
||||
*/
|
||||
short parseCardStatus(String response) {
|
||||
if (response.length() < 4) {
|
||||
return 0; // invalid input
|
||||
}
|
||||
|
||||
try {
|
||||
return Short.parseShort(response.substring(response.length() - 4), 16);
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a message to the screen
|
||||
*
|
||||
@@ -790,4 +885,18 @@ public abstract class BaseNfcActivity extends BaseActivity {
|
||||
return new String(Hex.encode(raw));
|
||||
}
|
||||
|
||||
public class CardException extends IOException {
|
||||
private short mResponseCode;
|
||||
|
||||
public CardException(String detailMessage, short responseCode) {
|
||||
super(detailMessage);
|
||||
mResponseCode = responseCode;
|
||||
}
|
||||
|
||||
public short getResponseCode() {
|
||||
return mResponseCode;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
package org.sufficientlysecure.keychain.ui.base;
|
||||
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.service.KeychainNewService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainService;
|
||||
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
|
||||
|
||||
public abstract class CachingCryptoOperationFragment <T extends Parcelable, S extends OperationResult>
|
||||
@@ -47,59 +38,6 @@ public abstract class CachingCryptoOperationFragment <T extends Parcelable, S ex
|
||||
|
||||
protected abstract T createOperationInput();
|
||||
|
||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||
|
||||
if (mCachedActionsParcel == null) {
|
||||
|
||||
mCachedActionsParcel = createOperationInput();
|
||||
// this is null if invalid, just return in that case
|
||||
if (mCachedActionsParcel == null) {
|
||||
// Notify was created by createCryptoInput.
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainNewService.class);
|
||||
|
||||
intent.putExtra(KeychainNewService.EXTRA_OPERATION_INPUT, mCachedActionsParcel);
|
||||
intent.putExtra(KeychainNewService.EXTRA_CRYPTO_INPUT, cryptoInput);
|
||||
|
||||
ServiceProgressHandler saveHandler = new ServiceProgressHandler(getActivity()) {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == MessageStatus.OKAY.ordinal()) {
|
||||
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
if (returnData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final OperationResult result =
|
||||
returnData.getParcelable(OperationResult.EXTRA_RESULT);
|
||||
|
||||
onHandleResult(result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
saveHandler.showProgressDialog(
|
||||
getString(R.string.progress_building_key),
|
||||
ProgressDialog.STYLE_HORIZONTAL, false);
|
||||
|
||||
getActivity().startService(intent);
|
||||
|
||||
}
|
||||
|
||||
protected T getCachedActionsParcel() {
|
||||
return mCachedActionsParcel;
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ public abstract class CryptoOperationFragment <T extends Parcelable, S extends O
|
||||
abstract protected void onCryptoOperationSuccess(S result);
|
||||
|
||||
protected void onCryptoOperationError(S result) {
|
||||
result.createNotify(getActivity()).show(this);
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
|
||||
protected void onCryptoOperationCancelled() {
|
||||
|
||||
@@ -38,25 +38,16 @@ import org.sufficientlysecure.keychain.util.FabContainer;
|
||||
public class Notify {
|
||||
|
||||
public static enum Style {
|
||||
OK, WARN, ERROR;
|
||||
OK (R.color.android_green_light), WARN(R.color.android_orange_light), ERROR(R.color.android_red_light);
|
||||
|
||||
public final int mLineColor;
|
||||
|
||||
Style(int color) {
|
||||
mLineColor = color;
|
||||
}
|
||||
|
||||
public void applyToBar(Snackbar bar) {
|
||||
|
||||
switch (this) {
|
||||
case OK:
|
||||
// bar.actionColorResource(R.color.android_green_light);
|
||||
bar.lineColorResource(R.color.android_green_light);
|
||||
break;
|
||||
case WARN:
|
||||
// bar.textColorResource(R.color.android_orange_light);
|
||||
bar.lineColorResource(R.color.android_orange_light);
|
||||
break;
|
||||
case ERROR:
|
||||
// bar.textColorResource(R.color.android_red_light);
|
||||
bar.lineColorResource(R.color.android_red_light);
|
||||
break;
|
||||
}
|
||||
|
||||
bar.lineColorResource(mLineColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
@@ -24,14 +28,11 @@ import android.os.Bundle;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||
|
||||
public class CertifyKeySpinner extends KeySpinner {
|
||||
private long mHiddenMasterKeyId = Constants.key.none;
|
||||
@@ -59,19 +60,9 @@ public class CertifyKeySpinner extends KeySpinner {
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
String[] projection = new String[]{
|
||||
KeychainContract.KeyRings._ID,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.KeyRings.KEY_ID,
|
||||
KeychainContract.KeyRings.USER_ID,
|
||||
KeychainContract.KeyRings.IS_REVOKED,
|
||||
KeychainContract.KeyRings.IS_EXPIRED,
|
||||
String[] projection = KeyAdapter.getProjectionWith(new String[] {
|
||||
KeychainContract.KeyRings.HAS_CERTIFY,
|
||||
KeychainContract.KeyRings.HAS_ANY_SECRET,
|
||||
KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID,
|
||||
KeychainContract.KeyRings.CREATION
|
||||
};
|
||||
});
|
||||
|
||||
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1 AND "
|
||||
+ KeychainDatabase.Tables.KEYS + "." + KeychainContract.KeyRings.MASTER_KEY_ID
|
||||
@@ -82,7 +73,7 @@ public class CertifyKeySpinner extends KeySpinner {
|
||||
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
|
||||
}
|
||||
|
||||
private int mIndexHasCertify, mIndexIsRevoked, mIndexIsExpired;
|
||||
private int mIndexHasCertify;
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
@@ -90,8 +81,6 @@ public class CertifyKeySpinner extends KeySpinner {
|
||||
|
||||
if (loader.getId() == LOADER_ID) {
|
||||
mIndexHasCertify = data.getColumnIndex(KeychainContract.KeyRings.HAS_CERTIFY);
|
||||
mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED);
|
||||
mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED);
|
||||
|
||||
// If:
|
||||
// - no key has been pre-selected (e.g. by SageSlinger)
|
||||
@@ -119,18 +108,15 @@ public class CertifyKeySpinner extends KeySpinner {
|
||||
|
||||
|
||||
@Override
|
||||
boolean setStatus(Context context, Cursor cursor, ImageView statusView) {
|
||||
if (cursor.getInt(mIndexIsRevoked) != 0) {
|
||||
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.REVOKED, R.color.bg_gray);
|
||||
boolean isItemEnabled(Cursor cursor) {
|
||||
if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (cursor.getInt(mIndexIsExpired) != 0) {
|
||||
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.EXPIRED, R.color.bg_gray);
|
||||
if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) {
|
||||
return false;
|
||||
}
|
||||
// don't invalidate the "None" entry, which is also null!
|
||||
if (cursor.getPosition() != 0 && cursor.isNull(mIndexHasCertify)) {
|
||||
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.UNAVAILABLE, R.color.bg_gray);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import com.tokenautocomplete.TokenCompleteTextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||
@@ -126,7 +127,13 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// These are the rows that we will retrieve.
|
||||
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
|
||||
String where = KeyRings.HAS_ENCRYPT + " NOT NULL AND " + KeyRings.IS_EXPIRED + " = 0 AND "
|
||||
|
||||
String[] projection = KeyAdapter.getProjectionWith(new String[] {
|
||||
KeychainContract.KeyRings.HAS_ENCRYPT,
|
||||
});
|
||||
|
||||
String where = KeyRings.HAS_ENCRYPT + " NOT NULL AND "
|
||||
+ KeyRings.IS_EXPIRED + " = 0 AND "
|
||||
+ Tables.KEYS + "." + KeyRings.IS_REVOKED + " = 0";
|
||||
|
||||
if (args != null && args.containsKey(ARG_QUERY)) {
|
||||
@@ -135,12 +142,12 @@ public class EncryptKeyCompletionView extends TokenCompleteTextView
|
||||
|
||||
where += " AND " + KeyRings.USER_ID + " LIKE ?";
|
||||
|
||||
return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where,
|
||||
return new CursorLoader(getContext(), baseUri, projection, where,
|
||||
new String[]{"%" + query + "%"}, null);
|
||||
}
|
||||
|
||||
mAdapter.setSearchQuery(null);
|
||||
return new CursorLoader(getContext(), baseUri, KeyAdapter.PROJECTION, where, null, null);
|
||||
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -17,33 +17,30 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.support.v7.widget.AppCompatSpinner;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.SpinnerAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter.KeyItem;
|
||||
|
||||
|
||||
/**
|
||||
* Use AppCompatSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon.
|
||||
@@ -119,7 +116,8 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
||||
if (getContext() instanceof FragmentActivity) {
|
||||
((FragmentActivity) getContext()).getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
|
||||
} else {
|
||||
Log.e(Constants.TAG, "KeySpinner must be attached to FragmentActivity, this is " + getContext().getClass());
|
||||
throw new AssertionError("KeySpinner must be attached to FragmentActivity, this is "
|
||||
+ getContext().getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +136,11 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
||||
}
|
||||
|
||||
public long getSelectedKeyId() {
|
||||
return getSelectedItemId();
|
||||
Object item = getSelectedItem();
|
||||
if (item instanceof KeyItem) {
|
||||
return ((KeyItem) item).mKeyId;
|
||||
}
|
||||
return Constants.key.none;
|
||||
}
|
||||
|
||||
public void setPreSelectedKeyId(long selectedKeyId) {
|
||||
@@ -146,161 +148,87 @@ public abstract class KeySpinner extends AppCompatSpinner implements
|
||||
}
|
||||
|
||||
protected class SelectKeyAdapter extends BaseAdapter implements SpinnerAdapter {
|
||||
private CursorAdapter inner;
|
||||
private int mIndexUserId;
|
||||
private int mIndexDuplicate;
|
||||
private KeyAdapter inner;
|
||||
private int mIndexMasterKeyId;
|
||||
private int mIndexCreationDate;
|
||||
|
||||
public SelectKeyAdapter() {
|
||||
inner = new CursorAdapter(getContext(), null, 0) {
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return View.inflate(getContext(), R.layout.keyspinner_item, null);
|
||||
}
|
||||
inner = new KeyAdapter(getContext(), null, 0) {
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name);
|
||||
ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status);
|
||||
TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email);
|
||||
TextView vDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate);
|
||||
|
||||
KeyRing.UserId userId = KeyRing.splitUserId(cursor.getString(mIndexUserId));
|
||||
vKeyName.setText(userId.name);
|
||||
vKeyEmail.setText(userId.email);
|
||||
|
||||
boolean duplicate = cursor.getLong(mIndexDuplicate) > 0;
|
||||
if (duplicate) {
|
||||
String dateTime = DateUtils.formatDateTime(context,
|
||||
cursor.getLong(mIndexCreationDate) * 1000,
|
||||
DateUtils.FORMAT_SHOW_DATE
|
||||
| DateUtils.FORMAT_SHOW_YEAR
|
||||
| DateUtils.FORMAT_ABBREV_MONTH);
|
||||
|
||||
vDuplicate.setText(context.getString(R.string.label_key_created, dateTime));
|
||||
vDuplicate.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
vDuplicate.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
boolean valid = setStatus(getContext(), cursor, vKeyStatus);
|
||||
setItemEnabled(view, valid);
|
||||
public boolean isEnabled(Cursor cursor) {
|
||||
return KeySpinner.this.isItemEnabled(cursor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
try {
|
||||
return ((Cursor) getItem(position)).getLong(mIndexMasterKeyId);
|
||||
} catch (Exception e) {
|
||||
// This can happen on concurrent modification :(
|
||||
return Constants.key.none;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void setItemEnabled(View view, boolean enabled) {
|
||||
TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name);
|
||||
ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status);
|
||||
TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email);
|
||||
TextView vKeyDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate);
|
||||
|
||||
if (enabled) {
|
||||
vKeyName.setTextColor(Color.BLACK);
|
||||
vKeyEmail.setTextColor(Color.BLACK);
|
||||
vKeyDuplicate.setTextColor(Color.BLACK);
|
||||
vKeyStatus.setVisibility(View.GONE);
|
||||
view.setClickable(false);
|
||||
} else {
|
||||
vKeyName.setTextColor(Color.GRAY);
|
||||
vKeyEmail.setTextColor(Color.GRAY);
|
||||
vKeyDuplicate.setTextColor(Color.GRAY);
|
||||
vKeyStatus.setVisibility(View.VISIBLE);
|
||||
// this is a HACK. the trick is, if the element itself is clickable, the
|
||||
// click is not passed on to the view list
|
||||
view.setClickable(true);
|
||||
}
|
||||
}
|
||||
|
||||
public Cursor swapCursor(Cursor newCursor) {
|
||||
if (newCursor == null) return inner.swapCursor(null);
|
||||
|
||||
mIndexDuplicate = newCursor.getColumnIndex(KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID);
|
||||
mIndexUserId = newCursor.getColumnIndex(KeychainContract.KeyRings.USER_ID);
|
||||
mIndexMasterKeyId = newCursor.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID);
|
||||
mIndexCreationDate = newCursor.getColumnIndex(KeychainContract.KeyRings.CREATION);
|
||||
|
||||
Cursor oldCursor = inner.swapCursor(newCursor);
|
||||
|
||||
// pre-select key if mPreSelectedKeyId is given
|
||||
if (mPreSelectedKeyId != Constants.key.none && newCursor.moveToFirst()) {
|
||||
do {
|
||||
if (newCursor.getLong(mIndexMasterKeyId) == mPreSelectedKeyId) {
|
||||
setSelection(newCursor.getPosition() + 1);
|
||||
setSelection(newCursor.getPosition() +1);
|
||||
}
|
||||
} while (newCursor.moveToNext());
|
||||
}
|
||||
return inner.swapCursor(newCursor);
|
||||
return oldCursor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return inner.getCount() + 1;
|
||||
return inner.getCount() +1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
if (position == 0) return null;
|
||||
return inner.getItem(position - 1);
|
||||
if (position == 0) {
|
||||
return null;
|
||||
}
|
||||
return inner.getItem(position -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (position == 0) return Constants.key.none;
|
||||
return inner.getItemId(position - 1);
|
||||
if (position == 0) {
|
||||
return Constants.key.none;
|
||||
}
|
||||
return inner.getItemId(position -1);
|
||||
}
|
||||
|
||||
@SuppressLint("ViewHolder") // inflate call is for the preview only
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
try {
|
||||
View v = getDropDownView(position, convertView, parent);
|
||||
v.findViewById(R.id.keyspinner_key_email).setVisibility(View.GONE);
|
||||
return v;
|
||||
} catch (NullPointerException e) {
|
||||
// This is for the preview...
|
||||
return View.inflate(getContext(), android.R.layout.simple_list_item_1, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, View convertView, ViewGroup parent) {
|
||||
View view;
|
||||
if (position == 0) {
|
||||
if (convertView == null) {
|
||||
view = inner.newView(null, null, parent);
|
||||
} else {
|
||||
view = convertView;
|
||||
// Unfortunately, SpinnerAdapter does not support multiple view
|
||||
// types. For this reason, we throw away convertViews of a bad
|
||||
// type. This is sort of a hack, but since the number of elements
|
||||
// we deal with in KeySpinners is usually very small (number of
|
||||
// secret keys), this is the easiest solution. (I'm sorry.)
|
||||
if (convertView != null) {
|
||||
// This assumes that the inner view has non-null tags on its views!
|
||||
boolean isWrongType = (convertView.getTag() == null) != (position == 0);
|
||||
if (isWrongType) {
|
||||
convertView = null;
|
||||
}
|
||||
TextView vKeyName = (TextView) view.findViewById(R.id.keyspinner_key_name);
|
||||
ImageView vKeyStatus = (ImageView) view.findViewById(R.id.keyspinner_key_status);
|
||||
TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email);
|
||||
TextView vKeyDuplicate = (TextView) view.findViewById(R.id.keyspinner_duplicate);
|
||||
|
||||
vKeyName.setText(R.string.choice_none);
|
||||
vKeyEmail.setVisibility(View.GONE);
|
||||
vKeyDuplicate.setVisibility(View.GONE);
|
||||
vKeyStatus.setVisibility(View.GONE);
|
||||
setItemEnabled(view, true);
|
||||
} else {
|
||||
view = inner.getView(position - 1, convertView, parent);
|
||||
TextView vKeyEmail = (TextView) view.findViewById(R.id.keyspinner_key_email);
|
||||
vKeyEmail.setVisibility(View.VISIBLE);
|
||||
}
|
||||
return view;
|
||||
|
||||
if (position > 0) {
|
||||
return inner.getView(position -1, convertView, parent);
|
||||
}
|
||||
|
||||
return convertView != null ? convertView :
|
||||
LayoutInflater.from(getContext()).inflate(
|
||||
R.layout.keyspinner_item_none, parent, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boolean setStatus(Context context, Cursor cursor, ImageView statusView) {
|
||||
boolean isItemEnabled(Cursor cursor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
@@ -24,12 +25,9 @@ import android.os.Bundle;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||
|
||||
public class SignKeySpinner extends KeySpinner {
|
||||
public SignKeySpinner(Context context) {
|
||||
@@ -50,19 +48,9 @@ public class SignKeySpinner extends KeySpinner {
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingsUri();
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
String[] projection = new String[]{
|
||||
KeychainContract.KeyRings._ID,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.KeyRings.KEY_ID,
|
||||
KeychainContract.KeyRings.USER_ID,
|
||||
KeychainContract.KeyRings.IS_REVOKED,
|
||||
KeychainContract.KeyRings.IS_EXPIRED,
|
||||
String[] projection = KeyAdapter.getProjectionWith(new String[] {
|
||||
KeychainContract.KeyRings.HAS_SIGN,
|
||||
KeychainContract.KeyRings.HAS_ANY_SECRET,
|
||||
KeychainContract.KeyRings.HAS_DUPLICATE_USER_ID,
|
||||
KeychainContract.KeyRings.CREATION
|
||||
};
|
||||
});
|
||||
|
||||
String where = KeychainContract.KeyRings.HAS_ANY_SECRET + " = 1";
|
||||
|
||||
@@ -71,7 +59,7 @@ public class SignKeySpinner extends KeySpinner {
|
||||
return new CursorLoader(getContext(), baseUri, projection, where, null, null);
|
||||
}
|
||||
|
||||
private int mIndexHasSign, mIndexIsRevoked, mIndexIsExpired;
|
||||
private int mIndexHasSign;
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
@@ -79,23 +67,18 @@ public class SignKeySpinner extends KeySpinner {
|
||||
|
||||
if (loader.getId() == LOADER_ID) {
|
||||
mIndexHasSign = data.getColumnIndex(KeychainContract.KeyRings.HAS_SIGN);
|
||||
mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED);
|
||||
mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean setStatus(Context context, Cursor cursor, ImageView statusView) {
|
||||
if (cursor.getInt(mIndexIsRevoked) != 0) {
|
||||
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.REVOKED, R.color.bg_gray);
|
||||
boolean isItemEnabled(Cursor cursor) {
|
||||
if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (cursor.getInt(mIndexIsExpired) != 0) {
|
||||
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.EXPIRED, R.color.bg_gray);
|
||||
if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (cursor.getInt(mIndexHasSign) == 0) {
|
||||
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, State.UNAVAILABLE, R.color.bg_gray);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user