aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org
diff options
context:
space:
mode:
authorDominik Schürmann <dominik@dominikschuermann.de>2015-12-31 16:31:03 +0100
committerDominik Schürmann <dominik@dominikschuermann.de>2015-12-31 16:31:03 +0100
commit3c937e858ed5858963d74e182352b9bb615e3f22 (patch)
tree8b25e3a943d8537cd9fae121dae0d8b3e00e09c4 /OpenKeychain/src/main/java/org
parent807e5f7901f98ad14f78f6b038c286e6ff0f6fe1 (diff)
parent6fb96ce7903623f2e3b1c38c765e8a501dbcc0a1 (diff)
downloadopen-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/main/java/org')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditIdentitiesActivity.java70
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditIdentitiesFragment.java461
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java145
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java144
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java47
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvSubkeysFragment.java401
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvUserIdsFragment.java310
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java157
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java29
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserAttributesAdapter.java1
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java33
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAddedAdapter.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsSelectableAdapter.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java12
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ContentDescriptionHint.java9
16 files changed, 1623 insertions, 214 deletions
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