From 269330f04f6b4a438a2e3ae3f5e714be50443fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Wed, 30 Dec 2015 16:47:42 +0100 Subject: Add activity to edit user ids only --- OpenKeychain/src/main/AndroidManifest.xml | 8 +- .../keychain/ui/EditIdentitiesActivity.java | 70 ++++ .../keychain/ui/EditIdentitiesFragment.java | 359 +++++++++++++++++++++ .../keychain/ui/ViewKeyActivity.java | 13 - .../keychain/ui/ViewKeyFragment.java | 27 +- .../main/res/layout/edit_identities_activity.xml | 25 ++ .../main/res/layout/edit_identities_fragment.xml | 77 +++++ OpenKeychain/src/main/res/menu/key_view.xml | 35 +- 8 files changed, 571 insertions(+), 43 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditIdentitiesActivity.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditIdentitiesFragment.java create mode 100644 OpenKeychain/src/main/res/layout/edit_identities_activity.xml create mode 100644 OpenKeychain/src/main/res/layout/edit_identities_fragment.xml diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index ee7d7e331..b9c8f8a2b 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -129,12 +129,16 @@ android:name=".ui.EditKeyActivity" android:configChanges="orientation|screenSize|keyboardHidden|keyboard" android:label="@string/title_edit_key" /> - + + android:parentActivityName=".ui.ViewKeyActivity"/> + 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 + * + * 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 . + */ + +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..ebc0c9158 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditIdentitiesFragment.java @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2014-2015 Dominik Schürmann + * + * 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 . + */ + +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.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.ListView; + +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.operations.results.OperationResult.LogType; +import org.sufficientlysecure.keychain.operations.results.SingletonResult; +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.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; +import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter; +import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; +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.util.Notify; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.Date; + +public class EditIdentitiesFragment extends QueueingCryptoOperationFragment + implements LoaderManager.LoaderCallbacks { + + public static final String ARG_DATA_URI = "uri"; + + 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 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); + + mUserIdsList = (ListView) view.findViewById(R.id.edit_key_user_ids); + mUserIdsAddedList = (ListView) view.findViewById(R.id.edit_key_user_ids_added); + mAddUserId = view.findViewById(R.id.edit_key_action_add_user_id); + + 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) { + // if we are working on an Uri, save directly + if (mDataUri == null) { + returnKeyringParcel(); + } else { + cryptoOperation(new CryptoInputParcel(new Date())); + } + } + }, 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); + } + + 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 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 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 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); + + addUserIdDialog.show(getActivity().getSupportFragmentManager(), "addUserIdDialog"); + } + + + protected void returnKeyringParcel() { + if (mSaveKeyringParcel.mAddUserIds.size() == 0) { + Notify.create(getActivity(), R.string.edit_key_error_add_identity, Notify.Style.ERROR).show(); + return; + } + + // use first user id as primary + mSaveKeyringParcel.mChangePrimaryUserId = mSaveKeyringParcel.mAddUserIds.get(0); + + Intent returnIntent = new Intent(); + returnIntent.putExtra(EditKeyActivity.EXTRA_SAVE_KEYRING_PARCEL, mSaveKeyringParcel); + getActivity().setResult(Activity.RESULT_OK, returnIntent); + getActivity().finish(); + } + + /** + * 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(EditKeyActivity.RESULT_OK, intent); + getActivity().finish(); + } + + @Override + public SaveKeyringParcel createOperationInput() { + return mSaveKeyringParcel; + } + + @Override + public void onQueuedOperationSuccess(OperationResult result) { + + // null-protected from Queueing*Fragment + Activity activity = getActivity(); + + // 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.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 1eb16b3c3..d2b58804e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -386,10 +386,6 @@ public class ViewKeyActivity extends BaseNfcActivity implements finish(); return true; } - case R.id.menu_key_view_edit: { - editKey(mDataUri); - return true; - } case R.id.menu_key_view_certify_fingerprint: { certifyFingerprint(mDataUri, false); return true; @@ -404,9 +400,6 @@ 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); @@ -691,12 +684,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 { 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 b8edb9b1c..888cc32b8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyFragment.java @@ -51,6 +51,7 @@ 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; @@ -76,6 +77,7 @@ public class ViewKeyFragment extends LoaderFragment implements public static final String ARG_POSTPONE_TYPE = "postpone_type"; private ListView mUserIds; + private Button mUserIdsEditButton; //private ListView mLinkedSystemContact; enum PostponeType { @@ -131,10 +133,20 @@ public class ViewKeyFragment extends LoaderFragment implements mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids); 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); + mUserIdsEditButton = (Button) view.findViewById(R.id.view_key_card_user_ids_edit); + 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); + + mUserIdsEditButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + editIdentities(mDataUri); + } + }); mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override @@ -149,14 +161,15 @@ 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 showLinkedId(final int position) { final LinkedIdViewFragment frag; try { diff --git a/OpenKeychain/src/main/res/layout/edit_identities_activity.xml b/OpenKeychain/src/main/res/layout/edit_identities_activity.xml new file mode 100644 index 000000000..c8b0e3afc --- /dev/null +++ b/OpenKeychain/src/main/res/layout/edit_identities_activity.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/edit_identities_fragment.xml b/OpenKeychain/src/main/res/layout/edit_identities_fragment.xml new file mode 100644 index 000000000..7d8b9548f --- /dev/null +++ b/OpenKeychain/src/main/res/layout/edit_identities_fragment.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenKeychain/src/main/res/menu/key_view.xml b/OpenKeychain/src/main/res/menu/key_view.xml index a3de4efed..cf519d117 100644 --- a/OpenKeychain/src/main/res/menu/key_view.xml +++ b/OpenKeychain/src/main/res/menu/key_view.xml @@ -2,50 +2,43 @@ - - + android:title="@string/key_view_action_update" + app:showAsAction="always" /> + android:title="@string/menu_export_key" + app:showAsAction="never" /> + android:title="@string/menu_delete_key" + app:showAsAction="never" /> + android:title="@string/menu_advanced" + app:showAsAction="never" /> + app:showAsAction="never" /> + app:showAsAction="never" /> + android:title="@string/menu_linked_add_identity" + app:showAsAction="never" /> -- cgit v1.2.3