diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java index 5505c39c4..0f5463810 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/model/SubKey.java @@ -58,5 +58,9 @@ public abstract class SubKey implements KeysModel { public boolean has_auth_key() { return has_auth_key_int() != 0; } + + public boolean has_encrypt_key() { + return has_encrypt_key_int() != 0; + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java index 52463dc0f..052c05a48 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java @@ -28,9 +28,11 @@ import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityOptions; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.ViewModel; +import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.Intent; -import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.net.Uri; @@ -49,9 +51,6 @@ import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.FloatingActionButton; import android.support.v4.app.ActivityCompat; import android.support.v4.app.FragmentManager; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; import android.support.v7.widget.CardView; import android.view.Menu; import android.view.MenuItem; @@ -71,14 +70,14 @@ import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.HkpKeyserverAddress; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +import org.sufficientlysecure.keychain.livedata.GenericLiveData; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.EditKeyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.provider.KeyRepository; import org.sufficientlysecure.keychain.provider.KeyRepository.NotFoundException; -import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection; import org.sufficientlysecure.keychain.service.ChangeUnlockParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; @@ -113,7 +112,6 @@ import timber.log.Timber; public class ViewKeyActivity extends BaseSecurityTokenActivity implements - LoaderManager.LoaderCallbacks, CryptoOperationHelper.Callback { @Retention(RetentionPolicy.SOURCE) @IntDef({REQUEST_QR_FINGERPRINT, REQUEST_BACKUP, REQUEST_CERTIFY, REQUEST_DELETE}) @@ -151,23 +149,13 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private byte[] qrCodeLoaded; - private static final int LOADER_ID_UNIFIED = 0; - - private boolean isSecret = false; - private boolean hasEncrypt = false; - private boolean isVerified = false; - private boolean isRevoked = false; - private boolean isSecure = true; - private boolean isExpired = false; + private UnifiedKeyInfo unifiedKeyInfo; private MenuItem refreshItem; private boolean isRefreshing; private Animation rotate, rotateSpin; private View refreshView; - private long masterKeyId; - private byte[] fingerprint; - public static Intent getViewKeyActivityIntent(@NonNull Context context, long masterKeyId) { Intent viewIntent = new Intent(context, ViewKeyActivity.class); viewIntent.putExtra(ViewKeyActivity.EXTRA_MASTER_KEY_ID, masterKeyId); @@ -250,6 +238,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements }); refreshView = getLayoutInflater().inflate(R.layout.indeterminate_progress, null); + long masterKeyId; Intent intent = getIntent(); Uri dataUri = intent.getData(); if (intent.hasExtra(EXTRA_MASTER_KEY_ID)) { @@ -271,7 +260,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements actionEncryptText.setOnClickListener(v -> encrypt(true)); floatingActionButton.setOnClickListener(v -> { - if (isSecret) { + if (unifiedKeyInfo.has_any_secret()) { startSafeSlinger(); } else { scanQrCode(); @@ -280,9 +269,8 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements qrCodeLayout.setOnClickListener(v -> showQrCodeDialog()); - // Prepare the loaders. Either re-connect with an existing ones, - // or start new ones. - getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); + ViewKeyViewModel viewModel = ViewModelProviders.of(this).get(ViewKeyViewModel.class); + viewModel.setMasterKeyId(getIntent().getLongExtra(EXTRA_MASTER_KEY_ID, 0L)); if (savedInstanceState == null && intent.hasExtra(EXTRA_DISPLAY_RESULT)) { OperationResult result = intent.getParcelableExtra(EXTRA_DISPLAY_RESULT); @@ -294,8 +282,13 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements return; } + FragmentManager manager = getSupportFragmentManager(); + + ViewKeyFragment frag = ViewKeyFragment.newInstance(); + manager.beginTransaction().replace(R.id.view_key_fragment, frag, "view_key_fragment").commit(); + if (Preferences.getPreferences(this).getExperimentalEnableKeybase()) { - FragmentManager manager = getSupportFragmentManager(); + manager = getSupportFragmentManager(); final ViewKeyKeybaseFragment keybaseFrag = ViewKeyKeybaseFragment.newInstance(masterKeyId); manager.beginTransaction() .replace(R.id.view_key_keybase_fragment, keybaseFrag) @@ -303,6 +296,30 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements } } + public static class ViewKeyViewModel extends ViewModel { + private Long masterKeyId; + private LiveData unifiedKeyInfoLiveData; + + void setMasterKeyId(long masterKeyId) { + if (this.masterKeyId != null) { + throw new IllegalStateException("cannot change masterKeyId once set!"); + } + this.masterKeyId = masterKeyId; + } + + LiveData getUnifiedKeyInfoLiveData(Context context) { + if (masterKeyId == null) { + throw new IllegalStateException("masterKeyId must be set to retrieve this!"); + } + if (unifiedKeyInfoLiveData == null) { + KeyRepository keyRepository = KeyRepository.create(context); + unifiedKeyInfoLiveData = new GenericLiveData<>(context, null, + () -> keyRepository.getUnifiedKeyInfo(masterKeyId)); + } + return unifiedKeyInfoLiveData; + } + } + @Override protected void initLayout() { setContentView(R.layout.view_key_activity); @@ -346,7 +363,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements } case R.id.menu_key_view_advanced: { Intent advancedIntent = new Intent(this, ViewKeyAdvActivity.class); - advancedIntent.putExtra(ViewKeyAdvActivity.EXTRA_MASTER_KEY_ID, masterKeyId); + advancedIntent.putExtra(ViewKeyAdvActivity.EXTRA_MASTER_KEY_ID, unifiedKeyInfo.master_key_id()); startActivity(advancedIntent); return true; } @@ -365,13 +382,15 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuItem backupKey = menu.findItem(R.id.menu_key_view_backup); - backupKey.setVisible(isSecret); - menu.findItem(R.id.menu_key_view_skt).setVisible(isSecret); + backupKey.setVisible(unifiedKeyInfo.has_any_secret()); + menu.findItem(R.id.menu_key_view_skt).setVisible(unifiedKeyInfo.has_any_secret()); MenuItem changePassword = menu.findItem(R.id.menu_key_change_password); - changePassword.setVisible(isSecret); + changePassword.setVisible(unifiedKeyInfo.has_any_secret()); MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint); - certifyFingerprint.setVisible(!isSecret && !isVerified && !isExpired && !isRevoked); + certifyFingerprint.setVisible( + !unifiedKeyInfo.has_any_secret() && !unifiedKeyInfo.is_verified() && !unifiedKeyInfo.is_expired() && + !unifiedKeyInfo.is_revoked()); return true; } @@ -387,6 +406,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements @Override public void onCryptoOperationSuccess(EditKeyResult result) { displayResult(result); + long masterKeyId = unifiedKeyInfo.master_key_id(); PassphraseCacheService.clearCachedPassphrase(getApplicationContext(), masterKeyId, masterKeyId); } @@ -417,7 +437,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements // use new passphrase! changeUnlockParcel = ChangeUnlockParcel.createChangeUnlockParcel( - masterKeyId, fingerprint, + unifiedKeyInfo.master_key_id(), unifiedKeyInfo.fingerprint(), data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE) ); @@ -447,14 +467,14 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private void certifyFingerprint() { Intent intent = new Intent(this, CertifyFingerprintActivity.class); - intent.setData(KeyRings.buildUnifiedKeyRingUri(masterKeyId)); + intent.setData(KeyRings.buildUnifiedKeyRingUri(unifiedKeyInfo.master_key_id())); startActivityForResult(intent, REQUEST_CERTIFY); } private void certifyImmediate() { Intent intent = new Intent(this, CertifyKeyActivity.class); - intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[] { masterKeyId }); + intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[] { unifiedKeyInfo.master_key_id() }); startActivityForResult(intent, REQUEST_CERTIFY); } @@ -471,7 +491,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements opts = options.toBundle(); } - qrCodeIntent.setData(KeyRings.buildUnifiedKeyRingUri(masterKeyId)); + qrCodeIntent.setData(KeyRings.buildUnifiedKeyRingUri(unifiedKeyInfo.master_key_id())); ActivityCompat.startActivity(this, qrCodeIntent, opts); } @@ -479,6 +499,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements if (keyHasPassphrase()) { Intent intent = new Intent(this, PassphraseDialogActivity.class); + long masterKeyId = unifiedKeyInfo.master_key_id(); RequiredInputParcel requiredInput = RequiredInputParcel.createRequiredDecryptPassphrase(masterKeyId, masterKeyId); requiredInput.mSkipCaching = true; @@ -491,6 +512,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private boolean keyHasPassphrase() { try { + long masterKeyId = unifiedKeyInfo.master_key_id(); SecretKeyType secretKeyType = keyRepository.getCachedPublicKeyRing(masterKeyId).getSecretKeyType(masterKeyId); switch (secretKeyType) { @@ -510,7 +532,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private void startBackupActivity() { Intent intent = new Intent(this, BackupActivity.class); - intent.putExtra(BackupActivity.EXTRA_MASTER_KEY_IDS, new long[]{ masterKeyId }); + intent.putExtra(BackupActivity.EXTRA_MASTER_KEY_IDS, new long[] { unifiedKeyInfo.master_key_id() }); intent.putExtra(BackupActivity.EXTRA_SECRET, true); startActivity(intent); } @@ -519,9 +541,9 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements Intent deleteIntent = new Intent(this, DeleteKeyDialogActivity.class); deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, - new long[]{ masterKeyId }); - deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, isSecret); - if (isSecret) { + new long[]{ unifiedKeyInfo.master_key_id() }); + deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, unifiedKeyInfo.has_any_secret()); + if (unifiedKeyInfo.has_any_secret()) { // for upload in case key is secret deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER, Preferences.getPreferences(this).getPreferredKeyserver()); @@ -559,7 +581,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements Notify.create(this, R.string.error_scan_fp, Notify.LENGTH_LONG, Style.ERROR).show(); return; } - if (Arrays.equals(this.fingerprint, fingerprint)) { + if (Arrays.equals(unifiedKeyInfo.fingerprint(), fingerprint)) { certifyImmediate(); } else { Notify.create(this, R.string.error_scan_match, Notify.LENGTH_LONG, Style.ERROR).show(); @@ -598,35 +620,13 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements finish(); } - public void showMainFragment() { - new Handler().post(() -> { - FragmentManager manager = getSupportFragmentManager(); - - // unless we must refresh - ViewKeyFragment frag = (ViewKeyFragment) manager.findFragmentByTag("view_key_fragment"); - // if everything is valid, just drop it - if (frag != null && frag.isValidForData(isSecret)) { - return; - } - - // if the main fragment doesn't exist, or is not of the correct type, (re)create it - frag = ViewKeyFragment.newInstance(masterKeyId, isSecret); - // get rid of possible backstack, this fragment is always at the bottom - manager.popBackStack("security_token", FragmentManager.POP_BACK_STACK_INCLUSIVE); - manager.beginTransaction() - .replace(R.id.view_key_fragment, frag, "view_key_fragment") - // if this gets lost, it doesn't really matter since the loader will reinstate it onResume - .commitAllowingStateLoss(); - }); - } - private void encrypt(boolean text) { // If there is no encryption key, don't bother. - if (!hasEncrypt) { + if (!unifiedKeyInfo.has_encrypt_key()) { Notify.create(this, R.string.error_no_encrypt_subkey, Notify.Style.ERROR).show(); return; } - long[] encryptionKeyIds = new long[] { masterKeyId }; + long[] encryptionKeyIds = new long[] { unifiedKeyInfo.master_key_id() }; Intent intent; if (text) { intent = new Intent(this, EncryptTextActivity.class); @@ -643,7 +643,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private void startSafeSlinger() { Intent safeSlingerIntent = new Intent(this, SafeSlingerActivity.class); - safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, masterKeyId); + safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, unifiedKeyInfo.master_key_id()); startActivityForResult(safeSlingerIntent, 0); } @@ -682,50 +682,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements loadTask.execute(); } - - // These are the rows that we will retrieve. - static final String[] PROJECTION = new String[]{ - KeychainContract.KeyRings._ID, - KeychainContract.KeyRings.MASTER_KEY_ID, - KeychainContract.KeyRings.USER_ID, - KeychainContract.KeyRings.IS_REVOKED, - KeychainContract.KeyRings.IS_EXPIRED, - KeychainContract.KeyRings.IS_SECURE, - KeychainContract.KeyRings.VERIFIED, - KeychainContract.KeyRings.HAS_ANY_SECRET, - KeychainContract.KeyRings.FINGERPRINT, - KeychainContract.KeyRings.HAS_ENCRYPT, - KeyRings.NAME, - KeyRings.EMAIL, - KeyRings.COMMENT - }; - - static final int INDEX_MASTER_KEY_ID = 1; - static final int INDEX_USER_ID = 2; - static final int INDEX_IS_REVOKED = 3; - static final int INDEX_IS_EXPIRED = 4; - static final int INDEX_IS_SECURE = 5; - static final int INDEX_VERIFIED = 6; - static final int INDEX_HAS_ANY_SECRET = 7; - static final int INDEX_FINGERPRINT = 8; - static final int INDEX_HAS_ENCRYPT = 9; - static final int INDEX_NAME = 10; - static final int INDEX_EMAIL = 11; - static final int INDEX_COMMENT = 12; - - @Override - public Loader onCreateLoader(int id, Bundle args) { - switch (id) { - case LOADER_ID_UNIFIED: { - Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(masterKeyId); - return new CursorLoader(this, baseUri, PROJECTION, null, null, null); - } - - default: - return null; - } - } - int mPreviousColor = 0; /** @@ -747,127 +703,104 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements return (0xff << 24) | (r << 16) | (g << 8) | b; } - @Override - public void onLoadFinished(Loader loader, Cursor data) { - /* TODO better error handling? May cause problems when a key is deleted, - * because the notification triggers faster than the activity closes. - */ + private void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) { + if (unifiedKeyInfo == null) { + return; + } - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - switch (loader.getId()) { - case LOADER_ID_UNIFIED: { - // Avoid NullPointerExceptions... - if (data.getCount() == 0) { - return; - } + this.unifiedKeyInfo = unifiedKeyInfo; - if (data.moveToFirst()) { - // get name, email, and comment from USER_ID + String name = unifiedKeyInfo.name(); + collapsingToolbarLayout.setTitle(name != null ? name : getString(R.string.user_id_no_name)); - String name = data.getString(INDEX_NAME); + // if the refresh animation isn't playing + if (!rotate.hasStarted() && !rotateSpin.hasStarted()) { + // re-create options menu based on isSecret, isVerified + supportInvalidateOptionsMenu(); + // this is done at the end of the animation otherwise + } - collapsingToolbarLayout.setTitle(name != null ? name : getString(R.string.user_id_no_name)); - - masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); - fingerprint = data.getBlob(INDEX_FINGERPRINT); - isSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; - hasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0; - isRevoked = data.getInt(INDEX_IS_REVOKED) > 0; - isExpired = data.getInt(INDEX_IS_EXPIRED) != 0; - isSecure = data.getInt(INDEX_IS_SECURE) == 1; - isVerified = data.getInt(INDEX_VERIFIED) > 0; - - // queue showing of the main fragment - showMainFragment(); - - // if the refresh animation isn't playing - if (!rotate.hasStarted() && !rotateSpin.hasStarted()) { - // re-create options menu based on isSecret, isVerified - supportInvalidateOptionsMenu(); - // this is done at the end of the animation otherwise + AsyncTask photoTask = + new AsyncTask() { + protected Bitmap doInBackground(Long... mMasterKeyId) { + return new ContactHelper(ViewKeyActivity.this) + .loadPhotoByMasterKeyId(mMasterKeyId[0], true); } - AsyncTask photoTask = - new AsyncTask() { - protected Bitmap doInBackground(Long... mMasterKeyId) { - return new ContactHelper(ViewKeyActivity.this) - .loadPhotoByMasterKeyId(mMasterKeyId[0], true); - } - - protected void onPostExecute(Bitmap photo) { - if (photo == null) { - return; - } - - photoView.setImageBitmap(photo); - photoView.setColorFilter(getResources().getColor(R.color.toolbar_photo_tint), PorterDuff.Mode.SRC_ATOP); - photoLayout.setVisibility(View.VISIBLE); - } - }; - - boolean showStatusText = isSecure && !isExpired && !isRevoked; - if (showStatusText) { - statusText.setVisibility(View.VISIBLE); - - if (isSecret) { - statusText.setText(R.string.view_key_my_key); - } else if (isVerified) { - statusText.setText(R.string.view_key_verified); - } else { - statusText.setText(R.string.view_key_unverified); + protected void onPostExecute(Bitmap photo) { + if (photo == null) { + return; } - } else { - statusText.setVisibility(View.GONE); + + photoView.setImageBitmap(photo); + photoView.setColorFilter(getResources().getColor(R.color.toolbar_photo_tint), + PorterDuff.Mode.SRC_ATOP); + photoLayout.setVisibility(View.VISIBLE); } + }; - // Note: order is important - int color; - if (isRevoked) { - statusImage.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(this, statusImage, statusText, - State.REVOKED, R.color.icons, true); - // noinspection deprecation, fix requires api level 23 - color = getResources().getColor(R.color.key_flag_red); + boolean showStatusText = unifiedKeyInfo.is_secure() && !unifiedKeyInfo.is_expired() && !unifiedKeyInfo.is_revoked(); + if (showStatusText) { + statusText.setVisibility(View.VISIBLE); - actionEncryptFile.setVisibility(View.INVISIBLE); - actionEncryptText.setVisibility(View.INVISIBLE); - hideFab(); - qrCodeLayout.setVisibility(View.GONE); - } else if (!isSecure) { - statusImage.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(this, statusImage, statusText, - State.INSECURE, R.color.icons, true); - // noinspection deprecation, fix requires api level 23 - color = getResources().getColor(R.color.key_flag_red); + if (unifiedKeyInfo.has_any_secret()) { + statusText.setText(R.string.view_key_my_key); + } else if (unifiedKeyInfo.is_verified()) { + statusText.setText(R.string.view_key_verified); + } else { + statusText.setText(R.string.view_key_unverified); + } + } else { + statusText.setVisibility(View.GONE); + } - actionEncryptFile.setVisibility(View.INVISIBLE); - actionEncryptText.setVisibility(View.INVISIBLE); - hideFab(); - qrCodeLayout.setVisibility(View.GONE); - } else if (isExpired) { - statusImage.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(this, statusImage, statusText, - State.EXPIRED, R.color.icons, true); - // noinspection deprecation, fix requires api level 23 - color = getResources().getColor(R.color.key_flag_red); + // Note: order is important + int color; + if (unifiedKeyInfo.is_revoked()) { + statusImage.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(this, statusImage, statusText, + State.REVOKED, R.color.icons, true); + // noinspection deprecation, fix requires api level 23 + color = getResources().getColor(R.color.key_flag_red); - actionEncryptFile.setVisibility(View.INVISIBLE); - actionEncryptText.setVisibility(View.INVISIBLE); - hideFab(); - qrCodeLayout.setVisibility(View.GONE); - } else if (isSecret) { - statusImage.setVisibility(View.GONE); - // noinspection deprecation, fix requires api level 23 - color = getResources().getColor(R.color.key_flag_green); - // reload qr code only if the fingerprint changed - if (!Arrays.equals(fingerprint, qrCodeLoaded)) { - loadQrCode(fingerprint); - } - photoTask.execute(masterKeyId); - qrCodeLayout.setVisibility(View.VISIBLE); + actionEncryptFile.setVisibility(View.INVISIBLE); + actionEncryptText.setVisibility(View.INVISIBLE); + hideFab(); + qrCodeLayout.setVisibility(View.GONE); + } else if (!unifiedKeyInfo.is_secure()) { + statusImage.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(this, statusImage, statusText, + State.INSECURE, R.color.icons, true); + // noinspection deprecation, fix requires api level 23 + color = getResources().getColor(R.color.key_flag_red); - // and place leftOf qr code + actionEncryptFile.setVisibility(View.INVISIBLE); + actionEncryptText.setVisibility(View.INVISIBLE); + hideFab(); + qrCodeLayout.setVisibility(View.GONE); + } else if (unifiedKeyInfo.is_expired()) { + statusImage.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(this, statusImage, statusText, + State.EXPIRED, R.color.icons, true); + // noinspection deprecation, fix requires api level 23 + color = getResources().getColor(R.color.key_flag_red); + + actionEncryptFile.setVisibility(View.INVISIBLE); + actionEncryptText.setVisibility(View.INVISIBLE); + hideFab(); + qrCodeLayout.setVisibility(View.GONE); + } else if (unifiedKeyInfo.has_any_secret()) { + statusImage.setVisibility(View.GONE); + // noinspection deprecation, fix requires api level 23 + color = getResources().getColor(R.color.key_flag_green); + // reload qr code only if the fingerprint changed + if (!Arrays.equals(unifiedKeyInfo.fingerprint(), qrCodeLoaded)) { + loadQrCode(unifiedKeyInfo.fingerprint()); + } + photoTask.execute(unifiedKeyInfo.master_key_id()); + qrCodeLayout.setVisibility(View.VISIBLE); + + // and place leftOf qr code // RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams) // mName.getLayoutParams(); // // remove right margin @@ -878,72 +811,67 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements // nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout); // mName.setLayoutParams(nameParams); - RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams) - statusText.getLayoutParams(); - statusParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0); - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - statusParams.setMarginEnd(0); - } - statusParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout); - statusText.setLayoutParams(statusParams); + RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams) + statusText.getLayoutParams(); + statusParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0); + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + statusParams.setMarginEnd(0); + } + statusParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout); + statusText.setLayoutParams(statusParams); - actionEncryptFile.setVisibility(View.VISIBLE); - actionEncryptText.setVisibility(View.VISIBLE); + actionEncryptFile.setVisibility(View.VISIBLE); + actionEncryptText.setVisibility(View.VISIBLE); - showFab(); - // noinspection deprecation (no getDrawable with theme at current minApi level 15!) - floatingActionButton.setImageDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp)); - } else { - actionEncryptFile.setVisibility(View.VISIBLE); - actionEncryptText.setVisibility(View.VISIBLE); - qrCodeLayout.setVisibility(View.GONE); + showFab(); + // noinspection deprecation (no getDrawable with theme at current minApi level 15!) + floatingActionButton.setImageDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp)); + } else { + actionEncryptFile.setVisibility(View.VISIBLE); + actionEncryptText.setVisibility(View.VISIBLE); + qrCodeLayout.setVisibility(View.GONE); - if (isVerified) { - statusText.setText(R.string.view_key_verified); - statusImage.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(this, statusImage, statusText, - State.VERIFIED, R.color.icons, true); - // noinspection deprecation, fix requires api level 23 - color = getResources().getColor(R.color.key_flag_green); - photoTask.execute(masterKeyId); + if (unifiedKeyInfo.is_verified()) { + statusText.setText(R.string.view_key_verified); + statusImage.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(this, statusImage, statusText, + State.VERIFIED, R.color.icons, true); + // noinspection deprecation, fix requires api level 23 + color = getResources().getColor(R.color.key_flag_green); + photoTask.execute(unifiedKeyInfo.master_key_id()); - hideFab(); - } else { - statusText.setText(R.string.view_key_unverified); - statusImage.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(this, statusImage, statusText, - State.UNVERIFIED, R.color.icons, true); - // noinspection deprecation, fix requires api level 23 - color = getResources().getColor(R.color.key_flag_orange); + hideFab(); + } else { + statusText.setText(R.string.view_key_unverified); + statusImage.setVisibility(View.VISIBLE); + KeyFormattingUtils.setStatusImage(this, statusImage, statusText, + State.UNVERIFIED, R.color.icons, true); + // noinspection deprecation, fix requires api level 23 + color = getResources().getColor(R.color.key_flag_orange); - showFab(); - } - } - - if (mPreviousColor == 0 || mPreviousColor == color) { - appBarLayout.setBackgroundColor(color); - collapsingToolbarLayout.setContentScrimColor(color); - collapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color)); - mPreviousColor = color; - } else { - ObjectAnimator colorFade = - ObjectAnimator.ofObject(appBarLayout, "backgroundColor", - new ArgbEvaluator(), mPreviousColor, color); - collapsingToolbarLayout.setContentScrimColor(color); - collapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color)); - - colorFade.setDuration(1200); - colorFade.start(); - mPreviousColor = color; - } - - //noinspection deprecation - statusImage.setAlpha(80); - - break; - } + showFab(); } } + + if (mPreviousColor == 0 || mPreviousColor == color) { + appBarLayout.setBackgroundColor(color); + collapsingToolbarLayout.setContentScrimColor(color); + collapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color)); + mPreviousColor = color; + } else { + ObjectAnimator colorFade = + ObjectAnimator.ofObject(appBarLayout, "backgroundColor", + new ArgbEvaluator(), mPreviousColor, color); + collapsingToolbarLayout.setContentScrimColor(color); + collapsingToolbarLayout.setStatusBarScrimColor(getStatusBarBackgroundColor(color)); + + colorFade.setDuration(1200); + colorFade.start(); + mPreviousColor = color; + } + + //noinspection deprecation + statusImage.setAlpha(80); } /** @@ -968,16 +896,11 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements floatingActionButton.setVisibility(View.GONE); } - @Override - public void onLoaderReset(Loader loader) { - - } - // CryptoOperationHelper.Callback functions private void updateFromKeyserver() { - if (fingerprint == null) { + if (unifiedKeyInfo == null) { return; } @@ -993,7 +916,7 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements public ImportKeyringParcel createOperationInput() { HkpKeyserverAddress preferredKeyserver = Preferences.getPreferences(this).getPreferredKeyserver(); - ParcelableKeyRing keyEntry = ParcelableKeyRing.createFromReference(fingerprint, null, null, null); + ParcelableKeyRing keyEntry = ParcelableKeyRing.createFromReference(unifiedKeyInfo.fingerprint(), null, null, null); return ImportKeyringParcel.createImportKeyringParcel(Collections.singletonList(keyEntry), preferredKeyserver); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java index 07e59d289..f9a68c9be 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java @@ -26,11 +26,11 @@ import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; import android.os.Bundle; import android.os.Handler; +import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v7.widget.PopupMenu; -import android.support.v7.widget.PopupMenu.OnDismissListener; import android.support.v7.widget.PopupMenu.OnMenuItemClickListener; import android.view.LayoutInflater; import android.view.MenuItem; @@ -40,8 +40,9 @@ import android.view.ViewGroup; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; import org.sufficientlysecure.keychain.model.KeyMetadata; +import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo; import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.ui.base.LoaderFragment; +import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity.ViewKeyViewModel; import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo; import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.KeySubkeyStatus; import org.sufficientlysecure.keychain.ui.keyview.loader.SystemContactDao.SystemContactInfo; @@ -56,12 +57,7 @@ import org.sufficientlysecure.keychain.ui.keyview.view.KeyserverStatusView; import org.sufficientlysecure.keychain.ui.keyview.view.SystemContactCardView; -public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView, OnMenuItemClickListener { - public static final String ARG_MASTER_KEY_ID = "master_key_id"; - public static final String ARG_IS_SECRET = "is_secret"; - - boolean mIsSecret = false; - +public class ViewKeyFragment extends Fragment implements ViewKeyMvpView, OnMenuItemClickListener { private IdentitiesCardView identitiesCardView; private IdentitiesPresenter identitiesPresenter; @@ -76,32 +72,20 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView, O private Integer displayedContextMenuPosition; - /** - * Creates new instance of this fragment - */ - public static ViewKeyFragment newInstance(long masterKeyId, boolean isSecret) { - ViewKeyFragment frag = new ViewKeyFragment(); - Bundle args = new Bundle(); - args.putLong(ARG_MASTER_KEY_ID, masterKeyId); - args.putBoolean(ARG_IS_SECRET, isSecret); - - frag.setArguments(args); - - return frag; + public static ViewKeyFragment newInstance() { + return new ViewKeyFragment(); } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { - View root = super.onCreateView(inflater, superContainer, savedInstanceState); - View view = inflater.inflate(R.layout.view_key_fragment, getContainer()); + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.view_key_fragment, viewGroup, false); identitiesCardView = view.findViewById(R.id.card_identities); - systemContactCard = view.findViewById(R.id.linked_system_contact_card); keyStatusHealth = view.findViewById(R.id.key_status_health); keyStatusKeyserver = view.findViewById(R.id.key_status_keyserver); - return root; + return view; } public static class KeyFragmentViewModel extends ViewModel { @@ -143,39 +127,36 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView, O public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - long masterKeyId = getArguments().getLong(ARG_MASTER_KEY_ID); - mIsSecret = getArguments().getBoolean(ARG_IS_SECRET); + ViewKeyViewModel viewKeyViewModel = ViewModelProviders.of(requireActivity()).get(ViewKeyViewModel.class); + viewKeyViewModel.getUnifiedKeyInfoLiveData(requireContext()).observe(this, this::onLoadUnifiedKeyInfo); + } + private void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) { KeyFragmentViewModel model = ViewModelProviders.of(this).get(KeyFragmentViewModel.class); identitiesPresenter = new IdentitiesPresenter( - getContext(), identitiesCardView, this, masterKeyId, mIsSecret); + getContext(), identitiesCardView, this, unifiedKeyInfo.master_key_id(), unifiedKeyInfo.has_any_secret()); model.getIdentityInfo(identitiesPresenter).observe(this, identitiesPresenter); systemContactPresenter = new SystemContactPresenter( - getContext(), systemContactCard, masterKeyId, mIsSecret); + getContext(), systemContactCard, unifiedKeyInfo.master_key_id(), unifiedKeyInfo.has_any_secret()); model.getSystemContactInfo(systemContactPresenter).observe(this, systemContactPresenter); - keyHealthPresenter = new KeyHealthPresenter(getContext(), keyStatusHealth, masterKeyId); + keyHealthPresenter = new KeyHealthPresenter(getContext(), keyStatusHealth, unifiedKeyInfo.master_key_id()); model.getSubkeyStatus(keyHealthPresenter).observe(this, keyHealthPresenter); keyserverStatusPresenter = new KeyserverStatusPresenter( - getContext(), keyStatusKeyserver, masterKeyId, mIsSecret); + getContext(), keyStatusKeyserver, unifiedKeyInfo.master_key_id(), unifiedKeyInfo.has_any_secret()); model.getKeyserverStatus(keyserverStatusPresenter).observe(this, keyserverStatusPresenter); } @Override public void switchToFragment(final Fragment frag, final String backStackName) { - new Handler().post(new Runnable() { - @Override - public void run() { - getFragmentManager().beginTransaction() - .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) - .replace(R.id.view_key_fragment, frag) - .addToBackStack(backStackName) - .commit(); - } - }); + new Handler().post(() -> requireFragmentManager().beginTransaction() + .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) + .replace(R.id.view_key_fragment, frag) + .addToBackStack(backStackName) + .commit()); } @Override @@ -189,10 +170,6 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView, O } } - public boolean isValidForData(boolean isSecret) { - return isSecret == mIsSecret; - } - @Override public void startActivityAndShowResultSnackbar(Intent intent) { startActivityForResult(intent, 0); @@ -200,26 +177,18 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView, O @Override public void showDialogFragment(final DialogFragment dialogFragment, final String tag) { - DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { - public void run() { - dialogFragment.show(getActivity().getSupportFragmentManager(), tag); - } - }); + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed( + () -> dialogFragment.show(requireFragmentManager(), tag)); } @Override public void showContextMenu(int position, View anchor) { displayedContextMenuPosition = position; - PopupMenu menu = new PopupMenu(getContext(), anchor); + PopupMenu menu = new PopupMenu(requireContext(), anchor); menu.inflate(R.menu.identity_context_menu); menu.setOnMenuItemClickListener(this); - menu.setOnDismissListener(new OnDismissListener() { - @Override - public void onDismiss(PopupMenu popupMenu) { - displayedContextMenuPosition = null; - } - }); + menu.setOnDismissListener(popupMenu -> displayedContextMenuPosition = null); menu.show(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java index 9bb3815b4..0b043e91c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/IdentitiesPresenter.java @@ -88,7 +88,6 @@ public class IdentitiesPresenter implements Observer> { @Override public void onChanged(@Nullable List identityInfos) { - viewKeyMvpView.setContentShown(true, false); identitiesAdapter.setData(identityInfos); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyHealthPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyHealthPresenter.java index 3a9e11d63..6c83be8f7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyHealthPresenter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyHealthPresenter.java @@ -21,7 +21,6 @@ package org.sufficientlysecure.keychain.ui.keyview.presenter; import java.util.Comparator; import java.util.Date; -import android.app.Application; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.Observer; import android.content.Context; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/ViewKeyMvpView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/ViewKeyMvpView.java index b77e4bbc8..5898ae907 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/ViewKeyMvpView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/ViewKeyMvpView.java @@ -30,7 +30,6 @@ public interface ViewKeyMvpView { void startActivity(Intent intent); void startActivityAndShowResultSnackbar(Intent intent); void showDialogFragment(DialogFragment dialogFragment, final String tag); - void setContentShown(boolean show, boolean animate); void showContextMenu(int position, View anchor); } diff --git a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq index 6235a5b73..4653b3ded 100644 --- a/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq +++ b/OpenKeychain/src/main/sqldelight/org/sufficientlysecure/keychain/Keys.sq @@ -28,6 +28,7 @@ SELECT keys.master_key_id, keys.fingerprint, MIN(user_packets.rank), user_packet (EXISTS (SELECT * FROM user_packets AS dups WHERE dups.master_key_id != keys.master_key_id AND dups.rank = 0 AND dups.name = user_packets.name COLLATE NOCASE AND dups.email = user_packets.email COLLATE NOCASE )) AS has_duplicate_int, (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.has_secret != 0 )) AS has_any_secret_int, (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.can_authenticate != 0 )) AS has_auth_key_int, + (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.can_encrypt != 0 )) AS has_encrypt_key_int, GROUP_CONCAT(DISTINCT aTI.package_name) AS autocrypt_package_names_csv, GROUP_CONCAT(user_packets.user_id, '|||') AS user_id_list FROM keys @@ -43,6 +44,7 @@ SELECT keys.master_key_id, keys.fingerprint, MIN(user_packets.rank), user_packet (EXISTS (SELECT * FROM user_packets AS dups WHERE dups.master_key_id != keys.master_key_id AND dups.rank = 0 AND dups.name = user_packets.name COLLATE NOCASE AND dups.email = user_packets.email COLLATE NOCASE )) AS has_duplicate_int, (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.has_secret != 0 )) AS has_any_secret_int, (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.can_authenticate != 0 )) AS has_auth_key_int, + (EXISTS (SELECT * FROM keys AS k WHERE k.master_key_id = keys.master_key_id AND k.can_encrypt != 0 )) AS has_encrypt_key_int, GROUP_CONCAT(DISTINCT aTI.package_name) AS autocrypt_package_names_csv, GROUP_CONCAT(user_packets.user_id, '|||') AS user_id_list FROM keys