Merge pull request #1584 from open-keychain/edit-redesign

Inline Identity Edit
This commit is contained in:
Dominik Schürmann
2015-12-31 16:31:03 +01:00
50 changed files with 2104 additions and 367 deletions

View File

@@ -129,12 +129,16 @@
android:name=".ui.EditKeyActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_edit_key" />
<!-- NOTE: Dont use configChanges for QR Code view! We use a different layout for landscape -->
<activity
android:name=".ui.EditIdentitiesActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_edit_identities" />
<activity
android:name=".ui.linked.LinkedIdWizard"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_linked_create"
android:parentActivityName=".ui.ViewKeyActivity"></activity>
android:parentActivityName=".ui.ViewKeyActivity"/>
<!-- NOTE: Dont use configChanges for QR Code view! We use a different layout for landscape -->
<activity
android:name=".ui.QrCodeViewActivity"
android:label="@string/share_qr_code_dialog_title" />

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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) {
}
}

View File

@@ -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);

View File

@@ -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);
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setContentShown(false);
// 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,
};
return new CursorLoader(getActivity(), mDataUriSubkeys,
SubkeysAdapter.SUBKEYS_PROJECTION, null, null, null);
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) {
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);
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();
}
}

View File

@@ -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;
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) {
setContentShown(false);
switch (id) {
case LOADER_ID_UNIFIED: {
Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
}
case LOADER_ID_USER_IDS: {
Uri baseUri = UserPackets.buildUserIdsUri(mDataUri);
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
return new CursorLoader(getActivity(), baseUri,
PROJECTION, null, null, null);
}
case LOADER_ID_USER_IDS: {
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;
}
}
case LOADER_ID_USER_IDS: {
mUserIdsAdapter.swapCursor(data);
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();
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -1,5 +1,6 @@
package org.sufficientlysecure.keychain.ui.adapter;
import android.content.Context;
import android.database.Cursor;
import android.support.v4.widget.CursorAdapter;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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>();
}

View File

@@ -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() {

View File

@@ -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

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromYDelta="0" android:toYDelta="300"
android:interpolator="@android:anim/anticipate_interpolator"
android:duration="250"
/>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromYDelta="300" android:toYDelta="0"
android:interpolator="@android:anim/overshoot_interpolator"
android:duration="250"
android:startOffset="100"
/>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="0dp"
android:src="@drawable/ic_mode_edit_white_24dp"
style="@style/Widget.AppCompat.ActionButton" />

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.EditIdentitiesActivity">
<include
android:id="@+id/toolbar_include"
layout="@layout/toolbar_standalone" />
<LinearLayout
android:layout_below="@id/toolbar_include"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/notify_area" />
<FrameLayout
android:id="@+id/edit_key_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
</LinearLayout>
</RelativeLayout>

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.EditIdentitiesActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="96dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp">
<CheckBox
android:id="@+id/edit_identities_upload_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/label_send_key"
android:paddingTop="16dp"
android:paddingBottom="16dp"/>
<android.support.v7.widget.CardView
android:id="@+id/edit_identities_user_ids_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
card_view:cardBackgroundColor="?attr/colorCardViewBackground"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="2dp"
card_view:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
style="@style/CardViewHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/section_user_ids" />
<org.sufficientlysecure.keychain.ui.widget.FixedListView
android:id="@+id/edit_identities_user_ids"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<org.sufficientlysecure.keychain.ui.widget.FixedListView
android:id="@+id/edit_identities_user_ids_added"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
</ScrollView>
<android.support.design.widget.FloatingActionButton
android:id="@+id/edit_identities_add_user_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="24dp"
android:layout_marginRight="24dp"
android:src="@drawable/ic_add_white_24dp" />
</RelativeLayout>

View File

@@ -144,14 +144,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="right|end">
android:gravity="left|start">
<Button
android:id="@+id/button_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/linked_button_view"
android:textColor="@color/link_text_material_light"
android:textColor="@color/card_view_button"
style="?android:attr/borderlessButtonStyle"
/>
@@ -166,21 +166,21 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/linked_button_verify"
android:textColor="@color/link_text_material_light"
android:textColor="@color/card_view_button"
style="?android:attr/borderlessButtonStyle" />
<Button
android:id="@+id/button_retry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/linked_button_retry"
android:textColor="@color/link_text_material_light"
android:textColor="@color/card_view_button"
style="?android:attr/borderlessButtonStyle" />
<Button
android:id="@+id/button_confirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/linked_button_confirm"
android:textColor="@color/link_text_material_light"
android:textColor="@color/card_view_button"
style="?android:attr/borderlessButtonStyle" />
</ViewAnimator>

View File

@@ -1,33 +0,0 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- focusable and related properties to workaround http://stackoverflow.com/q/16182331-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:descendantFocusability="beforeDescendants"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<TextView
style="@style/SectionHeader"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:text="@string/section_user_ids"
android:layout_weight="1" />
<org.sufficientlysecure.keychain.ui.widget.FixedListView
android:id="@+id/view_key_user_ids"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="4dp"
android:layout_weight="1" />
</LinearLayout>
</ScrollView>

View File

@@ -1,34 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:focusableInTouchMode="true"
android:descendantFocusability="beforeDescendants"
android:orientation="vertical">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<TextView
style="@style/SectionHeader"
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- focusable and related properties to workaround http://stackoverflow.com/q/16182331-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:descendantFocusability="beforeDescendants"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<TextView
style="@style/SectionHeader"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:text="@string/section_keys"
android:layout_weight="1" />
<org.sufficientlysecure.keychain.ui.widget.FixedListView
android:id="@+id/view_key_subkeys"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="4dp"
android:layout_weight="1"
android:scrollbarStyle="outsideOverlay" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="@+id/view_key_subkeys_add_layout"
android:visibility="gone"
tools:visibility="visible">
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<org.sufficientlysecure.keychain.ui.widget.FixedListView
android:id="@+id/view_key_subkeys_added"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<ViewAnimator
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:id="@+id/view_key_subkey_fab_layout"
android:inAnimation="@anim/fab_slide_in"
android:outAnimation="@anim/fab_slide_down">
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/view_key_subkey_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginTop="8dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:text="@string/section_keys" />
android:layout_margin="24dp"
android:src="@drawable/ic_add_white_24dp"
android:visibility="invisible"
android:layout_gravity="bottom"
tools:visibility="visible" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
</ViewAnimator>
<ListView
android:id="@+id/keys"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbarStyle="outsideOverlay"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:layout_marginBottom="8dp" />
</FrameLayout>
</LinearLayout>
</RelativeLayout>

View File

@@ -2,6 +2,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:singleLine="true">
@@ -40,16 +41,19 @@
</LinearLayout>
<LinearLayout
android:id="@+id/user_id_item_certified_layout"
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:id="@+id/user_id_icon_animator"
android:layout_width="22dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_gravity="center_vertical"
android:orientation="vertical">
android:orientation="vertical"
custom:initialView="1">
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/user_id_item_certified"
@@ -58,15 +62,14 @@
android:src="@drawable/status_signature_unverified_cutout_24dp"
android:layout_gravity="center_horizontal" />
</LinearLayout>
<ImageView
android:id="@+id/user_id_item_edit_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_mode_edit_grey_24dp"
android:layout_gravity="center_vertical" />
<ImageView
android:id="@+id/user_id_item_edit_image"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="@drawable/ic_mode_edit_grey_24dp"
android:padding="8dp"
android:layout_gravity="center_vertical" />
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
<ImageButton
android:id="@+id/user_id_item_delete_button"

View File

@@ -0,0 +1,86 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- focusable and related properties to workaround http://stackoverflow.com/q/16182331-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:descendantFocusability="beforeDescendants"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<TextView
style="@style/SectionHeader"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:text="@string/section_user_ids"
android:layout_weight="1" />
<org.sufficientlysecure.keychain.ui.widget.FixedListView
android:id="@+id/view_key_user_ids"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="4dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="@+id/view_key_user_ids_add_layout"
android:visibility="gone"
tools:visibility="visible">
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<org.sufficientlysecure.keychain.ui.widget.FixedListView
android:id="@+id/view_key_user_ids_added"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</ScrollView>
<ViewAnimator
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:id="@+id/view_key_subkey_fab_layout"
android:inAnimation="@anim/fab_slide_in"
android:outAnimation="@anim/fab_slide_down">
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/view_key_subkey_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:src="@drawable/ic_add_white_24dp"
android:visibility="invisible"
android:layout_gravity="bottom"
tools:visibility="visible" />
</ViewAnimator>
</RelativeLayout>

View File

@@ -1,8 +1,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
@@ -11,16 +11,16 @@
<android.support.v7.widget.CardView
android:id="@+id/card_linked_ids"
android:transitionName="card_linked_ids"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:transitionName="card_linked_ids"
android:visibility="gone"
tools:visibility="visible"
card_view:cardBackgroundColor="?attr/colorCardViewBackground"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="2dp"
card_view:cardUseCompatPadding="true"
card_view:cardCornerRadius="4dp">
tools:visibility="visible">
<LinearLayout
android:layout_width="match_parent"
@@ -39,32 +39,61 @@
android:layout_height="wrap_content"
android:layout_marginBottom="4dp" />
<TextView
android:id="@+id/view_key_linked_ids_empty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:gravity="center"
android:text="@string/linked_empty" />
<TextView
android:id="@+id/view_key_linked_ids_expander"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:gravity="center_vertical"
android:drawableTop="@drawable/divider"
android:drawableRight="@drawable/ic_expand_more_black_24dp"
android:layout_marginTop="4dp"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:drawableEnd="@drawable/ic_expand_more_black_24dp"
android:drawablePadding="3dp"
android:clickable="true"
android:text="@string/linked_ids_more_unknown"
android:drawableRight="@drawable/ic_expand_more_black_24dp"
android:drawableTop="@drawable/divider"
android:gravity="center_vertical"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:background="?android:selectableItemBackground"
android:text="@string/linked_ids_more_unknown"
android:visibility="gone"
tools:visibility="visible"
/>
tools:visibility="visible" />
<LinearLayout
android:id="@+id/view_key_card_linked_ids_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|start"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<Button
android:id="@+id/view_key_card_linked_ids_add"
style="?android:attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_linked_add_identity"
android:textColor="@color/card_view_button" />
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:id="@+id/card_view"
android:id="@+id/view_key_card_user_ids"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
@@ -78,17 +107,46 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
style="@style/CardViewHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/section_user_ids" />
<org.sufficientlysecure.keychain.ui.widget.FixedListView
android:id="@+id/view_key_user_ids"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp" />
android:orientation="vertical">
<TextView
style="@style/CardViewHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/section_user_ids" />
<org.sufficientlysecure.keychain.ui.widget.FixedListView
android:id="@+id/view_key_user_ids"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/view_key_card_user_ids_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="left|start"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<Button
android:id="@+id/view_key_card_user_ids_edit"
style="?android:attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/key_view_action_edit"
android:textColor="@color/card_view_button" />
</LinearLayout>
</LinearLayout>

View File

@@ -89,7 +89,7 @@
android:layout_height="wrap_content"
android:layout_gravity="right|end"
android:text="@string/button_bind_key"
android:textColor="@color/link_text_material_light"
android:textColor="@color/card_view_button"
style="?android:attr/borderlessButtonStyle"
android:visibility="gone"
/>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_uids_save"
android:title="@string/menu_uids_save"
app:showAsAction="always" />
</menu>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_action_mode_edit"
android:icon="@drawable/ic_mode_edit_white_24dp"
android:title="@string/key_view_action_edit"
app:showAsAction="always"
/>
</menu>

View File

@@ -2,50 +2,43 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_key_view_edit"
android:icon="@drawable/ic_mode_edit_white_24dp"
android:visible="false"
app:showAsAction="always"
android:title="@string/key_view_action_edit" />
<item
android:id="@+id/menu_key_view_refresh"
android:icon="@drawable/ic_refresh_white_24dp"
app:showAsAction="always"
android:title="@string/key_view_action_update" />
android:title="@string/key_view_action_update"
app:showAsAction="always" />
<item
android:id="@+id/menu_key_change_password"
android:title="@string/menu_change_password"
app:showAsAction="never" />
<item
android:id="@+id/menu_key_view_backup"
app:showAsAction="never"
android:title="@string/menu_export_key" />
android:title="@string/menu_export_key"
app:showAsAction="never" />
<item
android:id="@+id/menu_key_view_delete"
android:icon="@drawable/ic_delete_grey_24dp"
app:showAsAction="never"
android:title="@string/menu_delete_key" />
android:title="@string/menu_delete_key"
app:showAsAction="never" />
<item
android:id="@+id/menu_key_view_advanced"
app:showAsAction="never"
android:title="@string/menu_advanced" />
android:title="@string/menu_advanced"
app:showAsAction="never" />
<item
android:id="@+id/menu_key_view_certify_fingerprint"
app:showAsAction="never"
android:title="@string/menu_certify_fingerprint"
android:visible="false"
android:title="@string/menu_certify_fingerprint" />
app:showAsAction="never" />
<item
android:id="@+id/menu_key_view_certify_fingerprint_word"
app:showAsAction="never"
android:title="@string/menu_certify_fingerprint_phrases"
android:visible="false"
android:title="@string/menu_certify_fingerprint_phrases" />
<item
android:id="@+id/menu_key_view_add_linked_identity"
app:showAsAction="never"
android:title="@string/menu_linked_add_identity" />
app:showAsAction="never" />
</menu>

View File

@@ -385,7 +385,7 @@
<string name="key_list_empty_text1">Žádný klíč nenalezen!</string>
<string name="key_list_filter_show_all">Zobrazit všechny klíče</string>
<!--Key view-->
<string name="key_view_action_edit">Editovat klíč</string>
<string name="key_view_action_edit_ids">Editovat klíč</string>
<string name="key_view_action_encrypt">Zašifrovat text</string>
<string name="key_view_action_encrypt_files">soubory</string>
<string name="key_view_action_certify">Potvrdit klíč</string>

View File

@@ -572,7 +572,7 @@
<string name="key_list_fab_search">Schlüsselsuche</string>
<string name="key_list_fab_import">Aus Datei importieren</string>
<!--Key view-->
<string name="key_view_action_edit">Schlüssel bearbeiten</string>
<string name="key_view_action_edit_ids">Schlüssel bearbeiten</string>
<string name="key_view_action_encrypt">Text verschlüsseln</string>
<string name="key_view_action_encrypt_files">Dateien</string>
<string name="key_view_action_certify">Schlüssel bestätigen</string>

View File

@@ -581,7 +581,7 @@
<string name="key_list_fab_search">Búsqueda de clave</string>
<string name="key_list_fab_import">Importar desde fichero</string>
<!--Key view-->
<string name="key_view_action_edit">Editar clave</string>
<string name="key_view_action_edit_ids">Editar clave</string>
<string name="key_view_action_encrypt">Cifrar texto</string>
<string name="key_view_action_encrypt_files">ficheros</string>
<string name="key_view_action_certify">Confirmar clave</string>

View File

@@ -576,7 +576,7 @@
<string name="key_list_fab_search">Giltza Bilaketa</string>
<string name="key_list_fab_import">Inportatu Agiritik</string>
<!--Key view-->
<string name="key_view_action_edit">Editatu giltza</string>
<string name="key_view_action_edit_ids">Editatu giltza</string>
<string name="key_view_action_encrypt">Enkriptatu idazkia</string>
<string name="key_view_action_encrypt_files">agiriak</string>
<string name="key_view_action_certify">Baieztatu giltza</string>

View File

@@ -586,7 +586,7 @@
<string name="key_list_fab_search">Recherche de clefs</string>
<string name="key_list_fab_import">Importer d\'un fichier</string>
<!--Key view-->
<string name="key_view_action_edit">Modifier la clef</string>
<string name="key_view_action_edit_ids">Modifier la clef</string>
<string name="key_view_action_encrypt">Chiffrer un texte</string>
<string name="key_view_action_encrypt_files">fichiers</string>
<string name="key_view_action_certify">Confirmer la clef</string>

View File

@@ -441,7 +441,7 @@ Permetti accesso?\n\nATTENZIONE: Se non sai perche\' questo schermata e\' appars
<string name="key_list_empty_text1">Nessuna chiave trovata!</string>
<string name="key_list_filter_show_all">Mostra tutte le chiavi</string>
<!--Key view-->
<string name="key_view_action_edit">Modifica chiave</string>
<string name="key_view_action_edit_ids">Modifica chiave</string>
<string name="key_view_action_encrypt">Codifica Testo</string>
<string name="key_view_action_encrypt_files">documenti</string>
<string name="key_view_action_update">Aggiorna dal server delle chiavi</string>

View File

@@ -573,7 +573,7 @@
<string name="key_list_fab_search">鍵の検索</string>
<string name="key_list_fab_import">ファイルからインポート</string>
<!--Key view-->
<string name="key_view_action_edit">鍵の編集</string>
<string name="key_view_action_edit_ids">鍵の編集</string>
<string name="key_view_action_encrypt">テキスト暗号化</string>
<string name="key_view_action_encrypt_files">ファイル</string>
<string name="key_view_action_certify">鍵の確認</string>

View File

@@ -489,7 +489,7 @@
<string name="key_list_empty_text1">Geen sleutels gevonden!</string>
<string name="key_list_filter_show_all">Alle sleutels weergeven</string>
<!--Key view-->
<string name="key_view_action_edit">Sleutel bewerken</string>
<string name="key_view_action_edit_ids">Sleutel bewerken</string>
<string name="key_view_action_encrypt">Versleutel tekst</string>
<string name="key_view_action_encrypt_files">bestanden</string>
<string name="key_view_action_certify">Sleutel bevestigen</string>

View File

@@ -373,7 +373,7 @@ OSTRZEŻENIE: Jeżeli nie wiesz, czemu wyświetlił się ten komunikat, nie zezw
<string name="key_list_empty_text1">Nie znaleziono kluczy!</string>
<string name="key_list_filter_show_all">Pokaż wszystkie klucze</string>
<!--Key view-->
<string name="key_view_action_edit">Edytuj klucz</string>
<string name="key_view_action_edit_ids">Edytuj klucz</string>
<string name="key_view_action_encrypt">Szyfruj tekst</string>
<string name="key_view_action_encrypt_files">pliki</string>
<string name="key_view_action_update">Aktualizuj z serwera kluczy</string>

View File

@@ -487,7 +487,7 @@
<string name="key_list_filter_show_all">Показать все ключи</string>
<string name="key_list_fab_search">Поиск ключа</string>
<!--Key view-->
<string name="key_view_action_edit">Изменить ключ</string>
<string name="key_view_action_edit_ids">Изменить ключ</string>
<string name="key_view_action_encrypt">Зашифровать текст</string>
<string name="key_view_action_encrypt_files">файлы</string>
<string name="key_view_action_certify">Подтвердить ключ</string>

View File

@@ -426,7 +426,7 @@
<string name="key_list_empty_text1">Najden ni bil noben ključ!</string>
<string name="key_list_filter_show_all">Prikaži vse ključe</string>
<!--Key view-->
<string name="key_view_action_edit">Uredi ključ</string>
<string name="key_view_action_edit_ids">Uredi ključ</string>
<string name="key_view_action_encrypt">Šifriraj besedilo</string>
<string name="key_view_action_encrypt_files">datoteke</string>
<string name="key_view_action_certify">Potrdi ključ</string>

View File

@@ -600,7 +600,7 @@
<string name="key_list_fab_search">Претрага кључа</string>
<string name="key_list_fab_import">Увези из фајла</string>
<!--Key view-->
<string name="key_view_action_edit">Уреди кључ</string>
<string name="key_view_action_edit_ids">Уреди кључ</string>
<string name="key_view_action_encrypt">Шифруј текст</string>
<string name="key_view_action_encrypt_files">фајлови</string>
<string name="key_view_action_certify">Потврди кључ</string>

View File

@@ -476,7 +476,7 @@
<string name="key_list_fab_search">Nyckelsökning</string>
<string name="key_list_fab_import">Importera från fil</string>
<!--Key view-->
<string name="key_view_action_edit">Redigera nyckel</string>
<string name="key_view_action_edit_ids">Redigera nyckel</string>
<string name="key_view_action_encrypt">Kryptera text</string>
<string name="key_view_action_encrypt_files">filer</string>
<string name="key_view_action_certify">Bekräfta nyckel</string>

View File

@@ -302,7 +302,7 @@
</plurals>
<string name="key_list_filter_show_all">Tüm anahtarları göster</string>
<!--Key view-->
<string name="key_view_action_edit">Anahtarı düzenle</string>
<string name="key_view_action_edit_ids">Anahtarı düzenle</string>
<string name="key_view_action_encrypt">Metni şifrele</string>
<string name="key_view_action_encrypt_files">dosyalar</string>
<string name="key_view_action_update">Anahtar sunucusundan güncelle</string>

View File

@@ -309,7 +309,7 @@
<string name="key_list_empty_text1">Ключ не знайдено!</string>
<string name="key_list_filter_show_all">Показати усі ключі</string>
<!--Key view-->
<string name="key_view_action_edit">Редагувати ключ</string>
<string name="key_view_action_edit_ids">Редагувати ключ</string>
<string name="key_view_action_encrypt">Зашифрувати текст</string>
<string name="key_view_action_encrypt_files">файли</string>
<string name="key_view_action_update">Оновити із сервера ключів</string>

View File

@@ -479,7 +479,7 @@
<!--Key list-->
<string name="key_list_empty_text1">找不到金鑰!</string>
<!--Key view-->
<string name="key_view_action_edit">編輯金鑰</string>
<string name="key_view_action_edit_ids">編輯金鑰</string>
<string name="key_view_action_encrypt">加密文字</string>
<string name="key_view_action_encrypt_files">檔案</string>
<string name="key_view_action_share_with">分享...</string>

View File

@@ -34,6 +34,6 @@
<color name="translucent_scrim_bottom_center">#2A000000</color>
<!-- linked ID view -->
<color name="link_text_material_light">#ff009688</color>
<color name="card_view_button">#7bad45</color>
</resources>

View File

@@ -129,6 +129,7 @@
<string name="menu_certify_fingerprint">"Confirm with fingerprint"</string>
<string name="menu_certify_fingerprint_phrases">"Confirm with phrases"</string>
<string name="menu_share_log">"Share log"</string>
<string name="menu_change_password">"Change password"</string>
<string name="menu_keyserver_add">"Add"</string>
@@ -666,7 +667,7 @@
<string name="key_list_fab_import">"Import from File"</string>
<!-- Key view -->
<string name="key_view_action_edit">"Edit key"</string>
<string name="key_view_action_edit">"Edit"</string>
<string name="key_view_action_encrypt">"Encrypt text"</string>
<string name="key_view_action_encrypt_files">"files"</string>
<string name="key_view_action_certify">"Confirm key"</string>
@@ -1680,7 +1681,8 @@
<string name="linked_error_network">"Network error!"</string>
<string name="linked_error_http">"Communication error: %s"</string>
<string name="linked_webview_title_github">"GitHub Authorization"</string>
<string name="linked_gist_description">"OpenKeychain API Tests"</string>
<string name="linked_gist_description">"OpenKeychain Linked Identity"</string>
<string name="linked_empty">Link your key to Github, Twitter or other websites!</string>
<string name="snack_btn_overwrite">"Overwrite"</string>
<string name="backup_code_explanation">"The backup will be secured with a backup code. Write it down before you proceed!"</string>
<string name="backup_code_enter">"Please enter the backup code:"</string>
@@ -1703,6 +1705,9 @@
<string name="share_log_dialog_cancel_button">"Cancel"</string>
<string name="toast_wrong_mimetype">"Wrong data type, text was expected!"</string>
<string name="toast_no_text">"No text in shared data!"</string>
<string name="menu_uids_save">"Save"</string>
<string name="title_edit_identities">"Edit Identities"</string>
<string name="title_edit_subkeys">"Edit Subkeys"</string>
<string name="btn_search_for_query">"Search for\n'%s'"</string>
</resources>