diff options
author | Dominik Schürmann <dominik@dominikschuermann.de> | 2015-12-31 16:31:03 +0100 |
---|---|---|
committer | Dominik Schürmann <dominik@dominikschuermann.de> | 2015-12-31 16:31:03 +0100 |
commit | 3c937e858ed5858963d74e182352b9bb615e3f22 (patch) | |
tree | 8b25e3a943d8537cd9fae121dae0d8b3e00e09c4 /OpenKeychain/src | |
parent | 807e5f7901f98ad14f78f6b038c286e6ff0f6fe1 (diff) | |
parent | 6fb96ce7903623f2e3b1c38c765e8a501dbcc0a1 (diff) | |
download | open-keychain-3c937e858ed5858963d74e182352b9bb615e3f22.tar.gz open-keychain-3c937e858ed5858963d74e182352b9bb615e3f22.tar.bz2 open-keychain-3c937e858ed5858963d74e182352b9bb615e3f22.zip |
Merge pull request #1584 from open-keychain/edit-redesign
Inline Identity Edit
Diffstat (limited to 'OpenKeychain/src')
50 files changed, 2096 insertions, 359 deletions
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index ee7d7e331..b9c8f8a2b 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -129,12 +129,16 @@ android:name=".ui.EditKeyActivity" android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:label="@string/title_edit_key" /> - <!-- NOTE: Dont use configChanges for QR Code view! We use a different layout for landscape --> + <activity + android:name=".ui.EditIdentitiesActivity" + android:configChanges="orientation|screenSize|keyboardHidden|keyboard" + android:label="@string/title_edit_identities" /> <activity android:name=".ui.linked.LinkedIdWizard" android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:label="@string/title_linked_create" - android:parentActivityName=".ui.ViewKeyActivity"></activity> + android:parentActivityName=".ui.ViewKeyActivity"/> + <!-- NOTE: Dont use configChanges for QR Code view! We use a different layout for landscape --> <activity android:name=".ui.QrCodeViewActivity" android:label="@string/share_qr_code_dialog_title" /> diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditIdentitiesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditIdentitiesActivity.java new file mode 100644 index 000000000..6f65e9cef --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditIdentitiesActivity.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.net.Uri; +import android.os.Bundle; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.base.BaseActivity; +import org.sufficientlysecure.keychain.util.Log; + +public class EditIdentitiesActivity extends BaseActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Uri dataUri = getIntent().getData(); + if (dataUri == null) { + Log.e(Constants.TAG, "Either a key Uri or EXTRA_SAVE_KEYRING_PARCEL is required!"); + finish(); + return; + } + + loadFragment(savedInstanceState, dataUri); + } + + @Override + protected void initLayout() { + setContentView(R.layout.edit_identities_activity); + } + + private void loadFragment(Bundle savedInstanceState, Uri dataUri) { + // However, if we're being restored from a previous state, + // then we don't need to do anything and should return or else + // we could end up with overlapping fragments. + if (savedInstanceState != null) { + return; + } + + // Create an instance of the fragment + EditIdentitiesFragment mEditIdentitiesFragment; + mEditIdentitiesFragment = EditIdentitiesFragment.newInstance(dataUri); + + // Add the fragment to the 'fragment_container' FrameLayout + // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + getSupportFragmentManager().beginTransaction() + .replace(R.id.edit_key_fragment_container, mEditIdentitiesFragment) + .commitAllowingStateLoss(); + // do it immediately! + getSupportFragmentManager().executePendingTransactions(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditIdentitiesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditIdentitiesFragment.java new file mode 100644 index 000000000..cfd3f3cea --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditIdentitiesFragment.java @@ -0,0 +1,461 @@ +/* + * Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui; + +import android.app.Activity; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.CheckBox; +import android.widget.ListView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.SingletonResult; +import org.sufficientlysecure.keychain.operations.results.UploadResult; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; +import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.UploadKeyringParcel; +import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; +import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; +import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; + +public class EditIdentitiesFragment extends Fragment + implements LoaderManager.LoaderCallbacks<Cursor> { + + public static final String ARG_DATA_URI = "uri"; + + private CheckBox mUploadKeyCheckbox; + private ListView mUserIdsList; + private ListView mUserIdsAddedList; + private View mAddUserId; + + private static final int LOADER_ID_USER_IDS = 0; + + private UserIdsAdapter mUserIdsAdapter; + private UserIdsAddedAdapter mUserIdsAddedAdapter; + + private Uri mDataUri; + + private SaveKeyringParcel mSaveKeyringParcel; + + private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mEditOpHelper; + private CryptoOperationHelper<UploadKeyringParcel, UploadResult> mUploadOpHelper; + + private String mPrimaryUserId; + + /** + * Creates new instance of this fragment + */ + public static EditIdentitiesFragment newInstance(Uri dataUri) { + EditIdentitiesFragment frag = new EditIdentitiesFragment(); + + Bundle args = new Bundle(); + args.putParcelable(ARG_DATA_URI, dataUri); + + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.edit_identities_fragment, null); + + mUploadKeyCheckbox = (CheckBox) view.findViewById(R.id.edit_identities_upload_checkbox); + mUserIdsList = (ListView) view.findViewById(R.id.edit_identities_user_ids); + mUserIdsAddedList = (ListView) view.findViewById(R.id.edit_identities_user_ids_added); + mAddUserId = view.findViewById(R.id.edit_identities_add_user_id); + + // If this is a debug build, don't upload by default + if (Constants.DEBUG) { + mUploadKeyCheckbox.setChecked(false); + } + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + ((EditIdentitiesActivity) getActivity()).setFullScreenDialogDoneClose( + R.string.btn_save, + new OnClickListener() { + @Override + public void onClick(View v) { + editKey(); + } + }, new OnClickListener() { + @Override + public void onClick(View v) { + getActivity().setResult(Activity.RESULT_CANCELED); + getActivity().finish(); + } + }); + + Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); + if (dataUri == null) { + Log.e(Constants.TAG, "Either a key Uri is required!"); + getActivity().finish(); + return; + } + + initView(); + loadData(dataUri); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mEditOpHelper != null) { + mEditOpHelper.handleActivityResult(requestCode, resultCode, data); + } + if (mUploadOpHelper != null) { + mUploadOpHelper.handleActivityResult(requestCode, resultCode, data); + } + + super.onActivityResult(requestCode, resultCode, data); + } + + private void loadData(Uri dataUri) { + mDataUri = dataUri; + + Log.i(Constants.TAG, "mDataUri: " + mDataUri); + + // load the secret key ring. we do verify here that the passphrase is correct, so cached won't do + try { + Uri secretUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); + CachedPublicKeyRing keyRing = + new ProviderHelper(getActivity()).getCachedPublicKeyRing(secretUri); + long masterKeyId = keyRing.getMasterKeyId(); + + // check if this is a master secret key we can work with + switch (keyRing.getSecretKeyType(masterKeyId)) { + case GNU_DUMMY: + finishWithError(LogType.MSG_EK_ERROR_DUMMY); + return; + } + + mSaveKeyringParcel = new SaveKeyringParcel(masterKeyId, keyRing.getFingerprint()); + mPrimaryUserId = keyRing.getPrimaryUserIdWithFallback(); + + } catch (PgpKeyNotFoundException | NotFoundException e) { + finishWithError(LogType.MSG_EK_ERROR_NOT_FOUND); + return; + } + + // Prepare the loaders. Either re-connect with an existing ones, + // or start new ones. + getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditIdentitiesFragment.this); + + mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0); + mUserIdsAdapter.setEditMode(mSaveKeyringParcel); + mUserIdsList.setAdapter(mUserIdsAdapter); + + // TODO: SaveParcel from savedInstance?! + mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mSaveKeyringParcel.mAddUserIds, false); + mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter); + } + + private void initView() { + mAddUserId.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + addUserId(); + } + }); + + mUserIdsList.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + editUserId(position); + } + }); + } + + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + + switch (id) { + case LOADER_ID_USER_IDS: { + Uri baseUri = UserPackets.buildUserIdsUri(mDataUri); + return new CursorLoader(getActivity(), baseUri, + UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null); + } + + default: + return null; + } + } + + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + switch (loader.getId()) { + case LOADER_ID_USER_IDS: { + mUserIdsAdapter.swapCursor(data); + break; + } + } + } + + /** + * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. + * We need to make sure we are no longer using it. + */ + public void onLoaderReset(Loader<Cursor> loader) { + switch (loader.getId()) { + case LOADER_ID_USER_IDS: { + mUserIdsAdapter.swapCursor(null); + break; + } + } + } + + private void editUserId(final int position) { + final String userId = mUserIdsAdapter.getUserId(position); + final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position); + final boolean isRevokedPending = mUserIdsAdapter.getIsRevokedPending(position); + + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + switch (message.what) { + case EditUserIdDialogFragment.MESSAGE_CHANGE_PRIMARY_USER_ID: + // toggle + if (mSaveKeyringParcel.mChangePrimaryUserId != null + && mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) { + mSaveKeyringParcel.mChangePrimaryUserId = null; + } else { + mSaveKeyringParcel.mChangePrimaryUserId = userId; + } + break; + case EditUserIdDialogFragment.MESSAGE_REVOKE: + // toggle + if (mSaveKeyringParcel.mRevokeUserIds.contains(userId)) { + mSaveKeyringParcel.mRevokeUserIds.remove(userId); + } else { + mSaveKeyringParcel.mRevokeUserIds.add(userId); + // not possible to revoke and change to primary user id + if (mSaveKeyringParcel.mChangePrimaryUserId != null + && mSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) { + mSaveKeyringParcel.mChangePrimaryUserId = null; + } + } + break; + } + getLoaderManager().getLoader(LOADER_ID_USER_IDS).forceLoad(); + } + }; + + // Create a new Messenger for the communication back + final Messenger messenger = new Messenger(returnHandler); + + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { + public void run() { + EditUserIdDialogFragment dialogFragment = + EditUserIdDialogFragment.newInstance(messenger, isRevoked, isRevokedPending); + dialogFragment.show(getActivity().getSupportFragmentManager(), "editUserIdDialog"); + } + }); + } + + private void addUserId() { + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) { + Bundle data = message.getData(); + + // add new user id + mUserIdsAddedAdapter.add(data + .getString(AddUserIdDialogFragment.MESSAGE_DATA_USER_ID)); + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(returnHandler); + + // pre-fill out primary name + String predefinedName = KeyRing.splitUserId(mPrimaryUserId).name; + AddUserIdDialogFragment addUserIdDialog = AddUserIdDialogFragment.newInstance(messenger, + predefinedName, false); + + addUserIdDialog.show(getActivity().getSupportFragmentManager(), "addUserIdDialog"); + } + + + private void editKey() { + EditIdentitiesActivity activity = (EditIdentitiesActivity) getActivity(); + if (activity == null) { + // this is a ui-triggered action, nvm if it fails while detached! + return; + } + + CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult> editKeyCallback + = new CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult>() { + @Override + public SaveKeyringParcel createOperationInput() { + return mSaveKeyringParcel; + } + + @Override + public void onCryptoOperationSuccess(EditKeyResult result) { + + if (result.mMasterKeyId != null && mUploadKeyCheckbox.isChecked()) { + // result will be displayed after upload + uploadKey(result); + return; + } + + finishWithResult(result); + } + + @Override + public void onCryptoOperationCancelled() { + + } + + @Override + public void onCryptoOperationError(EditKeyResult result) { + displayResult(result); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; + + mEditOpHelper = new CryptoOperationHelper<>(1, this, editKeyCallback, R.string.progress_building_key); + mEditOpHelper.cryptoOperation(); + } + + + private void uploadKey(final EditKeyResult editKeyResult) { + Activity activity = getActivity(); + // if the activity is gone at this point, there is nothing we can do! + if (activity == null) { + return; + } + + // set data uri as path to keyring + final long masterKeyId = editKeyResult.mMasterKeyId; + // upload to favorite keyserver + final String keyserver = Preferences.getPreferences(activity).getPreferredKeyserver(); + + CryptoOperationHelper.Callback<UploadKeyringParcel, UploadResult> callback + = new CryptoOperationHelper.Callback<UploadKeyringParcel, UploadResult>() { + + @Override + public UploadKeyringParcel createOperationInput() { + return new UploadKeyringParcel(keyserver, masterKeyId); + } + + @Override + public void onCryptoOperationSuccess(UploadResult result) { + handleResult(result); + } + + @Override + public void onCryptoOperationCancelled() { + + } + + @Override + public void onCryptoOperationError(UploadResult result) { + displayResult(result); + } + + public void handleResult(UploadResult result) { + editKeyResult.getLog().add(result, 0); + finishWithResult(editKeyResult); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; + + mUploadOpHelper = new CryptoOperationHelper<>(3, this, callback, R.string.progress_uploading); + mUploadOpHelper.cryptoOperation(); + } + + /** + * Closes this activity, returning a result parcel with a single error log entry. + */ + void finishWithError(LogType reason) { + // Prepare an intent with an EXTRA_RESULT + Intent intent = new Intent(); + intent.putExtra(OperationResult.EXTRA_RESULT, + new SingletonResult(SingletonResult.RESULT_ERROR, reason)); + + // Finish with result + getActivity().setResult(Activity.RESULT_OK, intent); + getActivity().finish(); + } + + private void displayResult(OperationResult result) { + Activity activity = getActivity(); + if (activity == null) { + return; + } + result.createNotify(activity).show(); + } + + public void finishWithResult(OperationResult result) { + Activity activity = getActivity(); + if (activity == null) { + return; + } + + Intent data = new Intent(); + data.putExtra(OperationResult.EXTRA_RESULT, result); + activity.setResult(Activity.RESULT_OK, data); + activity.finish(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 07b0a12d3..1c72cdf41 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -223,14 +223,16 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this); getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, EditKeyFragment.this); - mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, mSaveKeyringParcel); + mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0); + mUserIdsAdapter.setEditMode(mSaveKeyringParcel); mUserIdsList.setAdapter(mUserIdsAdapter); // TODO: SaveParcel from savedInstance?! mUserIdsAddedAdapter = new UserIdsAddedAdapter(getActivity(), mSaveKeyringParcel.mAddUserIds, false); mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter); - mSubkeysAdapter = new SubkeysAdapter(getActivity(), null, 0, mSaveKeyringParcel); + mSubkeysAdapter = new SubkeysAdapter(getActivity(), null, 0); + mSubkeysAdapter.setEditMode(mSaveKeyringParcel); mSubkeysList.setAdapter(mSubkeysAdapter); mSubkeysAddedAdapter = new SubkeysAddedAdapter(getActivity(), mSaveKeyringParcel.mAddSubKeys, false); @@ -554,7 +556,7 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring // pre-fill out primary name String predefinedName = KeyRing.splitUserId(mPrimaryUserId).name; AddUserIdDialogFragment addUserIdDialog = AddUserIdDialogFragment.newInstance(messenger, - predefinedName); + predefinedName, true); addUserIdDialog.show(getActivity().getSupportFragmentManager(), "addUserIdDialog"); } @@ -610,7 +612,7 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring new SingletonResult(SingletonResult.RESULT_ERROR, reason)); // Finish with result - getActivity().setResult(EditKeyActivity.RESULT_OK, intent); + getActivity().setResult(Activity.RESULT_OK, intent); getActivity().finish(); } @@ -628,7 +630,7 @@ public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyring // if good -> finish, return result to showkey and display there! Intent intent = new Intent(); intent.putExtra(OperationResult.EXTRA_RESULT, result); - activity.setResult(EditKeyActivity.RESULT_OK, intent); + activity.setResult(Activity.RESULT_OK, intent); activity.finish(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 35e00ff21..1db273e48 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -36,6 +36,8 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.Message; +import android.os.Messenger; import android.provider.ContactsContract; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CollapsingToolbarLayout; @@ -64,6 +66,7 @@ import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; +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; @@ -75,10 +78,11 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.ViewKeyFragment.PostponeType; import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; -import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard; +import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; @@ -90,6 +94,7 @@ import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.NfcHelper; +import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; @@ -116,7 +121,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements // For CryptoOperationHelper.Callback private String mKeyserver; private ArrayList<ParcelableKeyRing> mKeyList; - private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mOperationHelper; + private CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult> mImportOpHelper; + private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mEditOpHelper; + private SaveKeyringParcel mSaveKeyringParcel; private TextView mStatusText; private ImageView mStatusImage; @@ -151,8 +158,10 @@ public class ViewKeyActivity extends BaseNfcActivity implements private boolean mIsRefreshing; private Animation mRotate, mRotateSpin; private View mRefresh; - private String mFingerprint; + private long mMasterKeyId; + private byte[] mFingerprint; + private String mFingerprintString; private byte[] mNfcFingerprints; private String mNfcUserId; @@ -164,7 +173,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements super.onCreate(savedInstanceState); mProviderHelper = new ProviderHelper(this); - mOperationHelper = new CryptoOperationHelper<>(1, this, this, null); + mImportOpHelper = new CryptoOperationHelper<>(1, this, this, null); setTitle(null); @@ -357,6 +366,10 @@ public class ViewKeyActivity extends BaseNfcActivity implements startActivity(homeIntent); return true; } + case R.id.menu_key_change_password: { + changePassword(); + return true; + } case R.id.menu_key_view_backup: { startPassphraseActivity(REQUEST_BACKUP); return true; @@ -379,23 +392,12 @@ public class ViewKeyActivity extends BaseNfcActivity implements } return true; } - case R.id.menu_key_view_add_linked_identity: { - Intent intent = new Intent(this, LinkedIdWizard.class); - intent.setData(mDataUri); - startActivity(intent); - finish(); - return true; - } - case R.id.menu_key_view_edit: { - editKey(mDataUri); - return true; - } case R.id.menu_key_view_certify_fingerprint: { - certifyFingeprint(mDataUri, false); + certifyFingerprint(mDataUri, false); return true; } case R.id.menu_key_view_certify_fingerprint_word: { - certifyFingeprint(mDataUri, true); + certifyFingerprint(mDataUri, true); return true; } } @@ -404,15 +406,10 @@ public class ViewKeyActivity extends BaseNfcActivity implements @Override public boolean onPrepareOptionsMenu(Menu menu) { - MenuItem editKey = menu.findItem(R.id.menu_key_view_edit); - editKey.setVisible(mIsSecret); - MenuItem backupKey = menu.findItem(R.id.menu_key_view_backup); backupKey.setVisible(mIsSecret); - - MenuItem addLinked = menu.findItem(R.id.menu_key_view_add_linked_identity); - addLinked.setVisible(mIsSecret - && Preferences.getPreferences(this).getExperimentalEnableLinkedIdentities()); + MenuItem changePassword = menu.findItem(R.id.menu_key_change_password); + changePassword.setVisible(mIsSecret); MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint); certifyFingerprint.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked); @@ -423,6 +420,69 @@ public class ViewKeyActivity extends BaseNfcActivity implements return true; } + private void changePassword() { + mSaveKeyringParcel = new SaveKeyringParcel(mMasterKeyId, mFingerprint); + + CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult> editKeyCallback + = new CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult>() { + @Override + public SaveKeyringParcel createOperationInput() { + return mSaveKeyringParcel; + } + + @Override + public void onCryptoOperationSuccess(EditKeyResult result) { + displayResult(result); + } + + @Override + public void onCryptoOperationCancelled() { + + } + + @Override + public void onCryptoOperationError(EditKeyResult result) { + displayResult(result); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; + + mEditOpHelper = new CryptoOperationHelper<>(2, this, editKeyCallback, R.string.progress_building_key); + + // Message is received after passphrase is cached + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) { + Bundle data = message.getData(); + + // use new passphrase! + mSaveKeyringParcel.mNewUnlock = new SaveKeyringParcel.ChangeUnlockParcel( + (Passphrase) data.getParcelable(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE), + null + ); + + mEditOpHelper.cryptoOperation(); + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(returnHandler); + + SetPassphraseDialogFragment setPassphraseDialog = SetPassphraseDialogFragment.newInstance( + messenger, R.string.title_change_passphrase); + + setPassphraseDialog.show(getSupportFragmentManager(), "setPassphraseDialog"); + } + + private void displayResult(OperationResult result) { + result.createNotify(this).show(); + } private void scanQrCode() { Intent scanQrCode = new Intent(this, ImportKeysProxyActivity.class); @@ -430,7 +490,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements startActivityForResult(scanQrCode, REQUEST_QR_FINGERPRINT); } - private void certifyFingeprint(Uri dataUri, boolean enableWordConfirm) { + private void certifyFingerprint(Uri dataUri, boolean enableWordConfirm) { Intent intent = new Intent(this, CertifyFingerprintActivity.class); intent.setData(dataUri); intent.putExtra(CertifyFingerprintActivity.EXTRA_ENABLE_WORD_CONFIRM, enableWordConfirm); @@ -440,7 +500,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements private void certifyImmediate() { Intent intent = new Intent(this, CertifyKeyActivity.class); - intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[] { mMasterKeyId }); + intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{mMasterKeyId}); startActivityForResult(intent, REQUEST_CERTIFY); } @@ -515,9 +575,12 @@ public class ViewKeyActivity extends BaseNfcActivity implements @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (mOperationHelper.handleActivityResult(requestCode, resultCode, data)) { + if (mImportOpHelper.handleActivityResult(requestCode, resultCode, data)) { return; } + if (mEditOpHelper != null) { + mEditOpHelper.handleActivityResult(requestCode, resultCode, data); + } switch (requestCode) { case REQUEST_QR_FINGERPRINT: { @@ -538,7 +601,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements Notify.create(this, R.string.error_scan_fp, Notify.LENGTH_LONG, Style.ERROR).show(); return; } - if (mFingerprint.equalsIgnoreCase(fp)) { + if (mFingerprintString.equalsIgnoreCase(fp)) { certifyImmediate(); } else { Notify.create(this, R.string.error_scan_match, Notify.LENGTH_LONG, Style.ERROR).show(); @@ -603,7 +666,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements byte[] candidateFp = ring.getFingerprint(); // if the master key of that key matches this one, just show the yubikey dialog - if (KeyFormattingUtils.convertFingerprintToHex(candidateFp).equals(mFingerprint)) { + if (KeyFormattingUtils.convertFingerprintToHex(candidateFp).equals(mFingerprintString)) { showYubiKeyFragment(mNfcFingerprints, mNfcUserId, mNfcAid); return; } @@ -692,12 +755,6 @@ public class ViewKeyActivity extends BaseNfcActivity implements } } - private void editKey(Uri dataUri) { - Intent editIntent = new Intent(this, EditKeyActivity.class); - editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri)); - startActivityForResult(editIntent, 0); - } - private void startSafeSlinger(Uri dataUri) { long keyId = 0; try { @@ -808,14 +865,15 @@ public class ViewKeyActivity extends BaseNfcActivity implements /* TODO better error handling? May cause problems when a key is deleted, * because the notification triggers faster than the activity closes. */ - // Avoid NullPointerExceptions... - if (data.getCount() == 0) { - 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; + } if (data.moveToFirst()) { // get name, email, and comment from USER_ID @@ -827,7 +885,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements } mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID); - mFingerprint = KeyFormattingUtils.convertFingerprintToHex(data.getBlob(INDEX_FINGERPRINT)); + mFingerprint = data.getBlob(INDEX_FINGERPRINT); + mFingerprintString = KeyFormattingUtils.convertFingerprintToHex(mFingerprint); // if it wasn't shown yet, display yubikey fragment if (mShowYubikeyAfterCreation && getIntent().hasExtra(EXTRA_NFC_AID)) { @@ -904,8 +963,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements mStatusImage.setVisibility(View.GONE); color = getResources().getColor(R.color.key_flag_green); // reload qr code only if the fingerprint changed - if (!mFingerprint.equals(mQrCodeLoaded)) { - loadQrCode(mFingerprint); + if (!mFingerprintString.equals(mQrCodeLoaded)) { + loadQrCode(mFingerprintString); } photoTask.execute(mMasterKeyId); mQrCodeLayout.setVisibility(View.VISIBLE); @@ -1045,7 +1104,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements mKeyserver = Preferences.getPreferences(this).getPreferredKeyserver(); - mOperationHelper.cryptoOperation(); + mImportOpHelper.cryptoOperation(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java index 94a171f14..b10e5f8d4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -17,16 +17,26 @@ package org.sufficientlysecure.keychain.ui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.provider.ContactsContract; -import android.support.v4.app.LoaderManager; +import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.view.ViewPager; +import android.support.v4.view.ViewPager.OnPageChangeListener; +import android.view.ActionMode; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; +import android.view.ViewPropertyAnimator; +import android.view.animation.OvershootInterpolator; import android.widget.Toast; import com.astuetz.PagerSlidingTabStrip; @@ -44,7 +54,7 @@ import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.Log; public class ViewKeyAdvActivity extends BaseActivity implements - LoaderManager.LoaderCallbacks<Cursor> { + LoaderCallbacks<Cursor>, OnPageChangeListener { ProviderHelper mProviderHelper; @@ -61,6 +71,11 @@ public class ViewKeyAdvActivity extends BaseActivity implements private PagerSlidingTabStrip mSlidingTabLayout; private static final int LOADER_ID_UNIFIED = 0; + private ActionMode mActionMode; + private boolean mHasSecret; + private PagerTabStripAdapter mTabAdapter; + private boolean mActionIconShown; + private boolean[] mTabsWithActionMode; @Override protected void onCreate(Bundle savedInstanceState) { @@ -78,9 +93,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements mViewPager = (ViewPager) findViewById(R.id.pager); mSlidingTabLayout = (PagerSlidingTabStrip) findViewById(R.id.sliding_tab_layout); - Intent intent = getIntent(); - int switchToTab = intent.getIntExtra(EXTRA_SELECTED_TAB, TAB_SHARE); - mDataUri = getIntent().getData(); if (mDataUri == null) { Log.e(Constants.TAG, "Data missing. Should be uri of key!"); @@ -102,9 +114,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); initTabs(mDataUri); - - // switch to tab selected by extra - mViewPager.setCurrentItem(switchToTab); } @Override @@ -113,31 +122,45 @@ public class ViewKeyAdvActivity extends BaseActivity implements } private void initTabs(Uri dataUri) { - PagerTabStripAdapter adapter = new PagerTabStripAdapter(this); - mViewPager.setAdapter(adapter); + mTabAdapter = new PagerTabStripAdapter(this); + mViewPager.setAdapter(mTabAdapter); + + // keep track which of these are action mode enabled! + mTabsWithActionMode = new boolean[4]; Bundle shareBundle = new Bundle(); - shareBundle.putParcelable(ViewKeyAdvUserIdsFragment.ARG_DATA_URI, dataUri); - adapter.addTab(ViewKeyAdvShareFragment.class, + shareBundle.putParcelable(ViewKeyAdvShareFragment.ARG_DATA_URI, dataUri); + mTabAdapter.addTab(ViewKeyAdvShareFragment.class, shareBundle, getString(R.string.key_view_tab_share)); + mTabsWithActionMode[0] = false; Bundle userIdsBundle = new Bundle(); userIdsBundle.putParcelable(ViewKeyAdvUserIdsFragment.ARG_DATA_URI, dataUri); - adapter.addTab(ViewKeyAdvUserIdsFragment.class, + mTabAdapter.addTab(ViewKeyAdvUserIdsFragment.class, userIdsBundle, getString(R.string.section_user_ids)); + mTabsWithActionMode[1] = true; Bundle keysBundle = new Bundle(); keysBundle.putParcelable(ViewKeyAdvSubkeysFragment.ARG_DATA_URI, dataUri); - adapter.addTab(ViewKeyAdvSubkeysFragment.class, + mTabAdapter.addTab(ViewKeyAdvSubkeysFragment.class, keysBundle, getString(R.string.key_view_tab_keys)); + mTabsWithActionMode[2] = true; Bundle certsBundle = new Bundle(); certsBundle.putParcelable(ViewKeyAdvCertsFragment.ARG_DATA_URI, dataUri); - adapter.addTab(ViewKeyAdvCertsFragment.class, + mTabAdapter.addTab(ViewKeyAdvCertsFragment.class, certsBundle, getString(R.string.key_view_tab_certs)); + mTabsWithActionMode[3] = false; // update layout after operations mSlidingTabLayout.setViewPager(mViewPager); + mSlidingTabLayout.setOnPageChangeListener(this); + + // switch to tab selected by extra + Intent intent = getIntent(); + int switchToTab = intent.getIntExtra(EXTRA_SELECTED_TAB, TAB_SHARE); + mViewPager.setCurrentItem(switchToTab); + } // These are the rows that we will retrieve. @@ -148,7 +171,8 @@ public class ViewKeyAdvActivity extends BaseActivity implements KeychainContract.KeyRings.IS_REVOKED, KeychainContract.KeyRings.IS_EXPIRED, KeychainContract.KeyRings.VERIFIED, - KeychainContract.KeyRings.HAS_ANY_SECRET + KeychainContract.KeyRings.HAS_ANY_SECRET, + KeychainContract.KeyRings.FINGERPRINT, }; static final int INDEX_MASTER_KEY_ID = 1; @@ -157,6 +181,7 @@ public class ViewKeyAdvActivity extends BaseActivity implements static final int INDEX_IS_EXPIRED = 4; static final int INDEX_VERIFIED = 5; static final int INDEX_HAS_ANY_SECRET = 6; + static final int INDEX_FINGERPRINT = 7; @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { @@ -190,11 +215,13 @@ public class ViewKeyAdvActivity extends BaseActivity implements setTitle(R.string.user_id_no_name); } + byte[] fingerprint = data.getBlob(INDEX_FINGERPRINT); + // get key id from MASTER_KEY_ID long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); getSupportActionBar().setSubtitle(KeyFormattingUtils.beautifyKeyIdWithPrefix(this, masterKeyId)); - boolean isSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; + mHasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; boolean isRevoked = data.getInt(INDEX_IS_REVOKED) > 0; boolean isExpired = data.getInt(INDEX_IS_EXPIRED) != 0; boolean isVerified = data.getInt(INDEX_VERIFIED) > 0; @@ -203,7 +230,7 @@ public class ViewKeyAdvActivity extends BaseActivity implements int color; if (isRevoked || isExpired) { color = getResources().getColor(R.color.key_flag_red); - } else if (isSecret) { + } else if (mHasSecret) { color = getResources().getColor(R.color.android_green_light); } else { if (isVerified) { @@ -237,4 +264,85 @@ public class ViewKeyAdvActivity extends BaseActivity implements super.onActivityResult(requestCode, resultCode, data); } } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + + if (!mHasSecret) { + return false; + } + + // always add the item, switch its visibility depending on fragment + getMenuInflater().inflate(R.menu.action_mode_edit, menu); + final MenuItem vActionModeItem = menu.findItem(R.id.menu_action_mode_edit); + + boolean isCurrentActionFragment = mTabsWithActionMode[mViewPager.getCurrentItem()]; + + // if the state is as it should be, never mind + if (isCurrentActionFragment == mActionIconShown) { + return isCurrentActionFragment; + } + + // show or hide accordingly + mActionIconShown = isCurrentActionFragment; + vActionModeItem.setEnabled(isCurrentActionFragment); + animateMenuItem(vActionModeItem, isCurrentActionFragment); + + return true; + } + + private void animateMenuItem(final MenuItem vEditSubkeys, final boolean animateShow) { + + View actionView = LayoutInflater.from(this).inflate(R.layout.edit_icon, null); + vEditSubkeys.setActionView(actionView); + actionView.setTranslationX(animateShow ? 150 : 0); + + ViewPropertyAnimator animator = actionView.animate(); + animator.translationX(animateShow ? 0 : 150); + animator.setDuration(300); + animator.setInterpolator(new OvershootInterpolator(1.5f)); + animator.setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (!animateShow) { + vEditSubkeys.setVisible(false); + } + vEditSubkeys.setActionView(null); + } + }); + animator.start(); + + } + + @Override + public void onActionModeStarted(final ActionMode mode) { + super.onActionModeStarted(mode); + mActionMode = mode; + } + + @Override + public void onActionModeFinished(ActionMode mode) { + super.onActionModeFinished(mode); + mActionMode = null; + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + if (mActionMode != null) { + mActionMode.finish(); + mActionMode = null; + } + invalidateOptionsMenu(); + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java index c5e575e32..ce2f2def8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -44,6 +44,7 @@ import android.support.v4.content.Loader; import android.support.v7.widget.CardView; import android.view.LayoutInflater; import android.view.View; +import android.view.View.OnLayoutChangeListener; import android.view.ViewGroup; import android.view.animation.AlphaAnimation; import android.widget.ImageButton; @@ -85,6 +86,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements private byte[] mFingerprint; private String mUserId; + private Bitmap mQrCodeBitmapCache; @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { @@ -96,6 +98,34 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements mFingerprintView = (TextView) view.findViewById(R.id.view_key_fingerprint); mQrCode = (ImageView) view.findViewById(R.id.view_key_qr_code); + + // We cache the QR code bitmap in its smallest possible size, then scale + // it manually for the correct size whenever the layout of the ImageView + // changes. The fingerprint qr code loader which runs in the background + // just calls requestLayout when it is finished, this way the loader and + // background task are disconnected from any layouting the ImageView may + // undergo. Please note how these six lines are perfectly right-aligned. + mQrCode.addOnLayoutChangeListener(new OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, + int oldRight, + int oldBottom) { + // bitmap scaling is expensive, avoid doing it if we already have the correct size! + int mCurrentWidth = 0, mCurrentHeight = 0; + if (mQrCodeBitmapCache != null) { + if (mCurrentWidth == mQrCode.getWidth() && mCurrentHeight == mQrCode.getHeight()) { + return; + } + mCurrentWidth = mQrCode.getWidth(); + mCurrentHeight = mQrCode.getHeight(); + // scale the image up to our actual size. we do this in code rather + // than let the ImageView do this because we don't require filtering. + Bitmap scaled = Bitmap.createScaledBitmap(mQrCodeBitmapCache, + mCurrentWidth, mCurrentHeight, false); + mQrCode.setImageBitmap(scaled); + } + } + }); mQrCodeLayout = (CardView) view.findViewById(R.id.view_key_qr_code_layout); mQrCodeLayout.setOnClickListener(new View.OnClickListener() { @Override @@ -379,6 +409,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements */ public void onLoaderReset(Loader<Cursor> loader) { mFingerprint = null; + mQrCodeBitmapCache = null; } /** @@ -390,6 +421,10 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob); mFingerprintView.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint)); + if (mQrCodeBitmapCache != null) { + return; + } + AsyncTask<Void, Void, Bitmap> loadTask = new AsyncTask<Void, Void, Bitmap>() { protected Bitmap doInBackground(Void... unused) { @@ -402,15 +437,11 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements } protected void onPostExecute(Bitmap qrCode) { - // only change view, if fragment is attached to activity - if (ViewKeyAdvShareFragment.this.isAdded()) { + // cache for later, and if we are attached request re-layout + mQrCodeBitmapCache = qrCode; - // scale the image up to our actual size. we do this in code rather - // than let the ImageView do this because we don't require filtering. - Bitmap scaled = Bitmap.createScaledBitmap(qrCode, - mQrCode.getHeight(), mQrCode.getHeight(), - false); - mQrCode.setImageBitmap(scaled); + if (ViewKeyAdvShareFragment.this.isAdded()) { + mQrCode.requestLayout(); // simple fade-in animation AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java index bd00c6780..14477723e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java @@ -17,21 +17,43 @@ package org.sufficientlysecure.keychain.ui; + +import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; +import android.view.ActionMode; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; import android.widget.ListView; +import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; +import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; +import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyExpiryDialogFragment; +import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements @@ -39,30 +61,62 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements public static final String ARG_DATA_URI = "data_uri"; + private static final int LOADER_ID_UNIFIED = 0; + private static final int LOADER_ID_SUBKEYS = 1; + private ListView mSubkeysList; + private ListView mSubkeysAddedList; + private View mSubkeysAddedLayout; + private ViewAnimator mSubkeyAddFabLayout; + private SubkeysAdapter mSubkeysAdapter; + private SubkeysAddedAdapter mSubkeysAddedAdapter; - private Uri mDataUriSubkeys; + private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mEditKeyHelper; - /** - * Creates new instance of this fragment - */ - public static ViewKeyAdvSubkeysFragment newInstance(Uri dataUri) { - ViewKeyAdvSubkeysFragment frag = new ViewKeyAdvSubkeysFragment(); + private Uri mDataUri; - Bundle args = new Bundle(); - args.putParcelable(ARG_DATA_URI, dataUri); - - frag.setArguments(args); - return frag; - } + private long mMasterKeyId; + private byte[] mFingerprint; + private boolean mHasSecret; + private SaveKeyringParcel mEditModeSaveKeyringParcel; @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_adv_subkeys_fragment, getContainer()); - mSubkeysList = (ListView) view.findViewById(R.id.keys); + mSubkeysList = (ListView) view.findViewById(R.id.view_key_subkeys); + mSubkeysAddedList = (ListView) view.findViewById(R.id.view_key_subkeys_added); + mSubkeysAddedLayout = view.findViewById(R.id.view_key_subkeys_add_layout); + + mSubkeysList.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + editSubkey(position); + } + }); + + View footer = new View(getActivity()); + int spacing = (int) android.util.TypedValue.applyDimension( + android.util.TypedValue.COMPLEX_UNIT_DIP, 72, getResources().getDisplayMetrics() + ); + android.widget.AbsListView.LayoutParams params = new android.widget.AbsListView.LayoutParams( + android.widget.AbsListView.LayoutParams.MATCH_PARENT, + spacing + ); + footer.setLayoutParams(params); + mSubkeysAddedList.addFooterView(footer, null, false); + + mSubkeyAddFabLayout = (ViewAnimator) view.findViewById(R.id.view_key_subkey_fab_layout); + view.findViewById(R.id.view_key_subkey_fab).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + addSubkey(); + } + }); + + setHasOptionsMenu(true); return root; } @@ -81,8 +135,17 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements loadData(dataUri); } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mEditKeyHelper != null) { + mEditKeyHelper.handleActivityResult(requestCode, resultCode, data); + } + + super.onActivityResult(requestCode, resultCode, data); + } + private void loadData(Uri dataUri) { - mDataUriSubkeys = KeychainContract.Keys.buildKeysUri(dataUri); + mDataUri = dataUri; // Create an empty adapter we will use to display the loaded data. mSubkeysAdapter = new SubkeysAdapter(getActivity(), null, 0); @@ -90,14 +153,42 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. - getLoaderManager().initLoader(0, null, this); + getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); + getLoaderManager().initLoader(LOADER_ID_SUBKEYS, null, this); } + // These are the rows that we will retrieve. + static final String[] PROJECTION = new String[]{ + KeychainContract.KeyRings._ID, + KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.HAS_ANY_SECRET, + KeychainContract.KeyRings.FINGERPRINT, + }; + + static final int INDEX_MASTER_KEY_ID = 1; + static final int INDEX_HAS_ANY_SECRET = 2; + static final int INDEX_FINGERPRINT = 3; + + @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { - setContentShown(false); + switch (id) { + case LOADER_ID_UNIFIED: { + Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); + return new CursorLoader(getActivity(), baseUri, + PROJECTION, null, null, null); + } + + case LOADER_ID_SUBKEYS: { + setContentShown(false); - return new CursorLoader(getActivity(), mDataUriSubkeys, - SubkeysAdapter.SUBKEYS_PROJECTION, null, null, null); + Uri subkeysUri = KeychainContract.Keys.buildKeysUri(mDataUri); + return new CursorLoader(getActivity(), subkeysUri, + SubkeysAdapter.SUBKEYS_PROJECTION, null, null, null); + } + + default: + return null; + } } public void onLoadFinished(Loader<Cursor> loader, Cursor data) { @@ -106,12 +197,26 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements return; } - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - mSubkeysAdapter.swapCursor(data); + switch (loader.getId()) { + case LOADER_ID_UNIFIED: { + data.moveToFirst(); + + mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID); + mHasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; + mFingerprint = data.getBlob(INDEX_FINGERPRINT); + break; + } + case LOADER_ID_SUBKEYS: { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + mSubkeysAdapter.swapCursor(data); + + // TODO: maybe show not before both are loaded! + setContentShown(true); + break; + } + } - // TODO: maybe show not before both are loaded! - setContentShown(true); } /** @@ -122,4 +227,254 @@ public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements mSubkeysAdapter.swapCursor(null); } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_action_mode_edit: + enterEditMode(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + public void enterEditMode() { + FragmentActivity activity = getActivity(); + if (activity == null) { + return; + } + activity.startActionMode(new ActionMode.Callback() { + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + + mEditModeSaveKeyringParcel = new SaveKeyringParcel(mMasterKeyId, mFingerprint); + + mSubkeysAddedAdapter = + new SubkeysAddedAdapter(getActivity(), mEditModeSaveKeyringParcel.mAddSubKeys, false); + mSubkeysAddedList.setAdapter(mSubkeysAddedAdapter); + mSubkeysAddedLayout.setVisibility(View.VISIBLE); + mSubkeyAddFabLayout.setDisplayedChild(1); + + mSubkeysAdapter.setEditMode(mEditModeSaveKeyringParcel); + getLoaderManager().restartLoader(LOADER_ID_SUBKEYS, null, ViewKeyAdvSubkeysFragment.this); + + mode.setTitle(R.string.title_edit_subkeys); + mode.getMenuInflater().inflate(R.menu.action_edit_uids, menu); + + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + editKey(mode); + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + mEditModeSaveKeyringParcel = null; + mSubkeysAdapter.setEditMode(null); + mSubkeysAddedLayout.setVisibility(View.GONE); + mSubkeyAddFabLayout.setDisplayedChild(0); + getLoaderManager().restartLoader(LOADER_ID_SUBKEYS, null, ViewKeyAdvSubkeysFragment.this); + } + }); + } + + private void addSubkey() { + boolean willBeMasterKey; + if (mSubkeysAdapter != null) { + willBeMasterKey = mSubkeysAdapter.getCount() == 0 && mSubkeysAddedAdapter.getCount() == 0; + } else { + willBeMasterKey = mSubkeysAddedAdapter.getCount() == 0; + } + + AddSubkeyDialogFragment addSubkeyDialogFragment = + AddSubkeyDialogFragment.newInstance(willBeMasterKey); + addSubkeyDialogFragment + .setOnAlgorithmSelectedListener( + new AddSubkeyDialogFragment.OnAlgorithmSelectedListener() { + @Override + public void onAlgorithmSelected(SaveKeyringParcel.SubkeyAdd newSubkey) { + mSubkeysAddedAdapter.add(newSubkey); + } + } + ); + addSubkeyDialogFragment.show(getActivity().getSupportFragmentManager(), "addSubkeyDialog"); + } + + private void editSubkey(final int position) { + final long keyId = mSubkeysAdapter.getKeyId(position); + + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + switch (message.what) { + case EditSubkeyDialogFragment.MESSAGE_CHANGE_EXPIRY: + editSubkeyExpiry(position); + break; + case EditSubkeyDialogFragment.MESSAGE_REVOKE: + // toggle + if (mEditModeSaveKeyringParcel.mRevokeSubKeys.contains(keyId)) { + mEditModeSaveKeyringParcel.mRevokeSubKeys.remove(keyId); + } else { + mEditModeSaveKeyringParcel.mRevokeSubKeys.add(keyId); + } + break; + case EditSubkeyDialogFragment.MESSAGE_STRIP: { + SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); + if (secretKeyType == SecretKeyType.GNU_DUMMY) { + // Key is already stripped; this is a no-op. + break; + } + + SubkeyChange change = mEditModeSaveKeyringParcel.getSubkeyChange(keyId); + if (change == null) { + mEditModeSaveKeyringParcel.mChangeSubKeys.add(new SubkeyChange(keyId, true, false)); + break; + } + // toggle + change.mDummyStrip = !change.mDummyStrip; + if (change.mDummyStrip && change.mMoveKeyToCard) { + // User had chosen to divert key, but now wants to strip it instead. + change.mMoveKeyToCard = false; + } + break; + } + case EditSubkeyDialogFragment.MESSAGE_MOVE_KEY_TO_CARD: { + // TODO: enable later when Admin PIN handling is resolved + Notify.create(getActivity(), + "This feature will be available in an upcoming OpenKeychain version.", + Notify.Style.WARN).show(); + break; + +// Activity activity = EditKeyFragment.this.getActivity(); +// SecretKeyType secretKeyType = mSubkeysAdapter.getSecretKeyType(position); +// if (secretKeyType == SecretKeyType.DIVERT_TO_CARD || +// secretKeyType == SecretKeyType.GNU_DUMMY) { +// Notify.create(activity, R.string.edit_key_error_bad_nfc_stripped, Notify.Style.ERROR) +// .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); +// break; +// } +// int algorithm = mSubkeysAdapter.getAlgorithm(position); +// // these are the PGP constants for RSA_GENERAL, RSA_ENCRYPT and RSA_SIGN +// if (algorithm != 1 && algorithm != 2 && algorithm != 3) { +// Notify.create(activity, R.string.edit_key_error_bad_nfc_algo, Notify.Style.ERROR) +// .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); +// break; +// } +// if (mSubkeysAdapter.getKeySize(position) != 2048) { +// Notify.create(activity, R.string.edit_key_error_bad_nfc_size, Notify.Style.ERROR) +// .show((ViewGroup) activity.findViewById(R.id.import_snackbar)); +// break; +// } +// +// +// SubkeyChange change; +// change = mSaveKeyringParcel.getSubkeyChange(keyId); +// if (change == null) { +// mSaveKeyringParcel.mChangeSubKeys.add( +// new SubkeyChange(keyId, false, true) +// ); +// break; +// } +// // toggle +// change.mMoveKeyToCard = !change.mMoveKeyToCard; +// if (change.mMoveKeyToCard && change.mDummyStrip) { +// // User had chosen to strip key, but now wants to divert it. +// change.mDummyStrip = false; +// } +// break; + } + } + getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); + } + }; + + // Create a new Messenger for the communication back + final Messenger messenger = new Messenger(returnHandler); + + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { + public void run() { + EditSubkeyDialogFragment dialogFragment = + EditSubkeyDialogFragment.newInstance(messenger); + + dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyDialog"); + } + }); + } + + private void editSubkeyExpiry(final int position) { + final long keyId = mSubkeysAdapter.getKeyId(position); + final Long creationDate = mSubkeysAdapter.getCreationDate(position); + final Long expiryDate = mSubkeysAdapter.getExpiryDate(position); + + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + switch (message.what) { + case EditSubkeyExpiryDialogFragment.MESSAGE_NEW_EXPIRY: + mEditModeSaveKeyringParcel.getOrCreateSubkeyChange(keyId).mExpiry = + (Long) message.getData().getSerializable( + EditSubkeyExpiryDialogFragment.MESSAGE_DATA_EXPIRY); + break; + } + getLoaderManager().getLoader(LOADER_ID_SUBKEYS).forceLoad(); + } + }; + + // Create a new Messenger for the communication back + final Messenger messenger = new Messenger(returnHandler); + + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { + public void run() { + EditSubkeyExpiryDialogFragment dialogFragment = + EditSubkeyExpiryDialogFragment.newInstance(messenger, creationDate, expiryDate); + + dialogFragment.show(getActivity().getSupportFragmentManager(), "editSubkeyExpiryDialog"); + } + }); + } + + + private void editKey(final ActionMode mode) { + CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult> editKeyCallback + = new CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult>() { + + @Override + public SaveKeyringParcel createOperationInput() { + return mEditModeSaveKeyringParcel; + } + + @Override + public void onCryptoOperationSuccess(EditKeyResult result) { + mode.finish(); + result.createNotify(getActivity()).show(); + } + + @Override + public void onCryptoOperationCancelled() { + mode.finish(); + } + + @Override + public void onCryptoOperationError(EditKeyResult result) { + mode.finish(); + result.createNotify(getActivity()).show(); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; + mEditKeyHelper = new CryptoOperationHelper<>(1, this, editKeyCallback, R.string.progress_saving); + mEditKeyHelper.cryptoOperation(); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java index ad437f924..69ccab162 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java @@ -18,24 +18,41 @@ package org.sufficientlysecure.keychain.ui; + +import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger; +import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; +import android.view.ActionMode; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ListView; +import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.operations.results.EditKeyResult; +import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; +import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; +import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; import org.sufficientlysecure.keychain.util.Log; @@ -44,33 +61,124 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements public static final String ARG_DATA_URI = "uri"; - private ListView mUserIds; - private static final int LOADER_ID_UNIFIED = 0; private static final int LOADER_ID_USER_IDS = 1; + private ListView mUserIds; + private ListView mUserIdsAddedList; + private View mUserIdsAddedLayout; + private ViewAnimator mUserIdAddFabLayout; + private UserIdsAdapter mUserIdsAdapter; + private UserIdsAddedAdapter mUserIdsAddedAdapter; + + private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mEditKeyHelper; private Uri mDataUri; + private long mMasterKeyId; + private byte[] mFingerprint; + private boolean mHasSecret; + private SaveKeyringParcel mEditModeSaveKeyringParcel; + @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_adv_main_fragment, getContainer()); + View view = inflater.inflate(R.layout.view_key_adv_user_ids_fragment, getContainer()); mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); + mUserIdsAddedList = (ListView) view.findViewById(R.id.view_key_user_ids_added); + mUserIdsAddedLayout = view.findViewById(R.id.view_key_user_ids_add_layout); mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - showUserIdInfo(position); + showOrEditUserIdInfo(position); } }); + View footer = new View(getActivity()); + int spacing = (int) android.util.TypedValue.applyDimension( + android.util.TypedValue.COMPLEX_UNIT_DIP, 72, getResources().getDisplayMetrics() + ); + android.widget.AbsListView.LayoutParams params = new android.widget.AbsListView.LayoutParams( + android.widget.AbsListView.LayoutParams.MATCH_PARENT, + spacing + ); + footer.setLayoutParams(params); + mUserIdsAddedList.addFooterView(footer, null, false); + + mUserIdAddFabLayout = (ViewAnimator) view.findViewById(R.id.view_key_subkey_fab_layout); + view.findViewById(R.id.view_key_subkey_fab).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + addUserId(); + } + }); + + setHasOptionsMenu(true); + return root; } + private void showOrEditUserIdInfo(final int position) { + if (mEditModeSaveKeyringParcel != null) { + editUserId(position); + } else { + showUserIdInfo(position); + } + } + + private void editUserId(final int position) { + final String userId = mUserIdsAdapter.getUserId(position); + final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position); + final boolean isRevokedPending = mUserIdsAdapter.getIsRevokedPending(position); + + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + switch (message.what) { + case EditUserIdDialogFragment.MESSAGE_CHANGE_PRIMARY_USER_ID: + // toggle + if (mEditModeSaveKeyringParcel.mChangePrimaryUserId != null + && mEditModeSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) { + mEditModeSaveKeyringParcel.mChangePrimaryUserId = null; + } else { + mEditModeSaveKeyringParcel.mChangePrimaryUserId = userId; + } + break; + case EditUserIdDialogFragment.MESSAGE_REVOKE: + // toggle + if (mEditModeSaveKeyringParcel.mRevokeUserIds.contains(userId)) { + mEditModeSaveKeyringParcel.mRevokeUserIds.remove(userId); + } else { + mEditModeSaveKeyringParcel.mRevokeUserIds.add(userId); + // not possible to revoke and change to primary user id + if (mEditModeSaveKeyringParcel.mChangePrimaryUserId != null + && mEditModeSaveKeyringParcel.mChangePrimaryUserId.equals(userId)) { + mEditModeSaveKeyringParcel.mChangePrimaryUserId = null; + } + } + break; + } + getLoaderManager().getLoader(LOADER_ID_USER_IDS).forceLoad(); + } + }; + + // Create a new Messenger for the communication back + final Messenger messenger = new Messenger(returnHandler); + + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { + public void run() { + EditUserIdDialogFragment dialogFragment = + EditUserIdDialogFragment.newInstance(messenger, isRevoked, isRevokedPending); + dialogFragment.show(getActivity().getSupportFragmentManager(), "editUserIdDialog"); + } + }); + } + private void showUserIdInfo(final int position) { + final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position); final int isVerified = mUserIdsAdapter.getIsVerified(position); @@ -84,6 +192,30 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements }); } + private void addUserId() { + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) { + Bundle data = message.getData(); + + // add new user id + mUserIdsAddedAdapter.add(data + .getString(AddUserIdDialogFragment.MESSAGE_DATA_USER_ID)); + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(returnHandler); + + // pre-fill out primary name + AddUserIdDialogFragment addUserIdDialog = + AddUserIdDialogFragment.newInstance(messenger, "", true); + + addUserIdDialog.show(getActivity().getSupportFragmentManager(), "addUserIdDialog"); + } + @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); @@ -98,10 +230,19 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements loadData(dataUri); } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (mEditKeyHelper != null) { + mEditKeyHelper.handleActivityResult(requestCode, resultCode, data); + } + + super.onActivityResult(requestCode, resultCode, data); + } + private void loadData(Uri dataUri) { mDataUri = dataUri; - Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); + Log.i(Constants.TAG, "mDataUri: " + mDataUri); mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0); mUserIds.setAdapter(mUserIdsAdapter); @@ -112,27 +253,31 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); } - static final String[] UNIFIED_PROJECTION = new String[]{ - KeyRings._ID, KeyRings.MASTER_KEY_ID, - KeyRings.HAS_ANY_SECRET, KeyRings.IS_REVOKED, KeyRings.IS_EXPIRED, KeyRings.HAS_ENCRYPT + // These are the rows that we will retrieve. + static final String[] PROJECTION = new String[]{ + KeychainContract.KeyRings._ID, + KeychainContract.KeyRings.MASTER_KEY_ID, + KeychainContract.KeyRings.HAS_ANY_SECRET, + KeychainContract.KeyRings.FINGERPRINT, }; - static final int INDEX_UNIFIED_MASTER_KEY_ID = 1; - static final int INDEX_UNIFIED_HAS_ANY_SECRET = 2; - static final int INDEX_UNIFIED_IS_REVOKED = 3; - static final int INDEX_UNIFIED_IS_EXPIRED = 4; - static final int INDEX_UNIFIED_HAS_ENCRYPT = 5; - public Loader<Cursor> onCreateLoader(int id, Bundle args) { - setContentShown(false); + static final int INDEX_MASTER_KEY_ID = 1; + static final int INDEX_HAS_ANY_SECRET = 2; + static final int INDEX_FINGERPRINT = 3; + public Loader<Cursor> onCreateLoader(int id, Bundle args) { switch (id) { case LOADER_ID_UNIFIED: { - Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri); - return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null); + Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); + return new CursorLoader(getActivity(), baseUri, + PROJECTION, null, null, null); } + case LOADER_ID_USER_IDS: { - Uri baseUri = UserPackets.buildUserIdsUri(mDataUri); - return new CursorLoader(getActivity(), baseUri, + setContentShown(false); + + Uri userIdUri = UserPackets.buildUserIdsUri(mDataUri); + return new CursorLoader(getActivity(), userIdUri, UserIdsAdapter.USER_PACKETS_PROJECTION, null, null, null); } @@ -142,31 +287,29 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements } public void onLoadFinished(Loader<Cursor> loader, Cursor data) { - /* TODO better error handling? May cause problems when a key is deleted, - * because the notification triggers faster than the activity closes. - */ - // Avoid NullPointerExceptions... + // Avoid NullPointerExceptions, if we get an empty result set. if (data.getCount() == 0) { 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: { - if (data.moveToFirst()) { - + data.moveToFirst(); - break; - } + mMasterKeyId = data.getLong(INDEX_MASTER_KEY_ID); + mHasSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; + mFingerprint = data.getBlob(INDEX_FINGERPRINT); + break; } - case LOADER_ID_USER_IDS: { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) mUserIdsAdapter.swapCursor(data); + + setContentShown(true); break; } - } - setContentShown(true); } /** @@ -174,11 +317,104 @@ public class ViewKeyAdvUserIdsFragment extends LoaderFragment implements * We need to make sure we are no longer using it. */ public void onLoaderReset(Loader<Cursor> loader) { - switch (loader.getId()) { - case LOADER_ID_USER_IDS: - mUserIdsAdapter.swapCursor(null); - break; + if (loader.getId() != LOADER_ID_USER_IDS) { + return; + } + mUserIdsAdapter.swapCursor(null); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_action_mode_edit: + enterEditMode(); + return true; + default: + return super.onOptionsItemSelected(item); } } + public void enterEditMode() { + FragmentActivity activity = getActivity(); + if (activity == null) { + return; + } + activity.startActionMode(new ActionMode.Callback() { + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + + mEditModeSaveKeyringParcel = new SaveKeyringParcel(mMasterKeyId, mFingerprint); + + mUserIdsAddedAdapter = + new UserIdsAddedAdapter(getActivity(), mEditModeSaveKeyringParcel.mAddUserIds, false); + mUserIdsAddedList.setAdapter(mUserIdsAddedAdapter); + mUserIdsAddedLayout.setVisibility(View.VISIBLE); + mUserIdAddFabLayout.setDisplayedChild(1); + + mUserIdsAdapter.setEditMode(mEditModeSaveKeyringParcel); + getLoaderManager().restartLoader(LOADER_ID_USER_IDS, null, ViewKeyAdvUserIdsFragment.this); + + mode.setTitle(R.string.title_edit_identities); + mode.getMenuInflater().inflate(R.menu.action_edit_uids, menu); + + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + editKey(mode); + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + mEditModeSaveKeyringParcel = null; + mUserIdsAdapter.setEditMode(null); + mUserIdsAddedLayout.setVisibility(View.GONE); + mUserIdAddFabLayout.setDisplayedChild(0); + getLoaderManager().restartLoader(LOADER_ID_USER_IDS, null, ViewKeyAdvUserIdsFragment.this); + } + }); + } + + private void editKey(final ActionMode mode) { + CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult> editKeyCallback + = new CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult>() { + + @Override + public SaveKeyringParcel createOperationInput() { + return mEditModeSaveKeyringParcel; + } + + @Override + public void onCryptoOperationSuccess(EditKeyResult result) { + mode.finish(); + result.createNotify(getActivity()).show(); + } + + @Override + public void onCryptoOperationCancelled() { + mode.finish(); + } + + @Override + public void onCryptoOperationError(EditKeyResult result) { + mode.finish(); + result.createNotify(getActivity()).show(); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; + mEditKeyHelper = new CryptoOperationHelper<>(1, this, editKeyCallback, R.string.progress_saving); + mEditKeyHelper.cryptoOperation(); + } + } 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 f75012731..89dd90ff7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java @@ -24,7 +24,6 @@ import java.util.List; import android.Manifest; import android.annotation.TargetApi; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -47,10 +46,10 @@ import android.transition.TransitionInflater; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnPreDrawListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; +import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; @@ -59,12 +58,14 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.ui.adapter.LinkedIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment; import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment; import org.sufficientlysecure.keychain.ui.linked.LinkedIdViewFragment.OnIdentityLoadedListener; +import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard; import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; @@ -76,7 +77,6 @@ public class ViewKeyFragment extends LoaderFragment implements public static final String ARG_POSTPONE_TYPE = "postpone_type"; private ListView mUserIds; - //private ListView mLinkedSystemContact; enum PostponeType { NONE, LINKED; @@ -86,8 +86,8 @@ public class ViewKeyFragment extends LoaderFragment implements private static final int LOADER_ID_UNIFIED = 0; private static final int LOADER_ID_USER_IDS = 1; - private static final int LOADER_ID_LINKED_CONTACT = 2; - private static final int LOADER_ID_LINKED_IDS = 3; + private static final int LOADER_ID_LINKED_IDS = 2; + private static final int LOADER_ID_LINKED_CONTACT = 3; private static final String LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID = "loader_linked_contact_master_key_id"; @@ -107,6 +107,7 @@ public class ViewKeyFragment extends LoaderFragment implements private ListView mLinkedIds; private CardView mLinkedIdsCard; + private TextView mLinkedIdsEmpty; private byte[] mFingerprint; private TextView mLinkedIdsExpander; @@ -130,11 +131,30 @@ public class ViewKeyFragment extends LoaderFragment implements View view = inflater.inflate(R.layout.view_key_fragment, getContainer()); mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); + Button userIdsEditButton = (Button) view.findViewById(R.id.view_key_card_user_ids_edit); mLinkedIdsCard = (CardView) view.findViewById(R.id.card_linked_ids); - mLinkedIds = (ListView) view.findViewById(R.id.view_key_linked_ids); - mLinkedIdsExpander = (TextView) view.findViewById(R.id.view_key_linked_ids_expander); + mLinkedIdsEmpty = (TextView) view.findViewById(R.id.view_key_linked_ids_empty); + Button linkedIdsAddButton = (Button) view.findViewById(R.id.view_key_card_linked_ids_add); + mSystemContactCard = (CardView) view.findViewById(R.id.linked_system_contact_card); + mSystemContactLayout = (LinearLayout) view.findViewById(R.id.system_contact_layout); + mSystemContactName = (TextView) view.findViewById(R.id.system_contact_name); + mSystemContactPicture = (ImageView) view.findViewById(R.id.system_contact_picture); + + userIdsEditButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + editIdentities(mDataUri); + } + }); + + linkedIdsAddButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + addLinkedIdentity(mDataUri); + } + }); mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override @@ -149,26 +169,34 @@ public class ViewKeyFragment extends LoaderFragment implements } }); - mSystemContactCard = (CardView) view.findViewById(R.id.linked_system_contact_card); - mSystemContactLayout = (LinearLayout) view.findViewById(R.id.system_contact_layout); - mSystemContactName = (TextView) view.findViewById(R.id.system_contact_name); - mSystemContactPicture = (ImageView) view.findViewById(R.id.system_contact_picture); - return root; } + private void editIdentities(Uri dataUri) { + Intent editIntent = new Intent(getActivity(), EditIdentitiesActivity.class); + editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri)); + startActivityForResult(editIntent, 0); + } + + private void addLinkedIdentity(Uri dataUri) { + Intent intent = new Intent(getActivity(), LinkedIdWizard.class); + intent.setData(dataUri); + startActivity(intent); + getActivity().finish(); + } + private void showLinkedId(final int position) { final LinkedIdViewFragment frag; try { frag = mLinkedIdsAdapter.getLinkedIdFragment(mDataUri, position, mFingerprint); } catch (IOException e) { - e.printStackTrace(); + Log.e(Constants.TAG, "IOException", e); return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Transition trans = TransitionInflater.from(getActivity()) - .inflateTransition(R.transition.linked_id_card_trans); + .inflateTransition(R.transition.linked_id_card_trans); // setSharedElementReturnTransition(trans); setExitTransition(new Fade()); frag.setSharedElementEnterTransition(trans); @@ -221,7 +249,7 @@ public class ViewKeyFragment extends LoaderFragment implements */ private void loadLinkedSystemContact(final long contactId) { // contact doesn't exist, stop - if(contactId == -1) return; + if (contactId == -1) return; final Context context = mSystemContactName.getContext(); ContactHelper contactHelper = new ContactHelper(context); @@ -298,7 +326,17 @@ public class ViewKeyFragment extends LoaderFragment implements loadData(dataUri); } - // These are the rows that we will retrieve. + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + // if a result has been returned, display a notify + if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { + OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); + result.createNotify(getActivity()).show(); + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } + static final String[] UNIFIED_PROJECTION = new String[]{ KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, @@ -325,7 +363,7 @@ public class ViewKeyFragment extends LoaderFragment implements @SuppressWarnings("unused") static final int INDEX_HAS_ENCRYPT = 8; - private static final String[] RAWCONTACT_PROJECTION = { + private static final String[] RAW_CONTACT_PROJECTION = { ContactsContract.RawContacts.CONTACT_ID }; @@ -359,29 +397,28 @@ public class ViewKeyFragment extends LoaderFragment implements return LinkedIdsAdapter.createLoader(getActivity(), mDataUri); } - //we need a separate loader for linked contact to ensure refreshing on verification case LOADER_ID_LINKED_CONTACT: { - //passed in args to explicitly specify their need + // we need a separate loader for linked contact + // to ensure refreshing on verification + + // passed in args to explicitly specify their need long masterKeyId = args.getLong(LOADER_EXTRA_LINKED_CONTACT_MASTER_KEY_ID); boolean isSecret = args.getBoolean(LOADER_EXTRA_LINKED_CONTACT_IS_SECRET); - Uri baseUri; - if (isSecret) - baseUri = ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI; - else - baseUri = ContactsContract.RawContacts.CONTENT_URI; + Uri baseUri = isSecret ? ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI : + ContactsContract.RawContacts.CONTENT_URI; return new CursorLoader( getActivity(), baseUri, - RAWCONTACT_PROJECTION, + RAW_CONTACT_PROJECTION, ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + ContactsContract.RawContacts.SOURCE_ID + "=? AND " + ContactsContract.RawContacts.DELETED + "=?", - new String[]{//"0" for "not deleted" + new String[]{ Constants.ACCOUNT_TYPE, Long.toString(masterKeyId), - "0" + "0" // "0" for "not deleted" }, null); } @@ -396,47 +433,46 @@ public class ViewKeyFragment extends LoaderFragment implements /* TODO better error handling? May cause problems when a key is deleted, * because the notification triggers faster than the activity closes. */ - // Avoid NullPointerExceptions... - if (data == null || data.getCount() == 0) { + if (data == 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: { - if (data.moveToFirst()) { + if (data.getCount() == 1 && data.moveToFirst()) { mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0; mFingerprint = data.getBlob(INDEX_FINGERPRINT); long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID); - // load user ids after we know if it's a secret key - mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !mIsSecret, null); - mUserIds.setAdapter(mUserIdsAdapter); - getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); - - if (Preferences.getPreferences(getActivity()).getExperimentalEnableLinkedIdentities()) { - mLinkedIdsAdapter = - new LinkedIdsAdapter(getActivity(), null, 0, mIsSecret, mLinkedIdsExpander); - mLinkedIds.setAdapter(mLinkedIdsAdapter); - getLoaderManager().initLoader(LOADER_ID_LINKED_IDS, null, this); - } - + // init other things after we know if it's a secret key + initUserIds(mIsSecret); + initLinkedIds(mIsSecret); initLinkedContactLoader(masterKeyId, mIsSecret); - - break; + initCardButtonsVisibility(mIsSecret); } + break; } case LOADER_ID_USER_IDS: { setContentShown(true, false); mUserIdsAdapter.swapCursor(data); + break; } case LOADER_ID_LINKED_IDS: { mLinkedIdsAdapter.swapCursor(data); - mLinkedIdsCard.setVisibility(mLinkedIdsAdapter.getCount() > 0 ? View.VISIBLE : View.GONE); + + if (mIsSecret) { + mLinkedIdsCard.setVisibility(View.VISIBLE); + mLinkedIdsEmpty.setVisibility(mLinkedIdsAdapter.getCount() > 0 ? View.GONE : View.VISIBLE); + } else { + mLinkedIdsCard.setVisibility(mLinkedIdsAdapter.getCount() > 0 ? View.VISIBLE : View.GONE); + mLinkedIdsEmpty.setVisibility(View.GONE); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mPostponeType == PostponeType.LINKED) { mLinkedIdsCard.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() { @TargetApi(VERSION_CODES.LOLLIPOP) @@ -452,13 +488,27 @@ public class ViewKeyFragment extends LoaderFragment implements } case LOADER_ID_LINKED_CONTACT: { - if (data.moveToFirst()) {// if we have a linked contact + if (data.moveToFirst()) { // if we have a linked contact long contactId = data.getLong(INDEX_CONTACT_ID); loadLinkedSystemContact(contactId); } break; } + } + } + private void initUserIds(boolean isSecret) { + mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, !isSecret, null); + mUserIds.setAdapter(mUserIdsAdapter); + getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); + } + + private void initLinkedIds(boolean isSecret) { + if (Preferences.getPreferences(getActivity()).getExperimentalEnableLinkedIdentities()) { + mLinkedIdsAdapter = + new LinkedIdsAdapter(getActivity(), null, 0, isSecret, mLinkedIdsExpander); + mLinkedIds.setAdapter(mLinkedIdsAdapter); + getLoaderManager().initLoader(LOADER_ID_LINKED_IDS, null, this); } } @@ -478,6 +528,20 @@ public class ViewKeyFragment extends LoaderFragment implements getLoaderManager().initLoader(LOADER_ID_LINKED_CONTACT, linkedContactData, this); } + private void initCardButtonsVisibility(boolean isSecret) { + LinearLayout buttonsUserIdsLayout = + (LinearLayout) getActivity().findViewById(R.id.view_key_card_user_ids_buttons); + LinearLayout buttonsLinkedIdsLayout = + (LinearLayout) getActivity().findViewById(R.id.view_key_card_linked_ids_buttons); + if (isSecret) { + buttonsUserIdsLayout.setVisibility(View.VISIBLE); + buttonsLinkedIdsLayout.setVisibility(View.VISIBLE); + } else { + buttonsUserIdsLayout.setVisibility(View.GONE); + buttonsLinkedIdsLayout.setVisibility(View.GONE); + } + } + /** * This is called when the last Cursor provided to onLoadFinished() above is about to be closed. * We need to make sure we are no longer using it. @@ -490,7 +554,6 @@ public class ViewKeyFragment extends LoaderFragment implements break; } case LOADER_ID_LINKED_IDS: { - mLinkedIdsCard.setVisibility(View.GONE); mLinkedIdsAdapter.swapCursor(null); break; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java index 24f5f04a1..84608f2dc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java @@ -22,6 +22,7 @@ import android.content.res.ColorStateList; import android.database.Cursor; import android.graphics.PorterDuff; import android.graphics.Typeface; +import android.support.annotation.Nullable; import android.support.v4.widget.CursorAdapter; import android.text.Spannable; import android.text.SpannableString; @@ -49,7 +50,7 @@ public class SubkeysAdapter extends CursorAdapter { private LayoutInflater mInflater; private SaveKeyringParcel mSaveKeyringParcel; - private boolean hasAnySecret; + private boolean mHasAnySecret; private ColorStateList mDefaultTextColor; public static final String[] SUBKEYS_PROJECTION = new String[]{ @@ -85,16 +86,10 @@ public class SubkeysAdapter extends CursorAdapter { private static final int INDEX_EXPIRY = 13; private static final int INDEX_FINGERPRINT = 14; - public SubkeysAdapter(Context context, Cursor c, int flags, - SaveKeyringParcel saveKeyringParcel) { + public SubkeysAdapter(Context context, Cursor c, int flags) { super(context, c, flags); mInflater = LayoutInflater.from(context); - mSaveKeyringParcel = saveKeyringParcel; - } - - public SubkeysAdapter(Context context, Cursor c, int flags) { - this(context, c, flags, null); } public long getKeyId(int position) { @@ -133,12 +128,12 @@ public class SubkeysAdapter extends CursorAdapter { @Override public Cursor swapCursor(Cursor newCursor) { - hasAnySecret = false; + mHasAnySecret = false; if (newCursor != null && newCursor.moveToFirst()) { do { SecretKeyType hasSecret = SecretKeyType.fromNum(newCursor.getInt(INDEX_HAS_SECRET)); if (hasSecret.isUsable()) { - hasAnySecret = true; + mHasAnySecret = true; break; } } while (newCursor.moveToNext()); @@ -354,4 +349,18 @@ public class SubkeysAdapter extends CursorAdapter { } } + /** Set this adapter into edit mode. This mode displays additional info for + * each item from a supplied SaveKeyringParcel reference. + * + * Note that it is up to the caller to reload the underlying cursor after + * updating the SaveKeyringParcel! + * + * @see SaveKeyringParcel + * + * @param saveKeyringParcel The parcel to get info from, or null to leave edit mode. + */ + public void setEditMode(@Nullable SaveKeyringParcel saveKeyringParcel) { + mSaveKeyringParcel = saveKeyringParcel; + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java index e0abaf4b0..31f8513fa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java @@ -1,5 +1,6 @@ package org.sufficientlysecure.keychain.ui.adapter; + import android.content.Context; import android.database.Cursor; import android.support.v4.widget.CursorAdapter; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java index 0f4312dad..7dee90b4e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java @@ -23,12 +23,14 @@ import android.content.Context; import android.database.Cursor; import android.graphics.Typeface; import android.net.Uri; +import android.support.annotation.Nullable; import android.support.v4.content.CursorLoader; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.KeyRing; @@ -52,10 +54,6 @@ public class UserIdsAdapter extends UserAttributesAdapter { mShowStatusImages = showStatusImages; } - public UserIdsAdapter(Context context, Cursor c, int flags, SaveKeyringParcel saveKeyringParcel) { - this(context, c, flags, true, saveKeyringParcel); - } - public UserIdsAdapter(Context context, Cursor c, int flags) { this(context, c, flags, true, null); } @@ -66,7 +64,7 @@ public class UserIdsAdapter extends UserAttributesAdapter { TextView vAddress = (TextView) view.findViewById(R.id.user_id_item_address); TextView vComment = (TextView) view.findViewById(R.id.user_id_item_comment); ImageView vVerified = (ImageView) view.findViewById(R.id.user_id_item_certified); - View vVerifiedLayout = view.findViewById(R.id.user_id_item_certified_layout); + ViewAnimator vVerifiedLayout = (ViewAnimator) view.findViewById(R.id.user_id_icon_animator); ImageView vEditImage = (ImageView) view.findViewById(R.id.user_id_item_edit_image); ImageView vDeleteButton = (ImageView) view.findViewById(R.id.user_id_item_delete_button); vDeleteButton.setVisibility(View.GONE); // not used @@ -114,16 +112,9 @@ public class UserIdsAdapter extends UserAttributesAdapter { } } - vEditImage.setVisibility(View.VISIBLE); - vVerifiedLayout.setVisibility(View.GONE); + vVerifiedLayout.setDisplayedChild(2); } else { - vEditImage.setVisibility(View.GONE); - - if (mShowStatusImages) { - vVerifiedLayout.setVisibility(View.VISIBLE); - } else { - vVerifiedLayout.setVisibility(View.GONE); - } + vVerifiedLayout.setDisplayedChild(mShowStatusImages ? 1 : 0); } if (isRevoked) { @@ -177,6 +168,20 @@ public class UserIdsAdapter extends UserAttributesAdapter { return isRevokedPending; } + /** Set this adapter into edit mode. This mode displays additional info for + * each item from a supplied SaveKeyringParcel reference. + * + * Note that it is up to the caller to reload the underlying cursor after + * updating the SaveKeyringParcel! + * + * @see SaveKeyringParcel + * + * @param saveKeyringParcel The parcel to get info from, or null to leave edit mode. + */ + public void setEditMode(@Nullable SaveKeyringParcel saveKeyringParcel) { + mSaveKeyringParcel = saveKeyringParcel; + } + @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return mInflater.inflate(R.layout.view_key_adv_user_id_item, null); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAddedAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAddedAdapter.java index c7197b46d..b1892b27e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAddedAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAddedAdapter.java @@ -72,7 +72,7 @@ public class UserIdsAddedAdapter extends ArrayAdapter<String> { holder.vDelete.setVisibility(View.VISIBLE); // always visible // not used: - View certifiedLayout = convertView.findViewById(R.id.user_id_item_certified_layout); + View certifiedLayout = convertView.findViewById(R.id.user_id_icon_animator); ImageView editImage = (ImageView) convertView.findViewById(R.id.user_id_item_edit_image); certifiedLayout.setVisibility(View.GONE); editImage.setVisibility(View.GONE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsSelectableAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsSelectableAdapter.java index 947d911c3..3cc2e2044 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsSelectableAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsSelectableAdapter.java @@ -18,8 +18,8 @@ public class UserIdsSelectableAdapter extends UserIdsAdapter implements AdapterV private final ArrayList<Boolean> mCheckStates; - public UserIdsSelectableAdapter(Context context, Cursor c, int flags, SaveKeyringParcel saveKeyringParcel) { - super(context, c, flags, saveKeyringParcel); + public UserIdsSelectableAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); mCheckStates = new ArrayList<Boolean>(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java index bc82feb70..4500ccd24 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java @@ -48,6 +48,7 @@ import org.sufficientlysecure.keychain.util.Log; public class AddUserIdDialogFragment extends DialogFragment implements OnEditorActionListener { private static final String ARG_MESSENGER = "messenger"; private static final String ARG_NAME = "name"; + private static final String ARG_ALLOW_COMMENT = "allow_comment"; public static final int MESSAGE_OKAY = 1; public static final int MESSAGE_CANCEL = 2; @@ -59,12 +60,14 @@ public class AddUserIdDialogFragment extends DialogFragment implements OnEditorA private EmailEditText mEmail; private EditText mComment; - public static AddUserIdDialogFragment newInstance(Messenger messenger, String predefinedName) { + public static AddUserIdDialogFragment newInstance(Messenger messenger, String predefinedName, + boolean allowComment) { AddUserIdDialogFragment frag = new AddUserIdDialogFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_MESSENGER, messenger); args.putString(ARG_NAME, predefinedName); + args.putBoolean(ARG_ALLOW_COMMENT, allowComment); frag.setArguments(args); return frag; @@ -78,6 +81,7 @@ public class AddUserIdDialogFragment extends DialogFragment implements OnEditorA final Activity activity = getActivity(); mMessenger = getArguments().getParcelable(ARG_MESSENGER); String predefinedName = getArguments().getString(ARG_NAME); + boolean allowComment = getArguments().getBoolean(ARG_ALLOW_COMMENT); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); @@ -91,6 +95,12 @@ public class AddUserIdDialogFragment extends DialogFragment implements OnEditorA mEmail = (EmailEditText) view.findViewById(R.id.add_user_id_address); mComment = (EditText) view.findViewById(R.id.add_user_id_comment); + if (allowComment) { + mComment.setVisibility(View.VISIBLE); + } else { + mComment.setVisibility(View.GONE); + } + mName.setText(predefinedName); alert.setPositiveButton(android.R.string.ok, new OnClickListener() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ContentDescriptionHint.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ContentDescriptionHint.java index 8e45a20e9..758e63eb1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ContentDescriptionHint.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ContentDescriptionHint.java @@ -1,8 +1,3 @@ -package org.sufficientlysecure.keychain.ui.util; - -/** - * Created by rohan on 20/9/15. - */ /* * Copyright 2012 Google Inc. * @@ -19,14 +14,18 @@ package org.sufficientlysecure.keychain.ui.util; * limitations under the License. */ +package org.sufficientlysecure.keychain.ui.util; + import android.content.Context; import android.graphics.Rect; import android.text.TextUtils; import android.view.Gravity; import android.view.View; import android.widget.Toast; + public class ContentDescriptionHint { private static final int ESTIMATED_TOAST_HEIGHT_DIPS = 48; + public static void setup(View view) { view.setOnLongClickListener(new View.OnLongClickListener() { @Override diff --git a/OpenKeychain/src/main/res/anim/fab_slide_down.xml b/OpenKeychain/src/main/res/anim/fab_slide_down.xml new file mode 100644 index 000000000..e51ac0cb9 --- /dev/null +++ b/OpenKeychain/src/main/res/anim/fab_slide_down.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<translate xmlns:android="http://schemas.android.com/apk/res/android" + android:fromYDelta="0" android:toYDelta="300" + android:interpolator="@android:anim/anticipate_interpolator" + android:duration="250" +/> diff --git a/OpenKeychain/src/main/res/anim/fab_slide_in.xml b/OpenKeychain/src/main/res/anim/fab_slide_in.xml new file mode 100644 index 000000000..cb38f81e8 --- /dev/null +++ b/OpenKeychain/src/main/res/anim/fab_slide_in.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<translate xmlns:android="http://schemas.android.com/apk/res/android" + android:fromYDelta="300" android:toYDelta="0" + android:interpolator="@android:anim/overshoot_interpolator" + android:duration="250" + android:startOffset="100" +/>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/edit_icon.xml b/OpenKeychain/src/main/res/layout/edit_icon.xml new file mode 100644 index 000000000..f3af5a3dc --- /dev/null +++ b/OpenKeychain/src/main/res/layout/edit_icon.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> + +<ImageView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:padding="0dp" + android:src="@drawable/ic_mode_edit_white_24dp" + style="@style/Widget.AppCompat.ActionButton" /> diff --git a/OpenKeychain/src/main/res/layout/edit_identities_activity.xml b/OpenKeychain/src/main/res/layout/edit_identities_activity.xml new file mode 100644 index 000000000..8505f789a --- /dev/null +++ b/OpenKeychain/src/main/res/layout/edit_identities_activity.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".ui.EditIdentitiesActivity"> + + <include + android:id="@+id/toolbar_include" + layout="@layout/toolbar_standalone" /> + + <LinearLayout + android:layout_below="@id/toolbar_include" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <include layout="@layout/notify_area" /> + + <FrameLayout + android:id="@+id/edit_key_fragment_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" /> + + </LinearLayout> +</RelativeLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/edit_identities_fragment.xml b/OpenKeychain/src/main/res/layout/edit_identities_fragment.xml new file mode 100644 index 000000000..1b6fcf5df --- /dev/null +++ b/OpenKeychain/src/main/res/layout/edit_identities_fragment.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:card_view="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".ui.EditIdentitiesActivity"> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingBottom="96dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:paddingTop="16dp"> + + <CheckBox + android:id="@+id/edit_identities_upload_checkbox" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:checked="true" + android:text="@string/label_send_key" + android:paddingTop="16dp" + android:paddingBottom="16dp"/> + + <android.support.v7.widget.CardView + android:id="@+id/edit_identities_user_ids_card" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + card_view:cardBackgroundColor="?attr/colorCardViewBackground" + card_view:cardCornerRadius="4dp" + card_view:cardElevation="2dp" + card_view:cardUseCompatPadding="true"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + style="@style/CardViewHeader" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/section_user_ids" /> + + <org.sufficientlysecure.keychain.ui.widget.FixedListView + android:id="@+id/edit_identities_user_ids" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:background="?android:attr/listDivider" /> + + <org.sufficientlysecure.keychain.ui.widget.FixedListView + android:id="@+id/edit_identities_user_ids_added" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + </android.support.v7.widget.CardView> + + </LinearLayout> + + </ScrollView> + + <android.support.design.widget.FloatingActionButton + android:id="@+id/edit_identities_add_user_id" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_alignParentRight="true" + android:layout_marginBottom="24dp" + android:layout_marginRight="24dp" + android:src="@drawable/ic_add_white_24dp" /> + +</RelativeLayout> + + diff --git a/OpenKeychain/src/main/res/layout/linked_id_view_fragment.xml b/OpenKeychain/src/main/res/layout/linked_id_view_fragment.xml index 8b275fcef..14db368bf 100644 --- a/OpenKeychain/src/main/res/layout/linked_id_view_fragment.xml +++ b/OpenKeychain/src/main/res/layout/linked_id_view_fragment.xml @@ -144,14 +144,14 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:gravity="right|end"> + android:gravity="left|start"> <Button android:id="@+id/button_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/linked_button_view" - android:textColor="@color/link_text_material_light" + android:textColor="@color/card_view_button" style="?android:attr/borderlessButtonStyle" /> @@ -166,21 +166,21 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/linked_button_verify" - android:textColor="@color/link_text_material_light" + android:textColor="@color/card_view_button" style="?android:attr/borderlessButtonStyle" /> <Button android:id="@+id/button_retry" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/linked_button_retry" - android:textColor="@color/link_text_material_light" + android:textColor="@color/card_view_button" style="?android:attr/borderlessButtonStyle" /> <Button android:id="@+id/button_confirm" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/linked_button_confirm" - android:textColor="@color/link_text_material_light" + android:textColor="@color/card_view_button" style="?android:attr/borderlessButtonStyle" /> </ViewAnimator> diff --git a/OpenKeychain/src/main/res/layout/view_key_adv_main_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_adv_main_fragment.xml deleted file mode 100644 index 3347a514c..000000000 --- a/OpenKeychain/src/main/res/layout/view_key_adv_main_fragment.xml +++ /dev/null @@ -1,33 +0,0 @@ -<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <!-- focusable and related properties to workaround http://stackoverflow.com/q/16182331--> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:focusable="true" - android:focusableInTouchMode="true" - android:descendantFocusability="beforeDescendants" - android:orientation="vertical" - android:paddingLeft="16dp" - android:paddingRight="16dp"> - - <TextView - style="@style/SectionHeader" - android:layout_width="wrap_content" - android:layout_height="0dp" - android:layout_marginTop="8dp" - android:text="@string/section_user_ids" - android:layout_weight="1" /> - - <org.sufficientlysecure.keychain.ui.widget.FixedListView - android:id="@+id/view_key_user_ids" - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_marginBottom="4dp" - android:layout_weight="1" /> - - </LinearLayout> - -</ScrollView> diff --git a/OpenKeychain/src/main/res/layout/view_key_adv_subkeys_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_adv_subkeys_fragment.xml index 62fd113f9..4679d5be9 100644 --- a/OpenKeychain/src/main/res/layout/view_key_adv_subkeys_fragment.xml +++ b/OpenKeychain/src/main/res/layout/view_key_adv_subkeys_fragment.xml @@ -1,34 +1,88 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" +android:layout_width="match_parent" +android:layout_height="match_parent" +xmlns:tools="http://schemas.android.com/tools"> + +<ScrollView android:layout_width="match_parent" - android:layout_height="match_parent" - android:focusable="true" - android:focusableInTouchMode="true" - android:descendantFocusability="beforeDescendants" - android:orientation="vertical"> - - <TextView - style="@style/SectionHeader" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="4dp" - android:layout_marginTop="8dp" - android:layout_marginLeft="16dp" - android:layout_marginRight="16dp" - android:text="@string/section_keys" /> + android:layout_height="match_parent"> - <FrameLayout + <!-- focusable and related properties to workaround http://stackoverflow.com/q/16182331--> + <LinearLayout android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="wrap_content" + android:focusable="true" + android:focusableInTouchMode="true" + android:descendantFocusability="beforeDescendants" + android:orientation="vertical" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + + <TextView + style="@style/SectionHeader" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_marginTop="8dp" + android:text="@string/section_keys" + android:layout_weight="1" /> - <ListView - android:id="@+id/keys" + <org.sufficientlysecure.keychain.ui.widget.FixedListView + android:id="@+id/view_key_subkeys" android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_marginBottom="4dp" + android:layout_weight="1" + android:scrollbarStyle="outsideOverlay" /> + + <LinearLayout + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:scrollbarStyle="outsideOverlay" - android:paddingLeft="16dp" - android:paddingRight="16dp" - android:layout_marginBottom="8dp" /> - </FrameLayout> + android:orientation="vertical" + android:id="@+id/view_key_subkeys_add_layout" + android:visibility="gone" + tools:visibility="visible"> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:background="?android:attr/listDivider" /> + + <org.sufficientlysecure.keychain.ui.widget.FixedListView + android:id="@+id/view_key_subkeys_added" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + </LinearLayout> + +</ScrollView> + +<ViewAnimator + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" + android:layout_alignParentBottom="true" + android:id="@+id/view_key_subkey_fab_layout" + android:inAnimation="@anim/fab_slide_in" + android:outAnimation="@anim/fab_slide_down"> + + <Space + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <android.support.design.widget.FloatingActionButton + android:id="@+id/view_key_subkey_fab" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="24dp" + android:src="@drawable/ic_add_white_24dp" + android:visibility="invisible" + android:layout_gravity="bottom" + tools:visibility="visible" /> + +</ViewAnimator> -</LinearLayout>
\ No newline at end of file +</RelativeLayout> diff --git a/OpenKeychain/src/main/res/layout/view_key_adv_user_id_item.xml b/OpenKeychain/src/main/res/layout/view_key_adv_user_id_item.xml index a9ebe43d3..e2899f806 100644 --- a/OpenKeychain/src/main/res/layout/view_key_adv_user_id_item.xml +++ b/OpenKeychain/src/main/res/layout/view_key_adv_user_id_item.xml @@ -2,6 +2,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" + xmlns:custom="http://schemas.android.com/apk/res-auto" android:minHeight="?android:attr/listPreferredItemHeight" android:orientation="horizontal" android:singleLine="true"> @@ -40,16 +41,19 @@ </LinearLayout> - - <LinearLayout - android:id="@+id/user_id_item_certified_layout" + <org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator + android:id="@+id/user_id_icon_animator" android:layout_width="22dp" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" - android:layout_gravity="center_vertical" - android:orientation="vertical"> + android:orientation="vertical" + custom:initialView="1"> + + <Space + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> <ImageView android:id="@+id/user_id_item_certified" @@ -58,15 +62,14 @@ android:src="@drawable/status_signature_unverified_cutout_24dp" android:layout_gravity="center_horizontal" /> - </LinearLayout> + <ImageView + android:id="@+id/user_id_item_edit_image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_mode_edit_grey_24dp" + android:layout_gravity="center_vertical" /> - <ImageView - android:id="@+id/user_id_item_edit_image" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:src="@drawable/ic_mode_edit_grey_24dp" - android:padding="8dp" - android:layout_gravity="center_vertical" /> + </org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator> <ImageButton android:id="@+id/user_id_item_delete_button" diff --git a/OpenKeychain/src/main/res/layout/view_key_adv_user_ids_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_adv_user_ids_fragment.xml new file mode 100644 index 000000000..b6399b057 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/view_key_adv_user_ids_fragment.xml @@ -0,0 +1,86 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:tools="http://schemas.android.com/tools"> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <!-- focusable and related properties to workaround http://stackoverflow.com/q/16182331--> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:focusable="true" + android:focusableInTouchMode="true" + android:descendantFocusability="beforeDescendants" + android:orientation="vertical" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + + <TextView + style="@style/SectionHeader" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_marginTop="8dp" + android:text="@string/section_user_ids" + android:layout_weight="1" /> + + <org.sufficientlysecure.keychain.ui.widget.FixedListView + android:id="@+id/view_key_user_ids" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_marginBottom="4dp" + android:layout_weight="1" /> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:id="@+id/view_key_user_ids_add_layout" + android:visibility="gone" + tools:visibility="visible"> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:background="?android:attr/listDivider" /> + + <org.sufficientlysecure.keychain.ui.widget.FixedListView + android:id="@+id/view_key_user_ids_added" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + </LinearLayout> + + </ScrollView> + + <ViewAnimator + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" + android:layout_alignParentBottom="true" + android:id="@+id/view_key_subkey_fab_layout" + android:inAnimation="@anim/fab_slide_in" + android:outAnimation="@anim/fab_slide_down"> + + <Space + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <android.support.design.widget.FloatingActionButton + android:id="@+id/view_key_subkey_fab" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="24dp" + android:src="@drawable/ic_add_white_24dp" + android:visibility="invisible" + android:layout_gravity="bottom" + tools:visibility="visible" /> + + </ViewAnimator> + +</RelativeLayout> diff --git a/OpenKeychain/src/main/res/layout/view_key_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_fragment.xml index 8715945bf..aa3a8e8da 100644 --- a/OpenKeychain/src/main/res/layout/view_key_fragment.xml +++ b/OpenKeychain/src/main/res/layout/view_key_fragment.xml @@ -1,8 +1,8 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:paddingBottom="16dp" android:paddingLeft="16dp" @@ -11,16 +11,16 @@ <android.support.v7.widget.CardView android:id="@+id/card_linked_ids" - android:transitionName="card_linked_ids" - android:layout_gravity="center" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_gravity="center" + android:transitionName="card_linked_ids" android:visibility="gone" - tools:visibility="visible" card_view:cardBackgroundColor="?attr/colorCardViewBackground" + card_view:cardCornerRadius="4dp" card_view:cardElevation="2dp" card_view:cardUseCompatPadding="true" - card_view:cardCornerRadius="4dp"> + tools:visibility="visible"> <LinearLayout android:layout_width="match_parent" @@ -40,31 +40,60 @@ android:layout_marginBottom="4dp" /> <TextView + android:id="@+id/view_key_linked_ids_empty" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" + android:gravity="center" + android:text="@string/linked_empty" /> + + <TextView android:id="@+id/view_key_linked_ids_expander" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_marginTop="4dp" android:layout_marginBottom="4dp" - android:gravity="center_vertical" - android:drawableTop="@drawable/divider" - android:drawableRight="@drawable/ic_expand_more_black_24dp" + android:layout_marginTop="4dp" + android:background="?android:selectableItemBackground" + android:clickable="true" android:drawableEnd="@drawable/ic_expand_more_black_24dp" android:drawablePadding="3dp" - android:clickable="true" - android:text="@string/linked_ids_more_unknown" + android:drawableRight="@drawable/ic_expand_more_black_24dp" + android:drawableTop="@drawable/divider" + android:gravity="center_vertical" android:paddingLeft="8dp" android:paddingRight="8dp" - android:background="?android:selectableItemBackground" + android:text="@string/linked_ids_more_unknown" android:visibility="gone" - tools:visibility="visible" - /> + tools:visibility="visible" /> + + <LinearLayout + android:id="@+id/view_key_card_linked_ids_buttons" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="left|start" + android:orientation="vertical"> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:background="?android:attr/listDivider" /> + + <Button + android:id="@+id/view_key_card_linked_ids_add" + style="?android:attr/borderlessButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/menu_linked_add_identity" + android:textColor="@color/card_view_button" /> + + </LinearLayout> </LinearLayout> </android.support.v7.widget.CardView> <android.support.v7.widget.CardView - android:id="@+id/card_view" + android:id="@+id/view_key_card_user_ids" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" @@ -78,17 +107,46 @@ android:layout_height="wrap_content" android:orientation="vertical"> - <TextView - style="@style/CardViewHeader" - android:layout_width="wrap_content" + <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/section_user_ids" /> + android:orientation="vertical"> - <org.sufficientlysecure.keychain.ui.widget.FixedListView - android:id="@+id/view_key_user_ids" + <TextView + style="@style/CardViewHeader" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/section_user_ids" /> + + <org.sufficientlysecure.keychain.ui.widget.FixedListView + android:id="@+id/view_key_user_ids" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="4dp" /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/view_key_card_user_ids_buttons" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="4dp" /> + android:gravity="left|start" + android:orientation="vertical"> + + <View + android:layout_width="match_parent" + android:layout_height="1dip" + android:background="?android:attr/listDivider" /> + + <Button + android:id="@+id/view_key_card_user_ids_edit" + style="?android:attr/borderlessButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/key_view_action_edit" + android:textColor="@color/card_view_button" /> + + </LinearLayout> </LinearLayout> diff --git a/OpenKeychain/src/main/res/layout/view_key_yubikey.xml b/OpenKeychain/src/main/res/layout/view_key_yubikey.xml index 57f93493d..5e2bfbfe6 100644 --- a/OpenKeychain/src/main/res/layout/view_key_yubikey.xml +++ b/OpenKeychain/src/main/res/layout/view_key_yubikey.xml @@ -89,7 +89,7 @@ android:layout_height="wrap_content" android:layout_gravity="right|end" android:text="@string/button_bind_key" - android:textColor="@color/link_text_material_light" + android:textColor="@color/card_view_button" style="?android:attr/borderlessButtonStyle" android:visibility="gone" /> diff --git a/OpenKeychain/src/main/res/menu/action_edit_uids.xml b/OpenKeychain/src/main/res/menu/action_edit_uids.xml new file mode 100644 index 000000000..da486afff --- /dev/null +++ b/OpenKeychain/src/main/res/menu/action_edit_uids.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <item + android:id="@+id/menu_uids_save" + android:title="@string/menu_uids_save" + app:showAsAction="always" /> + +</menu>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/menu/action_mode_edit.xml b/OpenKeychain/src/main/res/menu/action_mode_edit.xml new file mode 100644 index 000000000..edbebac8b --- /dev/null +++ b/OpenKeychain/src/main/res/menu/action_mode_edit.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <item + android:id="@+id/menu_action_mode_edit" + android:icon="@drawable/ic_mode_edit_white_24dp" + android:title="@string/key_view_action_edit" + app:showAsAction="always" + /> + +</menu>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/menu/key_view.xml b/OpenKeychain/src/main/res/menu/key_view.xml index c0adfcd6f..0e5a43007 100644 --- a/OpenKeychain/src/main/res/menu/key_view.xml +++ b/OpenKeychain/src/main/res/menu/key_view.xml @@ -3,49 +3,42 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> <item - android:id="@+id/menu_key_view_edit" - android:icon="@drawable/ic_mode_edit_white_24dp" - android:visible="false" - app:showAsAction="always" - android:title="@string/key_view_action_edit" /> - - <item android:id="@+id/menu_key_view_refresh" android:icon="@drawable/ic_refresh_white_24dp" - app:showAsAction="always" - android:title="@string/key_view_action_update" /> + android:title="@string/key_view_action_update" + app:showAsAction="always" /> + + <item + android:id="@+id/menu_key_change_password" + android:title="@string/menu_change_password" + app:showAsAction="never" /> <item android:id="@+id/menu_key_view_backup" - app:showAsAction="never" - android:title="@string/menu_export_key" /> + android:title="@string/menu_export_key" + app:showAsAction="never" /> <item android:id="@+id/menu_key_view_delete" android:icon="@drawable/ic_delete_grey_24dp" - app:showAsAction="never" - android:title="@string/menu_delete_key" /> + android:title="@string/menu_delete_key" + app:showAsAction="never" /> <item android:id="@+id/menu_key_view_advanced" - app:showAsAction="never" - android:title="@string/menu_advanced" /> + android:title="@string/menu_advanced" + app:showAsAction="never" /> <item android:id="@+id/menu_key_view_certify_fingerprint" - app:showAsAction="never" + android:title="@string/menu_certify_fingerprint" android:visible="false" - android:title="@string/menu_certify_fingerprint" /> + app:showAsAction="never" /> <item android:id="@+id/menu_key_view_certify_fingerprint_word" - app:showAsAction="never" + android:title="@string/menu_certify_fingerprint_phrases" android:visible="false" - android:title="@string/menu_certify_fingerprint_phrases" /> - - <item - android:id="@+id/menu_key_view_add_linked_identity" - app:showAsAction="never" - android:title="@string/menu_linked_add_identity" /> + app:showAsAction="never" /> </menu> diff --git a/OpenKeychain/src/main/res/values-cs/strings.xml b/OpenKeychain/src/main/res/values-cs/strings.xml index 25f6803d0..6b080e665 100644 --- a/OpenKeychain/src/main/res/values-cs/strings.xml +++ b/OpenKeychain/src/main/res/values-cs/strings.xml @@ -385,7 +385,7 @@ <string name="key_list_empty_text1">Žádný klíč nenalezen!</string> <string name="key_list_filter_show_all">Zobrazit všechny klíče</string> <!--Key view--> - <string name="key_view_action_edit">Editovat klíč</string> + <string name="key_view_action_edit_ids">Editovat klíč</string> <string name="key_view_action_encrypt">Zašifrovat text</string> <string name="key_view_action_encrypt_files">soubory</string> <string name="key_view_action_certify">Potvrdit klíč</string> diff --git a/OpenKeychain/src/main/res/values-de/strings.xml b/OpenKeychain/src/main/res/values-de/strings.xml index 6f4b8d1d5..6a29c338f 100644 --- a/OpenKeychain/src/main/res/values-de/strings.xml +++ b/OpenKeychain/src/main/res/values-de/strings.xml @@ -572,7 +572,7 @@ <string name="key_list_fab_search">Schlüsselsuche</string> <string name="key_list_fab_import">Aus Datei importieren</string> <!--Key view--> - <string name="key_view_action_edit">Schlüssel bearbeiten</string> + <string name="key_view_action_edit_ids">Schlüssel bearbeiten</string> <string name="key_view_action_encrypt">Text verschlüsseln</string> <string name="key_view_action_encrypt_files">Dateien</string> <string name="key_view_action_certify">Schlüssel bestätigen</string> diff --git a/OpenKeychain/src/main/res/values-es/strings.xml b/OpenKeychain/src/main/res/values-es/strings.xml index b39ccac5c..b895ced33 100644 --- a/OpenKeychain/src/main/res/values-es/strings.xml +++ b/OpenKeychain/src/main/res/values-es/strings.xml @@ -581,7 +581,7 @@ <string name="key_list_fab_search">Búsqueda de clave</string> <string name="key_list_fab_import">Importar desde fichero</string> <!--Key view--> - <string name="key_view_action_edit">Editar clave</string> + <string name="key_view_action_edit_ids">Editar clave</string> <string name="key_view_action_encrypt">Cifrar texto</string> <string name="key_view_action_encrypt_files">ficheros</string> <string name="key_view_action_certify">Confirmar clave</string> diff --git a/OpenKeychain/src/main/res/values-eu/strings.xml b/OpenKeychain/src/main/res/values-eu/strings.xml index 3d2ba88d8..b26be00ce 100644 --- a/OpenKeychain/src/main/res/values-eu/strings.xml +++ b/OpenKeychain/src/main/res/values-eu/strings.xml @@ -576,7 +576,7 @@ <string name="key_list_fab_search">Giltza Bilaketa</string> <string name="key_list_fab_import">Inportatu Agiritik</string> <!--Key view--> - <string name="key_view_action_edit">Editatu giltza</string> + <string name="key_view_action_edit_ids">Editatu giltza</string> <string name="key_view_action_encrypt">Enkriptatu idazkia</string> <string name="key_view_action_encrypt_files">agiriak</string> <string name="key_view_action_certify">Baieztatu giltza</string> diff --git a/OpenKeychain/src/main/res/values-fr/strings.xml b/OpenKeychain/src/main/res/values-fr/strings.xml index d80ff8988..506cf2af9 100644 --- a/OpenKeychain/src/main/res/values-fr/strings.xml +++ b/OpenKeychain/src/main/res/values-fr/strings.xml @@ -586,7 +586,7 @@ <string name="key_list_fab_search">Recherche de clefs</string> <string name="key_list_fab_import">Importer d\'un fichier</string> <!--Key view--> - <string name="key_view_action_edit">Modifier la clef</string> + <string name="key_view_action_edit_ids">Modifier la clef</string> <string name="key_view_action_encrypt">Chiffrer un texte</string> <string name="key_view_action_encrypt_files">fichiers</string> <string name="key_view_action_certify">Confirmer la clef</string> diff --git a/OpenKeychain/src/main/res/values-it/strings.xml b/OpenKeychain/src/main/res/values-it/strings.xml index 42478af12..8a9fc394f 100644 --- a/OpenKeychain/src/main/res/values-it/strings.xml +++ b/OpenKeychain/src/main/res/values-it/strings.xml @@ -441,7 +441,7 @@ Permetti accesso?\n\nATTENZIONE: Se non sai perche\' questo schermata e\' appars <string name="key_list_empty_text1">Nessuna chiave trovata!</string> <string name="key_list_filter_show_all">Mostra tutte le chiavi</string> <!--Key view--> - <string name="key_view_action_edit">Modifica chiave</string> + <string name="key_view_action_edit_ids">Modifica chiave</string> <string name="key_view_action_encrypt">Codifica Testo</string> <string name="key_view_action_encrypt_files">documenti</string> <string name="key_view_action_update">Aggiorna dal server delle chiavi</string> diff --git a/OpenKeychain/src/main/res/values-ja/strings.xml b/OpenKeychain/src/main/res/values-ja/strings.xml index 82a4c8f33..5b1b3e9ef 100644 --- a/OpenKeychain/src/main/res/values-ja/strings.xml +++ b/OpenKeychain/src/main/res/values-ja/strings.xml @@ -573,7 +573,7 @@ <string name="key_list_fab_search">鍵の検索</string> <string name="key_list_fab_import">ファイルからインポート</string> <!--Key view--> - <string name="key_view_action_edit">鍵の編集</string> + <string name="key_view_action_edit_ids">鍵の編集</string> <string name="key_view_action_encrypt">テキスト暗号化</string> <string name="key_view_action_encrypt_files">ファイル</string> <string name="key_view_action_certify">鍵の確認</string> diff --git a/OpenKeychain/src/main/res/values-nl/strings.xml b/OpenKeychain/src/main/res/values-nl/strings.xml index 04fac67f2..932832f60 100644 --- a/OpenKeychain/src/main/res/values-nl/strings.xml +++ b/OpenKeychain/src/main/res/values-nl/strings.xml @@ -489,7 +489,7 @@ <string name="key_list_empty_text1">Geen sleutels gevonden!</string> <string name="key_list_filter_show_all">Alle sleutels weergeven</string> <!--Key view--> - <string name="key_view_action_edit">Sleutel bewerken</string> + <string name="key_view_action_edit_ids">Sleutel bewerken</string> <string name="key_view_action_encrypt">Versleutel tekst</string> <string name="key_view_action_encrypt_files">bestanden</string> <string name="key_view_action_certify">Sleutel bevestigen</string> diff --git a/OpenKeychain/src/main/res/values-pl/strings.xml b/OpenKeychain/src/main/res/values-pl/strings.xml index 5c80bf161..adb3cf49f 100644 --- a/OpenKeychain/src/main/res/values-pl/strings.xml +++ b/OpenKeychain/src/main/res/values-pl/strings.xml @@ -373,7 +373,7 @@ OSTRZEŻENIE: Jeżeli nie wiesz, czemu wyświetlił się ten komunikat, nie zezw <string name="key_list_empty_text1">Nie znaleziono kluczy!</string> <string name="key_list_filter_show_all">Pokaż wszystkie klucze</string> <!--Key view--> - <string name="key_view_action_edit">Edytuj klucz</string> + <string name="key_view_action_edit_ids">Edytuj klucz</string> <string name="key_view_action_encrypt">Szyfruj tekst</string> <string name="key_view_action_encrypt_files">pliki</string> <string name="key_view_action_update">Aktualizuj z serwera kluczy</string> diff --git a/OpenKeychain/src/main/res/values-ru/strings.xml b/OpenKeychain/src/main/res/values-ru/strings.xml index c1710c063..10dd79cba 100644 --- a/OpenKeychain/src/main/res/values-ru/strings.xml +++ b/OpenKeychain/src/main/res/values-ru/strings.xml @@ -487,7 +487,7 @@ <string name="key_list_filter_show_all">Показать все ключи</string> <string name="key_list_fab_search">Поиск ключа</string> <!--Key view--> - <string name="key_view_action_edit">Изменить ключ</string> + <string name="key_view_action_edit_ids">Изменить ключ</string> <string name="key_view_action_encrypt">Зашифровать текст</string> <string name="key_view_action_encrypt_files">файлы</string> <string name="key_view_action_certify">Подтвердить ключ</string> diff --git a/OpenKeychain/src/main/res/values-sl/strings.xml b/OpenKeychain/src/main/res/values-sl/strings.xml index d7db38ccf..697de0984 100644 --- a/OpenKeychain/src/main/res/values-sl/strings.xml +++ b/OpenKeychain/src/main/res/values-sl/strings.xml @@ -426,7 +426,7 @@ <string name="key_list_empty_text1">Najden ni bil noben ključ!</string> <string name="key_list_filter_show_all">Prikaži vse ključe</string> <!--Key view--> - <string name="key_view_action_edit">Uredi ključ</string> + <string name="key_view_action_edit_ids">Uredi ključ</string> <string name="key_view_action_encrypt">Šifriraj besedilo</string> <string name="key_view_action_encrypt_files">datoteke</string> <string name="key_view_action_certify">Potrdi ključ</string> diff --git a/OpenKeychain/src/main/res/values-sr/strings.xml b/OpenKeychain/src/main/res/values-sr/strings.xml index f19f92087..8ff26f896 100644 --- a/OpenKeychain/src/main/res/values-sr/strings.xml +++ b/OpenKeychain/src/main/res/values-sr/strings.xml @@ -600,7 +600,7 @@ <string name="key_list_fab_search">Претрага кључа</string> <string name="key_list_fab_import">Увези из фајла</string> <!--Key view--> - <string name="key_view_action_edit">Уреди кључ</string> + <string name="key_view_action_edit_ids">Уреди кључ</string> <string name="key_view_action_encrypt">Шифруј текст</string> <string name="key_view_action_encrypt_files">фајлови</string> <string name="key_view_action_certify">Потврди кључ</string> diff --git a/OpenKeychain/src/main/res/values-sv/strings.xml b/OpenKeychain/src/main/res/values-sv/strings.xml index 23d7e6908..f3a818ecf 100644 --- a/OpenKeychain/src/main/res/values-sv/strings.xml +++ b/OpenKeychain/src/main/res/values-sv/strings.xml @@ -476,7 +476,7 @@ <string name="key_list_fab_search">Nyckelsökning</string> <string name="key_list_fab_import">Importera från fil</string> <!--Key view--> - <string name="key_view_action_edit">Redigera nyckel</string> + <string name="key_view_action_edit_ids">Redigera nyckel</string> <string name="key_view_action_encrypt">Kryptera text</string> <string name="key_view_action_encrypt_files">filer</string> <string name="key_view_action_certify">Bekräfta nyckel</string> diff --git a/OpenKeychain/src/main/res/values-tr/strings.xml b/OpenKeychain/src/main/res/values-tr/strings.xml index 22cd4e361..b6088a954 100644 --- a/OpenKeychain/src/main/res/values-tr/strings.xml +++ b/OpenKeychain/src/main/res/values-tr/strings.xml @@ -302,7 +302,7 @@ </plurals> <string name="key_list_filter_show_all">Tüm anahtarları göster</string> <!--Key view--> - <string name="key_view_action_edit">Anahtarı düzenle</string> + <string name="key_view_action_edit_ids">Anahtarı düzenle</string> <string name="key_view_action_encrypt">Metni şifrele</string> <string name="key_view_action_encrypt_files">dosyalar</string> <string name="key_view_action_update">Anahtar sunucusundan güncelle</string> diff --git a/OpenKeychain/src/main/res/values-uk/strings.xml b/OpenKeychain/src/main/res/values-uk/strings.xml index 0613819e9..97f9ec391 100644 --- a/OpenKeychain/src/main/res/values-uk/strings.xml +++ b/OpenKeychain/src/main/res/values-uk/strings.xml @@ -309,7 +309,7 @@ <string name="key_list_empty_text1">Ключ не знайдено!</string> <string name="key_list_filter_show_all">Показати усі ключі</string> <!--Key view--> - <string name="key_view_action_edit">Редагувати ключ</string> + <string name="key_view_action_edit_ids">Редагувати ключ</string> <string name="key_view_action_encrypt">Зашифрувати текст</string> <string name="key_view_action_encrypt_files">файли</string> <string name="key_view_action_update">Оновити із сервера ключів</string> diff --git a/OpenKeychain/src/main/res/values-zh-rTW/strings.xml b/OpenKeychain/src/main/res/values-zh-rTW/strings.xml index 81b3bf126..cb4ebf002 100644 --- a/OpenKeychain/src/main/res/values-zh-rTW/strings.xml +++ b/OpenKeychain/src/main/res/values-zh-rTW/strings.xml @@ -479,7 +479,7 @@ <!--Key list--> <string name="key_list_empty_text1">找不到金鑰!</string> <!--Key view--> - <string name="key_view_action_edit">編輯金鑰</string> + <string name="key_view_action_edit_ids">編輯金鑰</string> <string name="key_view_action_encrypt">加密文字</string> <string name="key_view_action_encrypt_files">檔案</string> <string name="key_view_action_share_with">分享...</string> diff --git a/OpenKeychain/src/main/res/values/colors.xml b/OpenKeychain/src/main/res/values/colors.xml index 4eb9e7d68..93cf126f7 100644 --- a/OpenKeychain/src/main/res/values/colors.xml +++ b/OpenKeychain/src/main/res/values/colors.xml @@ -34,6 +34,6 @@ <color name="translucent_scrim_bottom_center">#2A000000</color> <!-- linked ID view --> - <color name="link_text_material_light">#ff009688</color> + <color name="card_view_button">#7bad45</color> </resources> diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index eb6dab882..e5e20a014 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -129,6 +129,7 @@ <string name="menu_certify_fingerprint">"Confirm with fingerprint"</string> <string name="menu_certify_fingerprint_phrases">"Confirm with phrases"</string> <string name="menu_share_log">"Share log"</string> + <string name="menu_change_password">"Change password"</string> <string name="menu_keyserver_add">"Add"</string> @@ -666,7 +667,7 @@ <string name="key_list_fab_import">"Import from File"</string> <!-- Key view --> - <string name="key_view_action_edit">"Edit key"</string> + <string name="key_view_action_edit">"Edit"</string> <string name="key_view_action_encrypt">"Encrypt text"</string> <string name="key_view_action_encrypt_files">"files"</string> <string name="key_view_action_certify">"Confirm key"</string> @@ -1680,7 +1681,8 @@ <string name="linked_error_network">"Network error!"</string> <string name="linked_error_http">"Communication error: %s"</string> <string name="linked_webview_title_github">"GitHub Authorization"</string> - <string name="linked_gist_description">"OpenKeychain API Tests"</string> + <string name="linked_gist_description">"OpenKeychain Linked Identity"</string> + <string name="linked_empty">Link your key to Github, Twitter or other websites!</string> <string name="snack_btn_overwrite">"Overwrite"</string> <string name="backup_code_explanation">"The backup will be secured with a backup code. Write it down before you proceed!"</string> <string name="backup_code_enter">"Please enter the backup code:"</string> @@ -1703,6 +1705,9 @@ <string name="share_log_dialog_cancel_button">"Cancel"</string> <string name="toast_wrong_mimetype">"Wrong data type, text was expected!"</string> <string name="toast_no_text">"No text in shared data!"</string> + <string name="menu_uids_save">"Save"</string> + <string name="title_edit_identities">"Edit Identities"</string> + <string name="title_edit_subkeys">"Edit Subkeys"</string> <string name="btn_search_for_query">"Search for\n'%s'"</string> </resources> |