diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org')
20 files changed, 262 insertions, 312 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java index fa3600ffb..2f2838f70 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/compatibility/ClipboardReflection.java @@ -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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java index 469e386cb..da0aef018 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/EditKeyOperation.java @@ -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;                      } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 707cf0af1..8c68bda19 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -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 diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index fd3d41f93..e3059defb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -149,7 +149,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); @@ -757,6 +757,12 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>                  // TODO: slow annealing to fake a progress?              } +            metadata = new OpenPgpMetadata( +                    originalFilename, +                    mimeType, +                    literalData.getModificationTime().getTime(), +                    alreadyWritten); +              if (signature != null) {                  updateProgress(R.string.progress_verifying_signature, 90, 100);                  log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent); @@ -833,7 +839,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(); @@ -863,8 +869,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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 1da13023d..a018815f3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -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 diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java index 8253801d9..36ba47672 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java @@ -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; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index 72100677c..dbbfe3133 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -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 diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java index 1dcda5b8d..051da5d6b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java @@ -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 diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java index 1a23dda93..355c649e7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptModeAsymmetricFragment.java @@ -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 diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java index 83fede917..d93b52453 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java @@ -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; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java index f5a909676..a0f6d0e1b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java @@ -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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index 4b4e71f6e..bb12cd7ff 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java index b92163b59..a929d52f0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java @@ -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 diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java index deaaee87a..1fc24775b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java @@ -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()]); +    } +  } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java index 8c554dbde..7dfd56430 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java @@ -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 void applyToBar(Snackbar bar) { +        public final int mLineColor; -            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; -            } +        Style(int color) { +            mLineColor = color; +        } +        public void applyToBar(Snackbar bar) { +            bar.lineColorResource(mLineColor);          }      } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java index 845b35512..0fed46544 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java @@ -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;          } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java index 63a1aade9..48e6c2cee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EncryptKeyCompletionView.java @@ -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);      } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java index ad1a14a33..5050c01af 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java @@ -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;      } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java index df7347fa4..c59ad7a12 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java @@ -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;          } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java new file mode 100644 index 000000000..44f2b7c3e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015 Vincent Breitmoser <look@my.amazin.horse> + *  + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *  + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *  + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.sufficientlysecure.keychain.ui.widget; + + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ViewAnimator; + +import org.sufficientlysecure.keychain.R; + + +/** This view is essentially identical to ViewAnimator, but allows specifying the initial view + * for preview as an xml attribute. */ +public class ToolableViewAnimator extends ViewAnimator { + +    private int mInitChild = -1; + +    public ToolableViewAnimator(Context context, AttributeSet attrs, int defStyleAttr) { +        super(context, attrs); + +        if (isInEditMode()) { +            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ToolableViewAnimator, defStyleAttr, 0); +            mInitChild = a.getInt(R.styleable.ToolableViewAnimator_initialView, -1); +            a.recycle(); +        } +    } + +    @Override +    public void addView(View child, int index, ViewGroup.LayoutParams params) { +        if (isInEditMode() && mInitChild-- > 0) { +            return; +        } +        super.addView(child, index, params); +    } +}  | 
