diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui')
92 files changed, 4448 insertions, 1581 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java new file mode 100644 index 000000000..2d9ac6ee3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 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 java.util.ArrayList; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Intent; +import android.database.Cursor; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.util.ExportHelper; + +public class BackupFragment extends Fragment { + + // This ids for multiple key export. + private ArrayList<Long> mIdsForRepeatAskPassphrase; + // This index for remembering the number of master key. + private int mIndex; + + static final int REQUEST_REPEAT_PASSPHRASE = 1; + + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.backup_fragment, container, false); + + View backupAll = view.findViewById(R.id.backup_all); + View backupPublicKeys = view.findViewById(R.id.backup_public_keys); + + backupAll.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + exportToFile(true); + } + }); + + backupPublicKeys.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + exportToFile(false); + } + }); + + return view; + } + + private void exportToFile(boolean includeSecretKeys) { + FragmentActivity activity = getActivity(); + if (activity == null) { + return; + } + + if (!includeSecretKeys) { + ExportHelper exportHelper = new ExportHelper(activity); + exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, false); + return; + } + + new AsyncTask<ContentResolver,Void,ArrayList<Long>>() { + @Override + protected ArrayList<Long> doInBackground(ContentResolver... resolver) { + ArrayList<Long> askPassphraseIds = new ArrayList<>(); + Cursor cursor = resolver[0].query( + KeyRings.buildUnifiedKeyRingsUri(), new String[] { + KeyRings.MASTER_KEY_ID, + KeyRings.HAS_SECRET, + }, KeyRings.HAS_SECRET + " != 0", null, null); + try { + if (cursor != null) { + while (cursor.moveToNext()) { + SecretKeyType secretKeyType = SecretKeyType.fromNum(cursor.getInt(1)); + switch (secretKeyType) { + // all of these make no sense to ask + case PASSPHRASE_EMPTY: + case GNU_DUMMY: + case DIVERT_TO_CARD: + case UNAVAILABLE: + continue; + default: { + long keyId = cursor.getLong(0); + askPassphraseIds.add(keyId); + } + } + } + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return askPassphraseIds; + } + + @Override + protected void onPostExecute(ArrayList<Long> askPassphraseIds) { + super.onPostExecute(askPassphraseIds); + FragmentActivity activity = getActivity(); + if (activity == null) { + return; + } + + mIdsForRepeatAskPassphrase = askPassphraseIds; + mIndex = 0; + + if (mIdsForRepeatAskPassphrase.size() != 0) { + startPassphraseActivity(); + return; + } + + ExportHelper exportHelper = new ExportHelper(activity); + exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true); + } + + }.execute(activity.getContentResolver()); + + } + + private void startPassphraseActivity() { + Activity activity = getActivity(); + if (activity == null) { + return; + } + + Intent intent = new Intent(activity, PassphraseDialogActivity.class); + long masterKeyId = mIdsForRepeatAskPassphrase.get(mIndex++); + intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, masterKeyId); + startActivityForResult(intent, REQUEST_REPEAT_PASSPHRASE); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_REPEAT_PASSPHRASE) { + if (resultCode != Activity.RESULT_OK) { + return; + } + if (mIndex < mIdsForRepeatAskPassphrase.size()) { + startPassphraseActivity(); + return; + } + + ExportHelper exportHelper = new ExportHelper(getActivity()); + exportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index 891c2268c..5ca7c6bc7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -49,13 +49,17 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.CertifyActionsParcel; import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.adapter.MultiUserIdsAdapter; import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.widget.CertifyKeySpinner; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; import java.util.ArrayList; +import java.util.Date; public class CertifyKeyFragment extends CachingCryptoOperationFragment<CertifyActionsParcel, CertifyResult> @@ -151,7 +155,7 @@ public class CertifyKeyFragment // make certify image gray, like action icons ImageView vActionCertifyImage = (ImageView) view.findViewById(R.id.certify_key_action_certify_image); - vActionCertifyImage.setColorFilter(getResources().getColor(R.color.tertiary_text_light), + vActionCertifyImage.setColorFilter(FormattingUtils.getColorFromAttr(getActivity(), R.attr.colorTertiaryText), PorterDuff.Mode.SRC_IN); View vCertifyButton = view.findViewById(R.id.certify_key_certify_button); @@ -164,7 +168,7 @@ public class CertifyKeyFragment Notify.create(getActivity(), getString(R.string.select_key_to_certify), Notify.Style.ERROR).show(); } else { - cryptoOperation(); + cryptoOperation(new CryptoInputParcel(new Date())); } } }); @@ -311,6 +315,11 @@ public class CertifyKeyFragment CertifyActionsParcel actionsParcel = new CertifyActionsParcel(selectedKeyId); actionsParcel.mCertifyActions.addAll(certifyActions); + if (mUploadKeyCheckbox.isChecked()) { + actionsParcel.keyServerUri = Preferences.getPreferences(getActivity()) + .getPreferredKeyserver(); + } + // cached for next cryptoOperation loop cacheActionsParcel(actionsParcel); @@ -318,16 +327,15 @@ public class CertifyKeyFragment } @Override - public void onCryptoOperationSuccess(CertifyResult result) { + public void onQueuedOperationSuccess(CertifyResult result) { + // protected by Queueing*Fragment + Activity activity = getActivity(); + Intent intent = new Intent(); intent.putExtra(CertifyResult.EXTRA_RESULT, result); - getActivity().setResult(Activity.RESULT_OK, intent); - getActivity().finish(); - } + activity.setResult(Activity.RESULT_OK, intent); + activity.finish(); - @Override - public void onCryptoOperationCancelled() { - super.onCryptoOperationCancelled(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java index 7a473e49f..ff5fb7cca 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ConsolidateDialogActivity.java @@ -53,7 +53,7 @@ public class ConsolidateDialogActivity extends FragmentActivity mRecovery = recovery; - mConsolidateOpHelper = new CryptoOperationHelper<>(this, this, R.string.progress_importing); + mConsolidateOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_importing); mConsolidateOpHelper.cryptoOperation(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index 505638c7c..83b176680 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -30,6 +30,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Passphrase; import java.io.IOException; @@ -42,7 +43,9 @@ public class CreateKeyActivity extends BaseNfcActivity { public static final String EXTRA_FIRST_TIME = "first_time"; public static final String EXTRA_ADDITIONAL_EMAILS = "additional_emails"; public static final String EXTRA_PASSPHRASE = "passphrase"; - public static final String EXTRA_USE_SMART_CARD_SETTINGS = "use_smart_card_settings"; + public static final String EXTRA_CREATE_YUBI_KEY = "create_yubi_key"; + public static final String EXTRA_YUBI_KEY_PIN = "yubi_key_pin"; + public static final String EXTRA_YUBI_KEY_ADMIN_PIN = "yubi_key_admin_pin"; public static final String EXTRA_NFC_USER_ID = "nfc_user_id"; public static final String EXTRA_NFC_AID = "nfc_aid"; @@ -55,10 +58,17 @@ public class CreateKeyActivity extends BaseNfcActivity { ArrayList<String> mAdditionalEmails; Passphrase mPassphrase; boolean mFirstTime; - boolean mUseSmartCardSettings; + boolean mCreateYubiKey; + Passphrase mYubiKeyPin; + Passphrase mYubiKeyAdminPin; Fragment mCurrentFragment; + + byte[] mScannedFingerprints; + byte[] mNfcAid; + String mNfcUserId; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -66,13 +76,8 @@ public class CreateKeyActivity extends BaseNfcActivity { // React on NDEF_DISCOVERED from Manifest // NOTE: ACTION_NDEF_DISCOVERED and not ACTION_TAG_DISCOVERED like in BaseNfcActivity if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) { - try { - handleTagDiscoveredIntent(getIntent()); - } catch (CardException e) { - handleNfcError(e); - } catch (IOException e) { - handleNfcError(e); - } + + handleIntentInBackground(getIntent()); setTitle(R.string.title_manage_my_keys); @@ -88,7 +93,9 @@ public class CreateKeyActivity extends BaseNfcActivity { mAdditionalEmails = savedInstanceState.getStringArrayList(EXTRA_ADDITIONAL_EMAILS); mPassphrase = savedInstanceState.getParcelable(EXTRA_PASSPHRASE); mFirstTime = savedInstanceState.getBoolean(EXTRA_FIRST_TIME); - mUseSmartCardSettings = savedInstanceState.getBoolean(EXTRA_USE_SMART_CARD_SETTINGS); + mCreateYubiKey = savedInstanceState.getBoolean(EXTRA_CREATE_YUBI_KEY); + mYubiKeyPin = savedInstanceState.getParcelable(EXTRA_YUBI_KEY_PIN); + mYubiKeyAdminPin = savedInstanceState.getParcelable(EXTRA_YUBI_KEY_ADMIN_PIN); mCurrentFragment = getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG); } else { @@ -98,7 +105,7 @@ public class CreateKeyActivity extends BaseNfcActivity { mName = intent.getStringExtra(EXTRA_NAME); mEmail = intent.getStringExtra(EXTRA_EMAIL); mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false); - mUseSmartCardSettings = intent.getBooleanExtra(EXTRA_USE_SMART_CARD_SETTINGS, false); + mCreateYubiKey = intent.getBooleanExtra(EXTRA_CREATE_YUBI_KEY, false); if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) { byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS); @@ -106,15 +113,19 @@ public class CreateKeyActivity extends BaseNfcActivity { byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID); if (containsKeys(nfcFingerprints)) { - Fragment frag = CreateKeyYubiKeyImportFragment.newInstance( + Fragment frag = CreateYubiKeyImportFragment.newInstance( nfcFingerprints, nfcAid, nfcUserId); loadFragment(frag, FragAction.START); setTitle(R.string.title_import_keys); } else { - Fragment frag = CreateKeyYubiKeyBlankFragment.newInstance(); - loadFragment(frag, FragAction.START); - setTitle(R.string.title_manage_my_keys); +// Fragment frag = CreateYubiKeyBlankFragment.newInstance(); +// loadFragment(frag, FragAction.START); +// setTitle(R.string.title_manage_my_keys); + Notify.create(this, + "YubiKey key creation is currently not supported. Please follow our FAQ.", + Notify.Style.ERROR + ).show(); } // done @@ -136,40 +147,51 @@ public class CreateKeyActivity extends BaseNfcActivity { } @Override - protected void onNfcPerform() throws IOException { + protected void doNfcInBackground() throws IOException { if (mCurrentFragment instanceof NfcListenerFragment) { - ((NfcListenerFragment) mCurrentFragment).onNfcPerform(); + ((NfcListenerFragment) mCurrentFragment).doNfcInBackground(); return; } - byte[] scannedFingerprints = nfcGetFingerprints(); - byte[] nfcAid = nfcGetAid(); - String userId = nfcGetUserId(); + mScannedFingerprints = nfcGetFingerprints(); + mNfcAid = nfcGetAid(); + mNfcUserId = nfcGetUserId(); + } - if (containsKeys(scannedFingerprints)) { + @Override + protected void onNfcPostExecute() throws IOException { + if (mCurrentFragment instanceof NfcListenerFragment) { + ((NfcListenerFragment) mCurrentFragment).onNfcPostExecute(); + return; + } + + if (containsKeys(mScannedFingerprints)) { try { - long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints); + long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mScannedFingerprints); CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId); ring.getMasterKeyId(); Intent intent = new Intent(this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, userId); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, scannedFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mScannedFingerprints); startActivity(intent); finish(); } catch (PgpKeyNotFoundException e) { - Fragment frag = CreateKeyYubiKeyImportFragment.newInstance( - scannedFingerprints, nfcAid, userId); + Fragment frag = CreateYubiKeyImportFragment.newInstance( + mScannedFingerprints, mNfcAid, mNfcUserId); loadFragment(frag, FragAction.TO_RIGHT); } } else { - Fragment frag = CreateKeyYubiKeyBlankFragment.newInstance(); - loadFragment(frag, FragAction.TO_RIGHT); +// Fragment frag = CreateYubiKeyBlankFragment.newInstance(); +// loadFragment(frag, FragAction.TO_RIGHT); + Notify.create(this, + "YubiKey key creation is currently not supported. Please follow our FAQ.", + Notify.Style.ERROR + ).show(); } - } private boolean containsKeys(byte[] scannedFingerprints) { @@ -193,7 +215,9 @@ public class CreateKeyActivity extends BaseNfcActivity { outState.putStringArrayList(EXTRA_ADDITIONAL_EMAILS, mAdditionalEmails); outState.putParcelable(EXTRA_PASSPHRASE, mPassphrase); outState.putBoolean(EXTRA_FIRST_TIME, mFirstTime); - outState.putBoolean(EXTRA_USE_SMART_CARD_SETTINGS, mUseSmartCardSettings); + outState.putBoolean(EXTRA_CREATE_YUBI_KEY, mCreateYubiKey); + outState.putParcelable(EXTRA_YUBI_KEY_PIN, mYubiKeyPin); + outState.putParcelable(EXTRA_YUBI_KEY_ADMIN_PIN, mYubiKeyAdminPin); } @Override @@ -238,7 +262,8 @@ public class CreateKeyActivity extends BaseNfcActivity { } interface NfcListenerFragment { - public void onNfcPerform() throws IOException; + public void doNfcInBackground() throws IOException; + public void onNfcPostExecute() throws IOException; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java index 597f04d6b..acb768f55 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyEmailFragment.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; +import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -29,6 +30,7 @@ import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; @@ -37,7 +39,6 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.dialog.AddEmailDialogFragment; -import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.widget.EmailEditText; @@ -201,7 +202,7 @@ public class CreateKeyEmailFragment extends Fragment { Handler returnHandler = new Handler() { @Override public void handleMessage(Message message) { - if (message.what == SetPassphraseDialogFragment.MESSAGE_OKAY) { + if (message.what == AddEmailDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); String email = data.getString(AddEmailDialogFragment.MESSAGE_DATA_EMAIL); @@ -232,11 +233,35 @@ public class CreateKeyEmailFragment extends Fragment { mCreateKeyActivity.mEmail = mEmailEdit.getText().toString(); mCreateKeyActivity.mAdditionalEmails = getAdditionalEmails(); - CreateKeyPassphraseFragment frag = CreateKeyPassphraseFragment.newInstance(); - mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + CreateKeyActivity createKeyActivity = ((CreateKeyActivity) getActivity()); + + if (createKeyActivity.mCreateYubiKey) { + hideKeyboard(); + + CreateYubiKeyPinFragment frag = CreateYubiKeyPinFragment.newInstance(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } else { + CreateKeyPassphraseFragment frag = CreateKeyPassphraseFragment.newInstance(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } } } + private void hideKeyboard() { + if (getActivity() == null) { + return; + } + InputMethodManager inputManager = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + + // check if no view has focus + View v = getActivity().getCurrentFocus(); + if (v == null) + return; + + inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + private ArrayList<String> getAdditionalEmails() { ArrayList<String> emails = new ArrayList<>(); for (EmailAdapter.ViewModel holder : mAdditionalEmailModels) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java index e67715988..739eb3e35 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java @@ -17,6 +17,10 @@ package org.sufficientlysecure.keychain.ui; + +import java.util.Date; +import java.util.Iterator; + import android.app.Activity; import android.content.Intent; import android.database.Cursor; @@ -44,13 +48,13 @@ import org.sufficientlysecure.keychain.service.ExportKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; -import java.util.Iterator; - public class CreateKeyFinalFragment extends Fragment { public static final int REQUEST_EDIT_KEY = 0x00008007; @@ -69,8 +73,14 @@ public class CreateKeyFinalFragment extends Fragment { private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mCreateOpHelper; private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mMoveToCardOpHelper; + // queued results which may trigger delayed actions + private EditKeyResult mQueuedSaveKeyResult; + private OperationResult mQueuedFinishResult; + private EditKeyResult mQueuedDisplayResult; + public static CreateKeyFinalFragment newInstance() { CreateKeyFinalFragment frag = new CreateKeyFinalFragment(); + frag.setRetainInstance(true); Bundle args = new Bundle(); frag.setArguments(args); @@ -178,7 +188,7 @@ public class CreateKeyFinalFragment extends Fragment { if (mSaveKeyringParcel == null) { mSaveKeyringParcel = new SaveKeyringParcel(); - if (createKeyActivity.mUseSmartCardSettings) { + if (createKeyActivity.mCreateYubiKey) { mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 2048, null, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER, 0L)); mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, @@ -187,6 +197,9 @@ public class CreateKeyFinalFragment extends Fragment { 2048, null, KeyFlags.AUTHENTICATION, 0L)); mEditText.setText(R.string.create_key_custom); mEditButton.setEnabled(false); + + // use empty passphrase + mSaveKeyringParcel.mNewUnlock = new ChangeUnlockParcel(new Passphrase(), null); } else { mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 4096, null, KeyFlags.CERTIFY_OTHER, 0L)); @@ -194,6 +207,10 @@ public class CreateKeyFinalFragment extends Fragment { 4096, null, KeyFlags.SIGN_DATA, 0L)); mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA, 4096, null, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, 0L)); + + mSaveKeyringParcel.mNewUnlock = createKeyActivity.mPassphrase != null + ? new ChangeUnlockParcel(createKeyActivity.mPassphrase, null) + : null; } String userId = KeyRing.createUserId( new KeyRing.UserId(createKeyActivity.mName, createKeyActivity.mEmail, null) @@ -209,14 +226,44 @@ public class CreateKeyFinalFragment extends Fragment { mSaveKeyringParcel.mAddUserIds.add(thisUserId); } } - mSaveKeyringParcel.mNewUnlock = createKeyActivity.mPassphrase != null - ? new ChangeUnlockParcel(createKeyActivity.mPassphrase, null) - : null; } + + // handle queued actions + + if (mQueuedFinishResult != null) { + finishWithResult(mQueuedFinishResult); + return; + } + + if (mQueuedDisplayResult != null) { + try { + displayResult(mQueuedDisplayResult); + } finally { + // clear after operation, note that this may drop the operation if it didn't + // work when called from here! + mQueuedDisplayResult = null; + } + } + + if (mQueuedSaveKeyResult != null) { + try { + uploadKey(mQueuedSaveKeyResult); + } finally { + // see above + mQueuedSaveKeyResult = null; + } + } + } private void createKey() { - final CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity(); + CreateKeyActivity activity = (CreateKeyActivity) getActivity(); + if (activity == null) { + // this is a ui-triggered action, nvm if it fails while detached! + return; + } + + final boolean createYubiKey = activity.mCreateYubiKey; CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult> createKeyCallback = new CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult>() { @@ -228,7 +275,7 @@ public class CreateKeyFinalFragment extends Fragment { @Override public void onCryptoOperationSuccess(EditKeyResult result) { - if (createKeyActivity.mUseSmartCardSettings) { + if (createYubiKey) { moveToCard(result); return; } @@ -239,10 +286,7 @@ public class CreateKeyFinalFragment extends Fragment { return; } - Intent data = new Intent(); - data.putExtra(OperationResult.EXTRA_RESULT, result); - getActivity().setResult(Activity.RESULT_OK, data); - getActivity().finish(); + finishWithResult(result); } @Override @@ -252,7 +296,7 @@ public class CreateKeyFinalFragment extends Fragment { @Override public void onCryptoOperationError(EditKeyResult result) { - result.createNotify(getActivity()).show(); + displayResult(result); } @Override @@ -261,16 +305,25 @@ public class CreateKeyFinalFragment extends Fragment { } }; - mCreateOpHelper = new CryptoOperationHelper<>(this, createKeyCallback, - R.string.progress_building_key); + mCreateOpHelper = new CryptoOperationHelper<>(1, this, createKeyCallback, R.string.progress_building_key); mCreateOpHelper.cryptoOperation(); } + private void displayResult(EditKeyResult result) { + Activity activity = getActivity(); + if (activity == null) { + mQueuedDisplayResult = result; + return; + } + result.createNotify(activity).show(); + } + private void moveToCard(final EditKeyResult saveKeyResult) { - CachedPublicKeyRing key = (new ProviderHelper(getActivity())) - .getCachedPublicKeyRing(saveKeyResult.mMasterKeyId); + CreateKeyActivity activity = (CreateKeyActivity) getActivity(); final SaveKeyringParcel changeKeyringParcel; + CachedPublicKeyRing key = (new ProviderHelper(activity)) + .getCachedPublicKeyRing(saveKeyResult.mMasterKeyId); try { changeKeyringParcel = new SaveKeyringParcel(key.getMasterKeyId(), key.getFingerprint()); } catch (PgpKeyNotFoundException e) { @@ -278,9 +331,10 @@ public class CreateKeyFinalFragment extends Fragment { return; } - Cursor cursor = getActivity().getContentResolver().query( + // define subkeys that should be moved to the card + Cursor cursor = activity.getContentResolver().query( KeychainContract.Keys.buildKeysUri(changeKeyringParcel.mMasterKeyId), - new String[]{KeychainContract.Keys.KEY_ID,}, null, null, null + new String[] { KeychainContract.Keys.KEY_ID, }, null, null, null ); try { while (cursor != null && cursor.moveToNext()) { @@ -293,6 +347,10 @@ public class CreateKeyFinalFragment extends Fragment { } } + // define new PIN and Admin PIN for the card + changeKeyringParcel.mCardPin = activity.mYubiKeyPin; + changeKeyringParcel.mCardAdminPin = activity.mYubiKeyAdminPin; + CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult> callback = new CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult>() { @@ -326,10 +384,7 @@ public class CreateKeyFinalFragment extends Fragment { return; } - Intent data = new Intent(); - data.putExtra(OperationResult.EXTRA_RESULT, saveKeyResult); - getActivity().setResult(Activity.RESULT_OK, data); - getActivity().finish(); + finishWithResult(saveKeyResult); } @Override @@ -339,16 +394,22 @@ public class CreateKeyFinalFragment extends Fragment { }; - mMoveToCardOpHelper = new CryptoOperationHelper<>(this, callback, R.string.progress_modify); - mMoveToCardOpHelper.cryptoOperation(); + mMoveToCardOpHelper = new CryptoOperationHelper<>(2, this, callback, R.string.progress_modify); + mMoveToCardOpHelper.cryptoOperation(new CryptoInputParcel(new Date())); } private void uploadKey(final EditKeyResult saveKeyResult) { + Activity activity = getActivity(); + // if the activity is gone at this point, there is nothing we can do! + if (activity == null) { + mQueuedSaveKeyResult = saveKeyResult; + return; + } + // set data uri as path to keyring - final Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri( - saveKeyResult.mMasterKeyId); + final Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(saveKeyResult.mMasterKeyId); // upload to favorite keyserver - final String keyserver = Preferences.getPreferences(getActivity()).getPreferredKeyserver(); + final String keyserver = Preferences.getPreferences(activity).getPreferredKeyserver(); CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult> callback = new CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult>() { @@ -374,14 +435,8 @@ public class CreateKeyFinalFragment extends Fragment { } public void handleResult(ExportResult result) { - // TODO: ExportOperation UPLOAD_KEYSERVER needs logs! - // TODO: then merge logs here! - //saveKeyResult.getLog().add(result, 0); - - Intent data = new Intent(); - data.putExtra(OperationResult.EXTRA_RESULT, saveKeyResult); - getActivity().setResult(Activity.RESULT_OK, data); - getActivity().finish(); + saveKeyResult.getLog().add(result, 0); + finishWithResult(saveKeyResult); } @Override @@ -390,9 +445,21 @@ public class CreateKeyFinalFragment extends Fragment { } }; - - mUploadOpHelper = new CryptoOperationHelper<>(this, callback, R.string.progress_uploading); + mUploadOpHelper = new CryptoOperationHelper<>(3, this, callback, R.string.progress_uploading); mUploadOpHelper.cryptoOperation(); } + public void finishWithResult(OperationResult result) { + Activity activity = getActivity(); + if (activity == null) { + mQueuedFinishResult = result; + return; + } + + Intent data = new Intent(); + data.putExtra(OperationResult.EXTRA_RESULT, result); + activity.setResult(Activity.RESULT_OK, data); + activity.finish(); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java index 3379e0a6d..d858fd6ec 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyPassphraseFragment.java @@ -21,6 +21,8 @@ import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; import android.text.method.HideReturnsTransformationMethod; import android.text.method.PasswordTransformationMethod; import android.view.LayoutInflater; @@ -79,19 +81,10 @@ public class CreateKeyPassphraseFragment extends Fragment { return output; } - private static boolean areEditTextsEqual(Context context, EditText editText1, EditText editText2) { + private static boolean areEditTextsEqual(EditText editText1, EditText editText2) { Passphrase p1 = new Passphrase(editText1); Passphrase p2 = new Passphrase(editText2); - boolean output = (p1.equals(p2)); - - if (!output) { - editText2.setError(context.getString(R.string.create_key_passphrases_not_equal)); - editText2.requestFocus(); - } else { - editText2.setError(null); - } - - return output; + return (p1.equals(p2)); } @Override @@ -107,8 +100,8 @@ public class CreateKeyPassphraseFragment extends Fragment { // initial values // TODO: using String here is unsafe... if (mCreateKeyActivity.mPassphrase != null) { - mPassphraseEdit.setText(new String(mCreateKeyActivity.mPassphrase.getCharArray())); - mPassphraseEditAgain.setText(new String(mCreateKeyActivity.mPassphrase.getCharArray())); + mPassphraseEdit.setText(mCreateKeyActivity.mPassphrase.toStringUnsafe()); + mPassphraseEditAgain.setText(mCreateKeyActivity.mPassphrase.toStringUnsafe()); } mPassphraseEdit.requestFocus(); @@ -137,6 +130,35 @@ public class CreateKeyPassphraseFragment extends Fragment { } }); + TextWatcher textWatcher = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (!isEditTextNotEmpty(getActivity(), mPassphraseEdit)) { + mPassphraseEditAgain.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + return; + } + + if (areEditTextsEqual(mPassphraseEdit, mPassphraseEditAgain)) { + mPassphraseEditAgain.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_stat_retyped_ok, 0); + } else { + mPassphraseEditAgain.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_stat_retyped_bad, 0); + } + + } + + @Override + public void afterTextChanged(Editable s) { + + } + }; + + mPassphraseEdit.addTextChangedListener(textWatcher); + mPassphraseEditAgain.addTextChangedListener(textWatcher); return view; } @@ -153,9 +175,15 @@ public class CreateKeyPassphraseFragment extends Fragment { } private void nextClicked() { - if (isEditTextNotEmpty(getActivity(), mPassphraseEdit) - && areEditTextsEqual(getActivity(), mPassphraseEdit, mPassphraseEditAgain)) { + if (isEditTextNotEmpty(getActivity(), mPassphraseEdit)) { + + if (!areEditTextsEqual(mPassphraseEdit, mPassphraseEditAgain)) { + mPassphraseEditAgain.setError(getActivity().getApplicationContext().getString(R.string.create_key_passphrases_not_equal)); + mPassphraseEditAgain.requestFocus(); + return; + } + mPassphraseEditAgain.setError(null); // save state mCreateKeyActivity.mPassphrase = new Passphrase(mPassphraseEdit); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java index 1a844e6e4..cd97ef108 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyStartFragment.java @@ -81,7 +81,7 @@ public class CreateKeyStartFragment extends Fragment { mYubiKey.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - CreateKeyYubiKeyWaitFragment frag = new CreateKeyYubiKeyWaitFragment(); + CreateYubiKeyWaitFragment frag = new CreateYubiKeyWaitFragment(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); } }); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyBlankFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java index 6df340636..5b13dc88e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyBlankFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyBlankFragment.java @@ -27,7 +27,7 @@ import android.view.ViewGroup; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; -public class CreateKeyYubiKeyBlankFragment extends Fragment { +public class CreateYubiKeyBlankFragment extends Fragment { CreateKeyActivity mCreateKeyActivity; View mBackButton; @@ -36,8 +36,8 @@ public class CreateKeyYubiKeyBlankFragment extends Fragment { /** * Creates new instance of this fragment */ - public static CreateKeyYubiKeyBlankFragment newInstance() { - CreateKeyYubiKeyBlankFragment frag = new CreateKeyYubiKeyBlankFragment(); + public static CreateYubiKeyBlankFragment newInstance() { + CreateYubiKeyBlankFragment frag = new CreateYubiKeyBlankFragment(); Bundle args = new Bundle(); @@ -81,7 +81,7 @@ public class CreateKeyYubiKeyBlankFragment extends Fragment { } private void nextClicked() { - mCreateKeyActivity.mUseSmartCardSettings = true; + mCreateKeyActivity.mCreateYubiKey = true; CreateKeyNameFragment frag = CreateKeyNameFragment.newInstance(); mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyImportFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java index 8de874ec5..d88e6b9f9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyImportFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyImportFragment.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui; + import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -39,13 +40,13 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment; -import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Preferences; -public class CreateKeyYubiKeyImportFragment - extends CryptoOperationFragment<ImportKeyringParcel, ImportKeyResult> +public class CreateYubiKeyImportFragment + extends QueueingCryptoOperationFragment<ImportKeyringParcel, ImportKeyResult> implements NfcListenerFragment { private static final String ARG_FINGERPRINT = "fingerprint"; @@ -68,7 +69,7 @@ public class CreateKeyYubiKeyImportFragment public static Fragment newInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) { - CreateKeyYubiKeyImportFragment frag = new CreateKeyYubiKeyImportFragment(); + CreateYubiKeyImportFragment frag = new CreateYubiKeyImportFragment(); Bundle args = new Bundle(); args.putByteArray(ARG_FINGERPRINT, scannedFingerprints); @@ -195,7 +196,7 @@ public class CreateKeyYubiKeyImportFragment } @Override - public void onNfcPerform() throws IOException { + public void doNfcInBackground() throws IOException { mNfcFingerprints = mCreateKeyActivity.nfcGetFingerprints(); mNfcAid = mCreateKeyActivity.nfcGetAid(); @@ -204,10 +205,14 @@ public class CreateKeyYubiKeyImportFragment byte[] fp = new byte[20]; ByteBuffer.wrap(fp).put(mNfcFingerprints, 0, 20); mNfcFingerprint = KeyFormattingUtils.convertFingerprintToHex(fp); + } + + @Override + public void onNfcPostExecute() throws IOException { setData(); - refreshSearch(); + refreshSearch(); } @Override @@ -216,14 +221,17 @@ public class CreateKeyYubiKeyImportFragment } @Override - public void onCryptoOperationSuccess(ImportKeyResult result) { + public void onQueuedOperationSuccess(ImportKeyResult result) { long[] masterKeyIds = result.getImportedMasterKeyIds(); if (masterKeyIds.length == 0) { super.onCryptoOperationError(result); return; } - Intent intent = new Intent(getActivity(), ViewKeyActivity.class); + // null-protected from Queueing*Fragment + Activity activity = getActivity(); + + Intent intent = new Intent(activity, ViewKeyActivity.class); // use the imported masterKeyId, not the one from the yubikey, because // that one might* just have been a subkey of the imported key intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyIds[0])); @@ -232,6 +240,6 @@ public class CreateKeyYubiKeyImportFragment intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); startActivity(intent); - getActivity().finish(); + activity.finish(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java new file mode 100644 index 000000000..a793b31f2 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinFragment.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 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.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; +import org.sufficientlysecure.keychain.util.Passphrase; + +import java.security.SecureRandom; + +public class CreateYubiKeyPinFragment extends Fragment { + + // view + CreateKeyActivity mCreateKeyActivity; + TextView mPin; + TextView mAdminPin; + View mBackButton; + View mNextButton; + + /** + * Creates new instance of this fragment + */ + public static CreateYubiKeyPinFragment newInstance() { + CreateYubiKeyPinFragment frag = new CreateYubiKeyPinFragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.create_yubi_key_pin_fragment, container, false); + + mPin = (TextView) view.findViewById(R.id.create_yubi_key_pin); + mAdminPin = (TextView) view.findViewById(R.id.create_yubi_key_admin_pin); + mBackButton = view.findViewById(R.id.create_key_back_button); + mNextButton = view.findViewById(R.id.create_key_next_button); + + if (mCreateKeyActivity.mYubiKeyPin == null) { + new AsyncTask<Void, Void, Pair<Passphrase, Passphrase>>() { + @Override + protected Pair<Passphrase, Passphrase> doInBackground(Void... unused) { + SecureRandom secureRandom = new SecureRandom(); + // min = 6, we choose 6 + String pin = "" + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9); + // min = 8, we choose 10, but 6 are equals the PIN + String adminPin = pin + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9) + + secureRandom.nextInt(9); + + return new Pair<>(new Passphrase(pin), new Passphrase(adminPin)); + } + + @Override + protected void onPostExecute(Pair<Passphrase, Passphrase> pair) { + mCreateKeyActivity.mYubiKeyPin = pair.first; + mCreateKeyActivity.mYubiKeyAdminPin = pair.second; + + mPin.setText(mCreateKeyActivity.mYubiKeyPin.toStringUnsafe()); + mAdminPin.setText(mCreateKeyActivity.mYubiKeyAdminPin.toStringUnsafe()); + } + }.execute(); + } else { + mPin.setText(mCreateKeyActivity.mYubiKeyPin.toStringUnsafe()); + mAdminPin.setText(mCreateKeyActivity.mYubiKeyAdminPin.toStringUnsafe()); + } + + mBackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + back(); + } + }); + mNextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + nextClicked(); + } + }); + + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mCreateKeyActivity = (CreateKeyActivity) getActivity(); + } + + + private void nextClicked() { + CreateYubiKeyPinRepeatFragment frag = CreateYubiKeyPinRepeatFragment.newInstance(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } + + private void back() { + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java new file mode 100644 index 000000000..2e752e609 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyPinRepeatFragment.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 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.Context; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; + +public class CreateYubiKeyPinRepeatFragment extends Fragment { + + // view + CreateKeyActivity mCreateKeyActivity; + EditText mPin; + EditText mAdminPin; + View mBackButton; + View mNextButton; + + /** + * Creates new instance of this fragment + */ + public static CreateYubiKeyPinRepeatFragment newInstance() { + CreateYubiKeyPinRepeatFragment frag = new CreateYubiKeyPinRepeatFragment(); + + Bundle args = new Bundle(); + frag.setArguments(args); + + return frag; + } + + /** + * Checks if text of given EditText is not empty. If it is empty an error is + * set and the EditText gets the focus. + * + * @param context + * @param editText + * @return true if EditText is not empty + */ + private static boolean isEditTextNotEmpty(Context context, EditText editText) { + boolean output = true; + if (editText.getText().length() == 0) { + editText.setError(context.getString(R.string.create_key_empty)); + editText.requestFocus(); + output = false; + } else { + editText.setError(null); + } + + return output; + } + + private static boolean checkPin(Context context, EditText editText1, String pin) { + boolean output = editText1.getText().toString().equals(pin); + + if (!output) { + editText1.setError(context.getString(R.string.create_key_yubi_key_pin_not_correct)); + editText1.requestFocus(); + } else { + editText1.setError(null); + } + + return output; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.create_yubi_key_pin_repeat_fragment, container, false); + + mPin = (EditText) view.findViewById(R.id.create_yubi_key_pin_repeat); + mAdminPin = (EditText) view.findViewById(R.id.create_yubi_key_admin_pin_repeat); + mBackButton = view.findViewById(R.id.create_key_back_button); + mNextButton = view.findViewById(R.id.create_key_next_button); + + mPin.requestFocus(); + mBackButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + back(); + } + }); + mNextButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + nextClicked(); + } + }); + + return view; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + mCreateKeyActivity = (CreateKeyActivity) getActivity(); + } + + private void back() { + hideKeyboard(); + mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT); + } + + private void nextClicked() { + if (isEditTextNotEmpty(getActivity(), mPin) + && checkPin(getActivity(), mPin, mCreateKeyActivity.mYubiKeyPin.toStringUnsafe()) + && isEditTextNotEmpty(getActivity(), mAdminPin) + && checkPin(getActivity(), mAdminPin, mCreateKeyActivity.mYubiKeyAdminPin.toStringUnsafe())) { + + CreateKeyFinalFragment frag = CreateKeyFinalFragment.newInstance(); + hideKeyboard(); + mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT); + } + } + + private void hideKeyboard() { + if (getActivity() == null) { + return; + } + InputMethodManager inputManager = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + + // check if no view has focus + View v = getActivity().getCurrentFocus(); + if (v == null) + return; + + inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyWaitFragment.java index 0e2dd2531..d45195512 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyYubiKeyWaitFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateYubiKeyWaitFragment.java @@ -28,7 +28,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; -public class CreateKeyYubiKeyWaitFragment extends Fragment { +public class CreateYubiKeyWaitFragment extends Fragment { CreateKeyActivity mCreateKeyActivity; View mBackButton; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index 04f54f151..e581e069b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -49,13 +49,7 @@ public class DecryptActivity extends BaseActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setFullScreenDialogClose(new View.OnClickListener() { - @Override - public void onClick(View v) { - setResult(Activity.RESULT_CANCELED); - finish(); - } - }, false); + setFullScreenDialogClose(Activity.RESULT_CANCELED, false); // Handle intent actions handleActions(savedInstanceState, getIntent()); @@ -158,7 +152,6 @@ public class DecryptActivity extends BaseActivity { } - } catch (IOException e) { Toast.makeText(this, R.string.error_reading_text, Toast.LENGTH_LONG).show(); finish(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 4eb8cd5e8..aaf337f42 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -19,15 +19,11 @@ package org.sufficientlysecure.keychain.ui; import java.util.ArrayList; -import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; import android.support.v4.app.Fragment; -import android.os.Parcelable; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -52,7 +48,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; -import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; @@ -189,7 +184,7 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. } }; - mImportOpHelper = new CryptoOperationHelper<>(this, callback, R.string.progress_importing); + mImportOpHelper = new CryptoOperationHelper<>(1, this, callback, R.string.progress_importing); mImportOpHelper.cryptoOperation(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java index 96767463e..567589821 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -65,12 +65,11 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; -import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; // this import NEEDS to be above the ViewModel one, or it won't compile! (as of 06/06/15) +import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; import org.sufficientlysecure.keychain.ui.DecryptListFragment.DecryptFilesAdapter.ViewModel; import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; -import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; @@ -81,18 +80,21 @@ import org.sufficientlysecure.keychain.util.ParcelableHashMap; public class DecryptListFragment - extends CryptoOperationFragment<PgpDecryptVerifyInputParcel,DecryptVerifyResult> + extends QueueingCryptoOperationFragment<PgpDecryptVerifyInputParcel,DecryptVerifyResult> implements OnMenuItemClickListener { public static final String ARG_INPUT_URIS = "input_uris"; public static final String ARG_OUTPUT_URIS = "output_uris"; + public static final String ARG_CANCELLED_URIS = "cancelled_uris"; public static final String ARG_RESULTS = "results"; private static final int REQUEST_CODE_OUTPUT = 0x00007007; + public static final String ARG_CURRENT_URI = "current_uri"; private ArrayList<Uri> mInputUris; private HashMap<Uri, Uri> mOutputUris; private ArrayList<Uri> mPendingInputUris; + private ArrayList<Uri> mCancelledInputUris; private Uri mCurrentInputUri; @@ -111,6 +113,10 @@ public class DecryptListFragment return frag; } + public DecryptListFragment() { + super(null); + } + /** * Inflate the layout for this fragment */ @@ -152,6 +158,8 @@ public class DecryptListFragment outState.putParcelable(ARG_RESULTS, new ParcelableHashMap<>(results)); outState.putParcelable(ARG_OUTPUT_URIS, new ParcelableHashMap<>(mOutputUris)); + outState.putParcelableArrayList(ARG_CANCELLED_URIS, mCancelledInputUris); + outState.putParcelable(ARG_CURRENT_URI, mCurrentInputUri); } @@ -162,25 +170,45 @@ public class DecryptListFragment Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); ArrayList<Uri> inputUris = getArguments().getParcelableArrayList(ARG_INPUT_URIS); + ArrayList<Uri> cancelledUris = args.getParcelableArrayList(ARG_CANCELLED_URIS); ParcelableHashMap<Uri,Uri> outputUris = args.getParcelable(ARG_OUTPUT_URIS); ParcelableHashMap<Uri,DecryptVerifyResult> results = args.getParcelable(ARG_RESULTS); + Uri currentInputUri = args.getParcelable(ARG_CURRENT_URI); - displayInputUris(inputUris, + displayInputUris(inputUris, currentInputUri, cancelledUris, outputUris != null ? outputUris.getMap() : null, results != null ? results.getMap() : null ); } - private void displayInputUris(ArrayList<Uri> inputUris, HashMap<Uri,Uri> outputUris, + private void displayInputUris(ArrayList<Uri> inputUris, Uri currentInputUri, + ArrayList<Uri> cancelledUris, HashMap<Uri,Uri> outputUris, HashMap<Uri,DecryptVerifyResult> results) { mInputUris = inputUris; + mCurrentInputUri = currentInputUri; mOutputUris = outputUris != null ? outputUris : new HashMap<Uri,Uri>(inputUris.size()); + mCancelledInputUris = cancelledUris != null ? cancelledUris : new ArrayList<Uri>(); mPendingInputUris = new ArrayList<>(); - for (Uri uri : inputUris) { + for (final Uri uri : inputUris) { mAdapter.add(uri); + + if (uri.equals(mCurrentInputUri)) { + continue; + } + + if (mCancelledInputUris.contains(uri)) { + mAdapter.setCancelled(uri, new OnClickListener() { + @Override + public void onClick(View v) { + retryUri(uri); + } + }); + continue; + } + if (results != null && results.containsKey(uri)) { processResult(uri, results.get(uri)); } else { @@ -189,16 +217,9 @@ public class DecryptListFragment } } - cryptoOperation(); - } - - private String removeEncryptedAppend(String name) { - if (name.endsWith(Constants.FILE_EXTENSION_ASC) - || name.endsWith(Constants.FILE_EXTENSION_PGP_MAIN) - || name.endsWith(Constants.FILE_EXTENSION_PGP_ALTERNATE)) { - return name.substring(0, name.length() - 4); + if (mCurrentInputUri == null) { + cryptoOperation(); } - return name; } private void askForOutputFilename(Uri inputUri, String originalFilename, String mimeType) { @@ -249,17 +270,13 @@ public class DecryptListFragment } @Override - protected void cryptoOperation(CryptoInputParcel cryptoInput) { - super.cryptoOperation(cryptoInput, false); - } - - @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { mAdapter.setProgress(mCurrentInputUri, progress, max, msg); return true; } + @Override - public void onCryptoOperationError(DecryptVerifyResult result) { + public void onQueuedOperationError(DecryptVerifyResult result) { final Uri uri = mCurrentInputUri; mCurrentInputUri = null; @@ -269,7 +286,7 @@ public class DecryptListFragment } @Override - public void onCryptoOperationSuccess(DecryptVerifyResult result) { + public void onQueuedOperationSuccess(DecryptVerifyResult result) { Uri uri = mCurrentInputUri; mCurrentInputUri = null; @@ -278,6 +295,25 @@ public class DecryptListFragment cryptoOperation(); } + @Override + public void onCryptoOperationCancelled() { + super.onCryptoOperationCancelled(); + + final Uri uri = mCurrentInputUri; + mCurrentInputUri = null; + + mCancelledInputUris.add(uri); + mAdapter.setCancelled(uri, new OnClickListener() { + @Override + public void onClick(View v) { + retryUri(uri); + } + }); + + cryptoOperation(); + + } + private void processResult(final Uri uri, final DecryptVerifyResult result) { new AsyncTask<Void, Void, Drawable>() { @@ -304,7 +340,7 @@ public class DecryptListFragment } final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setType(type); + intent.setDataAndType(outputUri, type); final List<ResolveInfo> matches = context.getPackageManager().queryIntentActivities(intent, 0); @@ -361,6 +397,22 @@ public class DecryptListFragment } + public void retryUri(Uri uri) { + + // never interrupt running operations! + if (mCurrentInputUri != null) { + return; + } + + // un-cancel this one + mCancelledInputUris.remove(uri); + mPendingInputUris.add(uri); + mAdapter.setCancelled(uri, null); + + cryptoOperation(); + + } + public void displayWithViewIntent(final Uri uri) { Activity activity = getActivity(); if (activity == null || mCurrentInputUri != null) { @@ -539,12 +591,14 @@ public class DecryptListFragment int mProgress, mMax; String mProgressMsg; + OnClickListener mCancelled; ViewModel(Context context, Uri uri) { mContext = context; mInputUri = uri; mProgress = 0; mMax = 100; + mCancelled = null; } void addResult(DecryptVerifyResult result) { @@ -564,6 +618,10 @@ public class DecryptListFragment return mResult != null; } + void setCancelled(OnClickListener retryListener) { + mCancelled = retryListener; + } + void setProgress(int progress, int max, String msg) { if (msg != null) { mProgressMsg = msg; @@ -621,6 +679,11 @@ public class DecryptListFragment // - replace the contents of the view with that element final ViewModel model = mDataset.get(position); + if (model.mCancelled != null) { + bindItemCancelled(holder, model); + return; + } + if (!model.hasResult()) { bindItemProgress(holder, model); return; @@ -634,6 +697,14 @@ public class DecryptListFragment } + private void bindItemCancelled(ViewHolder holder, ViewModel model) { + if (holder.vAnimator.getDisplayedChild() != 3) { + holder.vAnimator.setDisplayedChild(3); + } + + holder.vCancelledRetry.setOnClickListener(model.mCancelled); + } + private void bindItemProgress(ViewHolder holder, ViewModel model) { if (holder.vAnimator.getDisplayedChild() != 0) { holder.vAnimator.setDisplayedChild(0); @@ -749,6 +820,13 @@ public class DecryptListFragment notifyItemChanged(pos); } + public void setCancelled(Uri uri, OnClickListener retryListener) { + ViewModel newModel = new ViewModel(mContext, uri); + int pos = mDataset.indexOf(newModel); + mDataset.get(pos).setCancelled(retryListener); + notifyItemChanged(pos); + } + public void addResult(Uri uri, DecryptVerifyResult result, Drawable icon, OnClickListener onFileClick, OnClickListener onKeyClick) { @@ -796,6 +874,8 @@ public class DecryptListFragment public TextView vErrorMsg; public ImageView vErrorViewLog; + public ImageView vCancelledRetry; + public ViewHolder(View itemView) { super(itemView); @@ -824,6 +904,8 @@ public class DecryptListFragment vErrorMsg = (TextView) itemView.findViewById(R.id.result_error_msg); vErrorViewLog = (ImageView) itemView.findViewById(R.id.result_error_log); + vCancelledRetry = (ImageView) itemView.findViewById(R.id.cancel_retry); + } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java new file mode 100644 index 000000000..b22053df1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DeleteKeyDialogActivity.java @@ -0,0 +1,427 @@ +/* + * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * 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.support.v7.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.AdapterView; +import android.widget.Spinner; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.DeleteResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.operations.results.RevokeResult; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.DeleteKeyringParcel; +import org.sufficientlysecure.keychain.service.RevokeKeyringParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; +import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.Date; +import java.util.HashMap; + +public class DeleteKeyDialogActivity extends FragmentActivity { + public static final String EXTRA_DELETE_MASTER_KEY_IDS = "extra_delete_master_key_ids"; + public static final String EXTRA_HAS_SECRET = "extra_has_secret"; + public static final String EXTRA_KEYSERVER = "extra_keyserver"; + + private CryptoOperationHelper<DeleteKeyringParcel, DeleteResult> mDeleteOpHelper; + private CryptoOperationHelper<RevokeKeyringParcel, RevokeResult> mRevokeOpHelper; + + private long[] mMasterKeyIds; + private boolean mHasSecret; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mDeleteOpHelper = new CryptoOperationHelper<>(1, DeleteKeyDialogActivity.this, + getDeletionCallback(), R.string.progress_deleting); + + mRevokeOpHelper = new CryptoOperationHelper<>(2, this, + getRevocationCallback(), R.string.progress_revoking_uploading); + + mMasterKeyIds = getIntent().getLongArrayExtra(EXTRA_DELETE_MASTER_KEY_IDS); + mHasSecret = getIntent().getBooleanExtra(EXTRA_HAS_SECRET, false); + + if (mMasterKeyIds.length > 1 && mHasSecret) { + // secret keys can only be deleted individually + OperationResult.OperationLog log = new OperationResult.OperationLog(); + log.add(OperationResult.LogType.MSG_DEL_ERROR_MULTI_SECRET, 0); + returnResult(new DeleteResult(OperationResult.RESULT_ERROR, log, 0, + mMasterKeyIds.length)); + } + + if (mMasterKeyIds.length == 1 && mHasSecret) { + // if mMasterKeyIds.length == 0 we let the DeleteOperation respond + try { + HashMap<String, Object> data = new ProviderHelper(this).getUnifiedData( + mMasterKeyIds[0], new String[]{ + KeychainContract.KeyRings.USER_ID, + KeychainContract.KeyRings.IS_REVOKED + }, new int[]{ + ProviderHelper.FIELD_TYPE_STRING, + ProviderHelper.FIELD_TYPE_INTEGER + } + ); + + String name; + KeyRing.UserId mainUserId = KeyRing.splitUserId( + (String) data.get(KeychainContract.KeyRings.USER_ID)); + if (mainUserId.name != null) { + name = mainUserId.name; + } else { + name = getString(R.string.user_id_no_name); + } + + if ((long) data.get(KeychainContract.KeyRings.IS_REVOKED) > 0) { + showNormalDeleteDialog(); + } else { + showRevokeDeleteDialog(name); + } + } catch (ProviderHelper.NotFoundException e) { + Log.e(Constants.TAG, + "Secret key to delete not found at DeleteKeyDialogActivity for " + + mMasterKeyIds[0], e); + finish(); + } + } else { + showNormalDeleteDialog(); + } + } + + private void showNormalDeleteDialog() { + + DeleteKeyDialogFragment deleteKeyDialogFragment + = DeleteKeyDialogFragment.newInstance(mMasterKeyIds, mHasSecret); + + deleteKeyDialogFragment.show(getSupportFragmentManager(), "deleteKeyDialog"); + + } + + private void showRevokeDeleteDialog(String keyname) { + + RevokeDeleteDialogFragment fragment = RevokeDeleteDialogFragment.newInstance(keyname); + fragment.show(getSupportFragmentManager(), "deleteRevokeDialog"); + } + + private void startRevocationOperation() { + mRevokeOpHelper.cryptoOperation(new CryptoInputParcel(new Date(), false)); + } + + private void startDeletionOperation() { + mDeleteOpHelper.cryptoOperation(); + } + + private CryptoOperationHelper.Callback<RevokeKeyringParcel, RevokeResult> getRevocationCallback() { + + return new CryptoOperationHelper.Callback<RevokeKeyringParcel, RevokeResult>() { + @Override + public RevokeKeyringParcel createOperationInput() { + return new RevokeKeyringParcel(mMasterKeyIds[0], true, + getIntent().getStringExtra(EXTRA_KEYSERVER)); + } + + @Override + public void onCryptoOperationSuccess(RevokeResult result) { + returnResult(result); + } + + @Override + public void onCryptoOperationCancelled() { + setResult(RESULT_CANCELED); + finish(); + } + + @Override + public void onCryptoOperationError(RevokeResult result) { + returnResult(result); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; + } + + private CryptoOperationHelper.Callback<DeleteKeyringParcel, DeleteResult> getDeletionCallback() { + + return new CryptoOperationHelper.Callback<DeleteKeyringParcel, DeleteResult>() { + @Override + public DeleteKeyringParcel createOperationInput() { + return new DeleteKeyringParcel(mMasterKeyIds, mHasSecret); + } + + @Override + public void onCryptoOperationSuccess(DeleteResult result) { + returnResult(result); + } + + @Override + public void onCryptoOperationCancelled() { + setResult(RESULT_CANCELED); + finish(); + } + + @Override + public void onCryptoOperationError(DeleteResult result) { + returnResult(result); + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; + } + + private void returnResult(OperationResult result) { + Intent intent = new Intent(); + intent.putExtra(OperationResult.EXTRA_RESULT, result); + setResult(RESULT_OK, intent); + finish(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + mDeleteOpHelper.handleActivityResult(requestCode, resultCode, data); + mRevokeOpHelper.handleActivityResult(requestCode, resultCode, data); + } + + public static class DeleteKeyDialogFragment extends DialogFragment { + + private static final String ARG_DELETE_MASTER_KEY_IDS = "delete_master_key_ids"; + private static final String ARG_HAS_SECRET = "has_secret"; + + private TextView mMainMessage; + private View mInflateView; + + /** + * Creates new instance of this delete file dialog fragment + */ + public static DeleteKeyDialogFragment newInstance(long[] masterKeyIds, boolean hasSecret) { + DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment(); + Bundle args = new Bundle(); + + args.putLongArray(ARG_DELETE_MASTER_KEY_IDS, masterKeyIds); + args.putBoolean(ARG_HAS_SECRET, hasSecret); + + frag.setArguments(args); + + return frag; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final FragmentActivity activity = getActivity(); + + final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS); + final boolean hasSecret = getArguments().getBoolean(ARG_HAS_SECRET); + + ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); + + CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme); + + // Setup custom View to display in AlertDialog + LayoutInflater inflater = LayoutInflater.from(theme); + mInflateView = inflater.inflate(R.layout.view_key_delete_fragment, null); + builder.setView(mInflateView); + + mMainMessage = (TextView) mInflateView.findViewById(R.id.mainMessage); + + // If only a single key has been selected + if (masterKeyIds.length == 1) { + long masterKeyId = masterKeyIds[0]; + + try { + HashMap<String, Object> data = new ProviderHelper(activity).getUnifiedData( + masterKeyId, new String[]{ + KeychainContract.KeyRings.USER_ID, + KeychainContract.KeyRings.HAS_ANY_SECRET + }, new int[]{ + ProviderHelper.FIELD_TYPE_STRING, + ProviderHelper.FIELD_TYPE_INTEGER + } + ); + String name; + KeyRing.UserId mainUserId = KeyRing.splitUserId((String) data.get(KeychainContract.KeyRings.USER_ID)); + if (mainUserId.name != null) { + name = mainUserId.name; + } else { + name = getString(R.string.user_id_no_name); + } + + if (hasSecret) { + // show title only for secret key deletions, + // see http://www.google.com/design/spec/components/dialogs.html#dialogs-behavior + builder.setTitle(getString(R.string.title_delete_secret_key, name)); + mMainMessage.setText(getString(R.string.secret_key_deletion_confirmation, name)); + } else { + mMainMessage.setText(getString(R.string.public_key_deletetion_confirmation, name)); + } + } catch (ProviderHelper.NotFoundException e) { + dismiss(); + return null; + } + } else { + mMainMessage.setText(R.string.key_deletion_confirmation_multi); + } + + builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + + ((DeleteKeyDialogActivity) getActivity()).startDeletionOperation(); + } + }); + + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + + return builder.show(); + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + getActivity().setResult(RESULT_CANCELED); + getActivity().finish(); + } + } + + public static class RevokeDeleteDialogFragment extends DialogFragment { + + public static final String ARG_KEY_NAME = "arg_key_name"; + + public static RevokeDeleteDialogFragment newInstance(String keyName) { + Bundle args = new Bundle(); + args.putString(ARG_KEY_NAME, keyName); + RevokeDeleteDialogFragment frag = new RevokeDeleteDialogFragment(); + frag.setArguments(args); + return frag; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Activity activity = getActivity(); + + final String CHOICE_REVOKE = getString(R.string.del_rev_dialog_choice_rev_upload); + final String CHOICE_DELETE = getString(R.string.del_rev_dialog_choice_delete); + + ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); + + CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme); + builder.setTitle(getString(R.string.del_rev_dialog_title, + getArguments().get(ARG_KEY_NAME))); + + LayoutInflater inflater = LayoutInflater.from(theme); + View view = inflater.inflate(R.layout.del_rev_dialog, null); + builder.setView(view); + + final Spinner spinner = (Spinner) view.findViewById(R.id.spinner); + + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + + builder.setPositiveButton(R.string.del_rev_dialog_btn_revoke, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + + String choice = spinner.getSelectedItem().toString(); + if (choice.equals(CHOICE_REVOKE)) { + ((DeleteKeyDialogActivity) activity) + .startRevocationOperation(); + } else if (choice.equals(CHOICE_DELETE)) { + ((DeleteKeyDialogActivity) activity) + .showNormalDeleteDialog(); + } else { + throw new AssertionError( + "Unsupported delete type in RevokeDeleteDialogFragment"); + } + } + }); + + final AlertDialog alertDialog = builder.show(); + + spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) { + + String choice = parent.getItemAtPosition(pos).toString(); + + if (choice.equals(CHOICE_REVOKE)) { + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) + .setText(R.string.del_rev_dialog_btn_revoke); + } else if (choice.equals(CHOICE_DELETE)) { + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) + .setText(R.string.del_rev_dialog_btn_delete); + } else { + throw new AssertionError( + "Unsupported delete type in RevokeDeleteDialogFragment"); + } + } + + public void onNothingSelected(AdapterView<?> parent) { + } + }); + + return alertDialog; + } + + @Override + public void onCancel(DialogInterface dialog) { + super.onCancel(dialog); + getActivity().setResult(RESULT_CANCELED); + getActivity().finish(); + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java index be21cdde1..3c8e972b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java @@ -41,13 +41,7 @@ public class DisplayTextActivity extends BaseActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setFullScreenDialogClose(new View.OnClickListener() { - @Override - public void onClick(View v) { - setResult(Activity.RESULT_CANCELED); - finish(); - } - }, false); + setFullScreenDialogClose(Activity.RESULT_CANCELED, false); // Handle intent actions handleActions(savedInstanceState, getIntent()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java index 7b3af48cc..dc06e9115 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java @@ -17,6 +17,10 @@ package org.sufficientlysecure.keychain.ui; +import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; @@ -29,9 +33,9 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.ShareHelper; public class DisplayTextFragment extends DecryptFragment { @@ -80,8 +84,19 @@ public class DisplayTextFragment extends DecryptFragment { } private void copyToClipboard(String text) { - ClipboardReflection.copyToClipboard(getActivity(), text); - Notify.create(getActivity(), R.string.text_copied_to_clipboard, Notify.Style.OK).show(); + Activity activity = getActivity(); + if (activity == null) { + return; + } + + ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + if (clipMan == null) { + Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR).show(); + return; + } + + clipMan.setPrimaryClip(ClipData.newPlainText(Constants.CLIPBOARD_LABEL, text)); + Notify.create(activity, R.string.text_copied_to_clipboard, Style.OK).show(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index 1363d44f2..4769e68d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -57,7 +57,7 @@ import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.SubkeysAddedAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; import org.sufficientlysecure.keychain.ui.adapter.UserIdsAddedAdapter; -import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment; import org.sufficientlysecure.keychain.ui.dialog.EditSubkeyDialogFragment; @@ -68,7 +68,9 @@ import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; -public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel, OperationResult> +import java.util.Date; + +public class EditKeyFragment extends QueueingCryptoOperationFragment<SaveKeyringParcel, OperationResult> implements LoaderManager.LoaderCallbacks<Cursor> { public static final String ARG_DATA_URI = "uri"; @@ -151,7 +153,7 @@ public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel, if (mDataUri == null) { returnKeyringParcel(); } else { - cryptoOperation(new CryptoInputParcel()); + cryptoOperation(new CryptoInputParcel(new Date())); } } }, new OnClickListener() { @@ -192,7 +194,7 @@ public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel, private void loadData(Uri dataUri) { mDataUri = dataUri; - Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); + 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 { @@ -437,44 +439,50 @@ public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel, } break; } - case EditSubkeyDialogFragment.MESSAGE_KEYTOCARD: { - 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; - } + 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(); @@ -612,13 +620,16 @@ public class EditKeyFragment extends CryptoOperationFragment<SaveKeyringParcel, } @Override - public void onCryptoOperationSuccess(OperationResult result) { + public void onQueuedOperationSuccess(OperationResult result) { + + // null-protected from Queueing*Fragment + Activity activity = getActivity(); // if good -> finish, return result to showkey and display there! Intent intent = new Intent(); intent.putExtra(OperationResult.EXTRA_RESULT, result); - getActivity().setResult(EditKeyActivity.RESULT_OK, intent); - getActivity().finish(); + activity.setResult(EditKeyActivity.RESULT_OK, intent); + activity.finish(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java index 779b22535..fc72a6c9f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptDecryptOverviewFragment.java @@ -124,8 +124,7 @@ public class EncryptDecryptOverviewFragment extends Fragment { super.onResume(); // get text from clipboard - final CharSequence clipboardText = - ClipboardReflection.getClipboardText(getActivity()); + final CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity()); // if it's null, nothing to do here /o/ if (clipboardText == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java index d51b6e0ff..136787984 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesActivity.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; +import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -41,12 +42,7 @@ public class EncryptFilesActivity extends EncryptActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setFullScreenDialogClose(new View.OnClickListener() { - @Override - public void onClick(View v) { - finish(); - } - }, false); + setFullScreenDialogClose(Activity.RESULT_OK, false); Intent intent = getIntent(); String action = intent.getAction(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index 215af5885..3dc93872d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.ui; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -35,6 +36,7 @@ import android.graphics.Point; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.support.v4.app.FragmentActivity; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -56,6 +58,7 @@ import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.PgpConstants; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment; @@ -276,18 +279,21 @@ public class EncryptFilesFragment public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.encrypt_save: { + hideKeyboard(); mAfterEncryptAction = AfterEncryptAction.SAVE; - cryptoOperation(); + cryptoOperation(new CryptoInputParcel(new Date())); break; } case R.id.encrypt_share: { + hideKeyboard(); mAfterEncryptAction = AfterEncryptAction.SHARE; - cryptoOperation(); + cryptoOperation(new CryptoInputParcel(new Date())); break; } case R.id.encrypt_copy: { + hideKeyboard(); mAfterEncryptAction = AfterEncryptAction.COPY; - cryptoOperation(); + cryptoOperation(new CryptoInputParcel(new Date())); break; } case R.id.check_use_armor: { @@ -386,7 +392,13 @@ public class EncryptFilesFragment } @Override - public void onCryptoOperationSuccess(final SignEncryptResult result) { + public void onQueuedOperationSuccess(final SignEncryptResult result) { + super.onQueuedOperationSuccess(result); + + hideKeyboard(); + + // protected by Queueing*Fragment + FragmentActivity activity = getActivity(); if (mDeleteAfterEncrypt) { // TODO make behavior coherent here @@ -400,13 +412,18 @@ public class EncryptFilesFragment // Share encrypted message/file startActivity(sendWithChooserExcludingEncrypt()); } else { + Activity activity = getActivity(); + if (activity == null) { + // it's gone, there's nothing we can do here + return; + } // Save encrypted file - result.createNotify(getActivity()).show(); + result.createNotify(activity).show(); } } }); - deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); + deleteFileDialog.show(activity.getSupportFragmentManager(), "deleteDialog"); } else { switch (mAfterEncryptAction) { @@ -417,25 +434,24 @@ public class EncryptFilesFragment break; case COPY: - Activity activity = getActivity(); - if (activity == null) { - // it's gone, there's nothing we can do here - return; - } ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + if (clipMan == null) { + Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR).show(); + break; + } ClipData clip = new ClipData(getString(R.string.label_clip_title), // make available as application/pgp-encrypted new String[] { "text/plain" }, new ClipData.Item(mOutputUris.get(0)) ); clipMan.setPrimaryClip(clip); - result.createNotify(getActivity()).show(); + result.createNotify(activity).show(); break; case SAVE: // Encrypted file was saved already, just show notification - result.createNotify(getActivity()).show(); + result.createNotify(activity).show(); break; } } @@ -652,7 +668,7 @@ public class EncryptFilesFragment mOutputUris.add(data.getData()); // make sure this is correct at this point mAfterEncryptAction = AfterEncryptAction.SAVE; - cryptoOperation(); + cryptoOperation(new CryptoInputParcel(new Date())); } return; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java index cb26ea452..a849cdf12 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextActivity.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; +import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.FragmentTransaction; @@ -39,12 +40,7 @@ public class EncryptTextActivity extends EncryptActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setFullScreenDialogClose(new View.OnClickListener() { - @Override - public void onClick(View v) { - finish(); - } - }, false); + setFullScreenDialogClose(Activity.RESULT_OK, false); Intent intent = getIntent(); String action = intent.getAction(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java index 886c52651..32257eba5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptTextFragment.java @@ -18,6 +18,9 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.text.Editable; @@ -33,11 +36,11 @@ import android.widget.TextView; import org.spongycastle.bcpg.CompressionAlgorithmTags; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.operations.results.SignEncryptResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.pgp.PgpConstants; import org.sufficientlysecure.keychain.pgp.SignEncryptParcel; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.ui.base.CachingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener; @@ -46,6 +49,7 @@ import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.ShareHelper; +import java.util.Date; import java.util.HashSet; import java.util.Set; @@ -164,13 +168,15 @@ public class EncryptTextFragment // break; // } case R.id.encrypt_copy: { + hideKeyboard(); mShareAfterEncrypt = false; - cryptoOperation(); + cryptoOperation(new CryptoInputParcel(new Date())); break; } case R.id.encrypt_share: { + hideKeyboard(); mShareAfterEncrypt = true; - cryptoOperation(); + cryptoOperation(new CryptoInputParcel(new Date())); break; } default: { @@ -265,8 +271,21 @@ public class EncryptTextFragment return data; } - private void copyToClipboard(byte[] resultBytes) { - ClipboardReflection.copyToClipboard(getActivity(), new String(resultBytes)); + private void copyToClipboard(SignEncryptResult result) { + Activity activity = getActivity(); + if (activity == null) { + return; + } + + ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + if (clipMan == null) { + Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR).show(); + return; + } + + ClipData clip = ClipData.newPlainText(Constants.CLIPBOARD_LABEL, new String(result.getResultBytes())); + clipMan.setPrimaryClip(clip); + result.createNotify(activity).show(); } /** @@ -316,18 +335,17 @@ public class EncryptTextFragment } @Override - public void onCryptoOperationSuccess(SignEncryptResult result) { + public void onQueuedOperationSuccess(SignEncryptResult result) { + super.onQueuedOperationSuccess(result); + + hideKeyboard(); if (mShareAfterEncrypt) { // Share encrypted message/file startActivity(sendWithChooserExcludingEncrypt(result.getResultBytes())); } else { // Copy to clipboard - copyToClipboard(result.getResultBytes()); - result.createNotify(getActivity()).show(); - // Notify.create(EncryptTextActivity.this, - // R.string.encrypt_sign_clipboard_successful, Notify.Style.OK) - // .show(getSupportFragmentManager().findFragmentById(R.id.encrypt_text_fragment)); + copyToClipboard(result); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java index ac4b94d64..acbb695df 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpAboutFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2012-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 @@ -51,7 +51,7 @@ public class HelpAboutFragment extends Fragment { try { String html = new Markdown4jProcessor().process( getActivity().getResources().openRawResource(R.raw.help_about)); - aboutTextView.setHtmlFromString(html, true); + aboutTextView.setHtmlFromString(html, new HtmlTextView.LocalImageGetter()); } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java index 97d39feb1..73ce7e03e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/HelpMarkdownFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2012-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 @@ -17,7 +17,6 @@ package org.sufficientlysecure.keychain.ui; -import android.app.Activity; import android.os.Bundle; import android.support.v4.app.Fragment; import android.util.TypedValue; @@ -34,9 +33,6 @@ import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; public class HelpMarkdownFragment extends Fragment { - private Activity mActivity; - - private int mHtmlFile; public static final String ARG_MARKDOWN_RES = "htmlFile"; @@ -56,15 +52,13 @@ public class HelpMarkdownFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - mActivity = getActivity(); - - mHtmlFile = getArguments().getInt(ARG_MARKDOWN_RES); + int mHtmlFile = getArguments().getInt(ARG_MARKDOWN_RES); - ScrollView scroller = new ScrollView(mActivity); - HtmlTextView text = new HtmlTextView(mActivity); + ScrollView scroller = new ScrollView(getActivity()); + HtmlTextView text = new HtmlTextView(getActivity()); // padding - int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, mActivity + int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, getActivity() .getResources().getDisplayMetrics()); text.setPadding(padding, padding, padding, 0); @@ -72,8 +66,9 @@ public class HelpMarkdownFragment extends Fragment { // load markdown from raw resource try { - String html = new Markdown4jProcessor().process(getActivity().getResources().openRawResource(mHtmlFile)); - text.setHtmlFromString(html, true); + String html = new Markdown4jProcessor().process( + getActivity().getResources().openRawResource(mHtmlFile)); + text.setHtmlFromString(html, new HtmlTextView.LocalImageGetter()); } catch (IOException e) { Log.e(Constants.TAG, "IOException", e); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index ba4f759e6..4ef6c40dc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain.ui; +import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -92,6 +93,7 @@ public class ImportKeysActivity extends BaseNfcActivity protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setFullScreenDialogClose(Activity.RESULT_CANCELED, true); mImportButton = findViewById(R.id.import_import); mImportButton.setOnClickListener(new OnClickListener() { @Override @@ -224,9 +226,9 @@ public class ImportKeysActivity extends BaseNfcActivity Notify.Style.WARN).show(mTopFragment); // we just set the keyserver startCloudFragment(savedInstanceState, null, false, keyserver); - // it's not necessary to set the keyserver for ImportKeysListFragment since - // it'll be taken care of by ImportKeysCloudFragment when the user clicks - // the search button + // we don't set the keyserver for ImportKeysListFragment since + // it'll be set in the cloudSearchPrefs of ImportKeysCloudFragment + // which is used when the user clicks on the search button startListFragment(savedInstanceState, null, null, null, null); } else { // we allow our users to edit the query if they wish @@ -316,7 +318,8 @@ public class ImportKeysActivity extends BaseNfcActivity * specified in user preferences */ - private void startCloudFragment(Bundle savedInstanceState, String query, boolean disableQueryEdit, String keyserver) { + private void startCloudFragment(Bundle savedInstanceState, String query, boolean disableQueryEdit, String + keyserver) { // 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. @@ -346,7 +349,7 @@ public class ImportKeysActivity extends BaseNfcActivity } } - public void loadCallback(ImportKeysListFragment.LoaderState loaderState) { + public void loadCallback(final ImportKeysListFragment.LoaderState loaderState) { mListFragment.loadNew(loaderState); } @@ -395,7 +398,7 @@ public class ImportKeysActivity extends BaseNfcActivity } mOperationHelper = new CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult>( - this, this, R.string.progress_importing + 1, this, this, R.string.progress_importing ); ImportKeysListFragment.LoaderState ls = mListFragment.getLoaderState(); @@ -448,10 +451,8 @@ public class ImportKeysActivity extends BaseNfcActivity } @Override - protected void onNfcPerform() throws IOException { - // this displays the key or moves to the yubikey import dialogue. - super.onNfcPerform(); - // either way, finish afterwards + protected void onNfcPostExecute() throws IOException { + // either way, finish after NFC AsyncTask finish(); } @@ -463,7 +464,7 @@ public class ImportKeysActivity extends BaseNfcActivity } } - public void handleResult (ImportKeyResult result) { + public void handleResult(ImportKeyResult result) { if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(getIntent().getAction()) || ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(getIntent().getAction())) { Intent intent = new Intent(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index bf7e41045..53e5efabe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import android.support.v4.app.ListFragment; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; @@ -34,6 +35,7 @@ import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.GetKeyResult; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter; import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListCloudLoader; @@ -41,7 +43,9 @@ import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader; import org.sufficientlysecure.keychain.util.InputData; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize; +import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; @@ -62,6 +66,7 @@ public class ImportKeysListFragment extends ListFragment implements private Activity mActivity; private ImportKeysAdapter mAdapter; + private ParcelableProxy mParcelableProxy; private LoaderState mLoaderState; @@ -71,6 +76,8 @@ public class ImportKeysListFragment extends ListFragment implements private LongSparseArray<ParcelableKeyRing> mCachedKeyData; private boolean mNonInteractive; + private boolean mShowingOrbotDialog; + public LoaderState getLoaderState() { return mLoaderState; } @@ -126,6 +133,7 @@ public class ImportKeysListFragment extends ListFragment implements /** * Creates an interactive ImportKeyListFragment which reads keyrings from bytes, or file specified * by dataUri, or searches a keyserver for serverQuery, if parameter is not null, in that order + * Will immediately load data if non-null bytes/dataUri/serverQuery * * @param bytes byte data containing list of keyrings to be imported * @param dataUri file from which keyrings are to be imported @@ -141,7 +149,7 @@ public class ImportKeysListFragment extends ListFragment implements /** * Visually consists of a list of keyrings with checkboxes to specify which are to be imported - * Can immediately load keyrings specified by any of its parameters + * Will immediately load data if non-null bytes/dataUri/serverQuery is supplied * * @param bytes byte data containing list of keyrings to be imported * @param dataUri file from which keyrings are to be imported @@ -259,6 +267,7 @@ public class ImportKeysListFragment extends ListFragment implements } public void loadNew(LoaderState loaderState) { + mLoaderState = loaderState; restartLoaders(); @@ -301,7 +310,8 @@ public class ImportKeysListFragment extends ListFragment implements } case LOADER_ID_CLOUD: { CloudLoaderState ls = (CloudLoaderState) mLoaderState; - return new ImportKeysListCloudLoader(getActivity(), ls.mServerQuery, ls.mCloudPrefs); + return new ImportKeysListCloudLoader(getActivity(), ls.mServerQuery, ls.mCloudPrefs, + mParcelableProxy); } default: @@ -349,6 +359,46 @@ public class ImportKeysListFragment extends ListFragment implements if (getKeyResult.success()) { // No error + } else if (getKeyResult.isPending()) { + if (getKeyResult.getRequiredInputParcel().mType == + RequiredInputParcel.RequiredInputType.ENABLE_ORBOT) { + if (mShowingOrbotDialog) { + // to prevent dialogs stacking + return; + } + + // this is because we can't commit fragment dialogs in onLoadFinished + Runnable showOrbotDialog = new Runnable() { + @Override + public void run() { + final Runnable ignoreTor = new Runnable() { + @Override + public void run() { + mParcelableProxy = ParcelableProxy + .getForNoProxy(); + mShowingOrbotDialog = false; + restartLoaders(); + } + }; + + final Runnable dialogDismiss = new Runnable() { + @Override + public void run() { + mShowingOrbotDialog = false; + } + }; + + if (OrbotHelper.putOrbotInRequiredState( + ignoreTor, dialogDismiss, getActivity())) { + // looks like we didn't have to show the + // dialog after all + restartLoaders(); + } + } + }; + new Handler().post(showOrbotDialog ); + mShowingOrbotDialog = true; + } } else { getKeyResult.createNotify(getActivity()).show(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java index da713d0d8..b60f3984c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysProxyActivity.java @@ -110,7 +110,16 @@ public class ImportKeysProxyActivity extends FragmentActivity @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (mImportOpHelper != null) { - mImportOpHelper.cryptoOperation(); + if (!mImportOpHelper.handleActivityResult(requestCode, resultCode, data)) { + // if a result has been returned, and it does not belong to mImportOpHelper, + // return it down to other activity + if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { + returnResult(data); + } else { + super.onActivityResult(requestCode, resultCode, data); + finish(); + } + } } if (requestCode == IntentIntegratorSupportV4.REQUEST_CODE) { @@ -128,13 +137,6 @@ public class ImportKeysProxyActivity extends FragmentActivity return; } - // if a result has been returned, return it down to other activity - if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { - returnResult(data); - } else { - super.onActivityResult(requestCode, resultCode, data); - finish(); - } } private void processScannedContent(String content) { @@ -157,8 +159,7 @@ public class ImportKeysProxyActivity extends FragmentActivity returnResult(intent); return; } - - String fingerprint = uri.getEncodedSchemeSpecificPart().toLowerCase(Locale.ENGLISH); + final String fingerprint = uri.getEncodedSchemeSpecificPart().toLowerCase(Locale.ENGLISH); if (!fingerprint.matches("[a-fA-F0-9]{40}")) { SingletonResult result = new SingletonResult( SingletonResult.RESULT_ERROR, LogType.MSG_WRONG_QR_CODE_FP); @@ -222,7 +223,7 @@ public class ImportKeysProxyActivity extends FragmentActivity mKeyList = keyRings; - mImportOpHelper = new CryptoOperationHelper<>(this, this, R.string.progress_importing); + mImportOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_importing); mImportOpHelper.cryptoOperation(); } @@ -256,7 +257,6 @@ public class ImportKeysProxyActivity extends FragmentActivity Intent data = new Intent(); data.putExtras(returnData); returnResult(data); - return; } @Override @@ -273,7 +273,8 @@ public class ImportKeysProxyActivity extends FragmentActivity // only one message sent during the beam NdefMessage msg = (NdefMessage) rawMsgs[0]; // record 0 contains the MIME type, record 1 is the AAR, if present - byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload(); + final byte[] receivedKeyringBytes = msg.getRecords()[0].getPayload(); + importKeys(receivedKeyringBytes); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java index e038cf948..0488d8235 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java @@ -18,6 +18,11 @@ package org.sufficientlysecure.keychain.ui; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; + import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.app.Activity; @@ -28,9 +33,6 @@ import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -51,12 +53,10 @@ import android.widget.TextView; import com.getbase.floatingactionbutton.FloatingActionButton; import com.getbase.floatingactionbutton.FloatingActionsMenu; - import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; import org.sufficientlysecure.keychain.operations.results.ConsolidateResult; -import org.sufficientlysecure.keychain.operations.results.DeleteResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.provider.KeychainContract; @@ -65,22 +65,15 @@ import org.sufficientlysecure.keychain.provider.KeychainDatabase; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ConsolidateInputParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; -import org.sufficientlysecure.keychain.service.PassphraseCacheService; +import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; -import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; -import org.sufficientlysecure.keychain.util.ExportHelper; import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; - import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; import se.emilsjolander.stickylistheaders.StickyListHeadersListView; @@ -93,10 +86,9 @@ public class KeyListFragment extends LoaderFragment LoaderManager.LoaderCallbacks<Cursor>, FabContainer, CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> { - static final int REQUEST_REPEAT_PASSPHRASE = 1; - static final int REQUEST_ACTION = 2; - - ExportHelper mExportHelper; + static final int REQUEST_ACTION = 1; + private static final int REQUEST_DELETE = 2; + private static final int REQUEST_VIEW_KEY = 3; private KeyListAdapter mAdapter; private StickyListHeadersListView mStickyList; @@ -116,18 +108,6 @@ public class KeyListFragment extends LoaderFragment // for ConsolidateOperation private CryptoOperationHelper<ConsolidateInputParcel, ConsolidateResult> mConsolidateOpHelper; - // This ids for multiple key export. - private ArrayList<Long> mIdsForRepeatAskPassphrase; - // This index for remembering the number of master key. - private int mIndex; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mExportHelper = new ExportHelper(getActivity()); - } - /** * Load custom layout with StickyListView from library */ @@ -238,19 +218,7 @@ public class KeyListFragment extends LoaderFragment } case R.id.menu_key_list_multi_delete: { ids = mAdapter.getCurrentSelectedMasterKeyIds(); - showDeleteKeyDialog(mode, ids, mAdapter.isAnySecretSelected()); - break; - } - case R.id.menu_key_list_multi_export: { - ids = mAdapter.getCurrentSelectedMasterKeyIds(); - showMultiExportDialog(ids); - break; - } - case R.id.menu_key_list_multi_select_all: { - // select all - for (int i = 0; i < mAdapter.getCount(); i++) { - mStickyList.setItemChecked(i, true); - } + showDeleteKeyDialog(ids, mAdapter.isAnySecretSelected()); break; } } @@ -366,7 +334,7 @@ public class KeyListFragment extends LoaderFragment Intent viewIntent = new Intent(getActivity(), ViewKeyActivity.class); viewIntent.setData( KeyRings.buildGenericKeyRingUri(mAdapter.getMasterKeyId(position))); - startActivity(viewIntent); + startActivityForResult(viewIntent, REQUEST_VIEW_KEY); } protected void encrypt(ActionMode mode, long[] masterKeyIds) { @@ -384,38 +352,15 @@ public class KeyListFragment extends LoaderFragment * * @param hasSecret must contain whether the list of masterKeyIds contains a secret key or not */ - public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds, boolean hasSecret) { - // Can only work on singular secret keys - if (hasSecret && masterKeyIds.length > 1) { - Notify.create(getActivity(), R.string.secret_cannot_multiple, - Notify.Style.ERROR).show(); - return; + public void showDeleteKeyDialog(long[] masterKeyIds, boolean hasSecret) { + Intent intent = new Intent(getActivity(), DeleteKeyDialogActivity.class); + intent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, masterKeyIds); + intent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, hasSecret); + if (hasSecret) { + intent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER, + Preferences.getPreferences(getActivity()).getPreferredKeyserver()); } - - // Message is received after key is deleted - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.arg1 == DeleteKeyDialogFragment.MESSAGE_OKAY) { - Bundle data = message.getData(); - if (data != null) { - DeleteResult result = data.getParcelable(DeleteResult.EXTRA_RESULT); - if (result != null) { - result.createNotify(getActivity()).show(); - } - } - mode.finish(); - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - - DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger, - masterKeyIds); - - deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog"); + startActivityForResult(intent, REQUEST_DELETE); } @@ -470,18 +415,10 @@ public class KeyListFragment extends LoaderFragment createKey(); return true; - case R.id.menu_key_list_export: - mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true); - return true; - case R.id.menu_key_list_update_all_keys: updateAllKeys(); return true; - case R.id.menu_key_list_debug_cons: - consolidate(); - return true; - case R.id.menu_key_list_debug_read: try { KeychainDatabase.debugBackup(getActivity(), true); @@ -512,6 +449,10 @@ public class KeyListFragment extends LoaderFragment getActivity().finish(); return true; + case R.id.menu_key_list_debug_cons: + consolidate(); + return true; + default: return super.onOptionsItemSelected(item); } @@ -602,7 +543,7 @@ public class KeyListFragment extends LoaderFragment mKeyserver = cloudPrefs.keyserver; } - mImportOpHelper = new CryptoOperationHelper<>(this, + mImportOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_updating); mImportOpHelper.cryptoOperation(); } @@ -639,45 +580,11 @@ public class KeyListFragment extends LoaderFragment }; mConsolidateOpHelper = - new CryptoOperationHelper<>(this, callback, R.string.progress_importing); + new CryptoOperationHelper<>(2, this, callback, R.string.progress_importing); mConsolidateOpHelper.cryptoOperation(); } - private void showMultiExportDialog(long[] masterKeyIds) { - mIdsForRepeatAskPassphrase = new ArrayList<>(); - for (long id : masterKeyIds) { - try { - if (PassphraseCacheService.getCachedPassphrase( - getActivity(), id, id) == null) { - mIdsForRepeatAskPassphrase.add(id); - } - } catch (PassphraseCacheService.KeyNotFoundException e) { - // This happens when the master key is stripped - // and ignore this key. - } - } - mIndex = 0; - if (mIdsForRepeatAskPassphrase.size() != 0) { - startPassphraseActivity(); - return; - } - long[] idsForMultiExport = new long[mIdsForRepeatAskPassphrase.size()]; - for (int i = 0; i < mIdsForRepeatAskPassphrase.size(); ++i) { - idsForMultiExport[i] = mIdsForRepeatAskPassphrase.get(i); - } - mExportHelper.showExportKeysDialog(idsForMultiExport, - Constants.Path.APP_DIR_FILE, - mAdapter.isAnySecretSelected()); - } - - private void startPassphraseActivity() { - Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class); - long masterKeyId = mIdsForRepeatAskPassphrase.get(mIndex++); - intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, masterKeyId); - startActivityForResult(intent, REQUEST_REPEAT_PASSPHRASE); - } - @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mImportOpHelper != null) { @@ -688,31 +595,37 @@ public class KeyListFragment extends LoaderFragment mConsolidateOpHelper.handleActivityResult(requestCode, resultCode, data); } - if (requestCode == REQUEST_REPEAT_PASSPHRASE) { - if (resultCode != Activity.RESULT_OK) { - return; - } - if (mIndex < mIdsForRepeatAskPassphrase.size()) { - startPassphraseActivity(); - return; - } - long[] idsForMultiExport = new long[mIdsForRepeatAskPassphrase.size()]; - for (int i = 0; i < mIdsForRepeatAskPassphrase.size(); ++i) { - idsForMultiExport[i] = mIdsForRepeatAskPassphrase.get(i); - } - mExportHelper.showExportKeysDialog(idsForMultiExport, - Constants.Path.APP_DIR_FILE, - mAdapter.isAnySecretSelected()); - } + switch (requestCode) { + case REQUEST_DELETE: + if (mActionMode != null) { + mActionMode.finish(); + } + 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); + } + break; - if (requestCode == REQUEST_ACTION) { - // 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); - } + case REQUEST_ACTION: + // 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); + } + break; + + case REQUEST_VIEW_KEY: + 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); + } + break; } } @@ -763,8 +676,11 @@ public class KeyListFragment extends LoaderFragment private HashMap<Integer, Boolean> mSelection = new HashMap<>(); + private Context mContext; + public KeyListAdapter(Context context, Cursor c, int flags) { super(context, c, flags); + mContext = context; } @Override @@ -793,9 +709,11 @@ public class KeyListFragment extends LoaderFragment // let the adapter handle setting up the row views View v = super.getView(position, convertView, parent); + int colorEmphasis = FormattingUtils.getColorFromAttr(mContext, R.attr.colorEmphasis); + if (mSelection.get(position) != null) { // selected position color - v.setBackgroundColor(parent.getResources().getColor(R.color.emphasis)); + v.setBackgroundColor(colorEmphasis); } else { // default color v.setBackgroundColor(Color.TRANSPARENT); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java index 3c8fbf8c9..1cd1a3099 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java @@ -43,6 +43,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogLevel; import org.sufficientlysecure.keychain.operations.results.OperationResult.SubLogEntryParcel; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Log; @@ -57,10 +58,12 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe OperationResult mResult; public static final String EXTRA_RESULT = "log"; + protected int mTextColor; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mTextColor = FormattingUtils.getColorFromAttr(getActivity(), R.attr.colorText); setHasOptionsMenu(true); } @@ -357,13 +360,13 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe ih.mSecondText.setText(getResources().getString(subEntry.mType.getMsgId(), subEntry.mParameters)); } - ih.mSecondText.setTextColor(subEntry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : Color.BLACK); + ih.mSecondText.setTextColor(subEntry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : mTextColor); switch (subEntry.mType.mLevel) { case DEBUG: ih.mSecondImg.setBackgroundColor(Color.GRAY); break; - case INFO: ih.mSecondImg.setBackgroundColor(Color.BLACK); break; + case INFO: ih.mSecondImg.setBackgroundColor(mTextColor); break; case WARN: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); break; case ERROR: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; - case START: ih.mSecondImg.setBackgroundColor(Color.BLACK); break; + case START: ih.mSecondImg.setBackgroundColor(mTextColor); break; case OK: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_green_light)); break; case CANCELLED: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; } @@ -388,13 +391,13 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe entry.mParameters)); } convertView.setPadding((entry.mIndent) * dipFactor, 0, 0, 0); - ih.mText.setTextColor(entry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : Color.BLACK); + ih.mText.setTextColor(entry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : mTextColor); switch (entry.mType.mLevel) { case DEBUG: ih.mImg.setBackgroundColor(Color.GRAY); break; - case INFO: ih.mImg.setBackgroundColor(Color.BLACK); break; + case INFO: ih.mImg.setBackgroundColor(mTextColor); break; case WARN: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); break; case ERROR: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; - case START: ih.mImg.setBackgroundColor(Color.BLACK); break; + case START: ih.mImg.setBackgroundColor(mTextColor); break; case OK: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_green_light)); break; case CANCELLED: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java index ec6fd1bbe..6f5d98afd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java @@ -33,6 +33,7 @@ import com.mikepenz.community_material_typeface_library.CommunityMaterial; import com.mikepenz.google_material_typeface_library.GoogleMaterial; import com.mikepenz.iconics.typeface.FontAwesome; import com.mikepenz.materialdrawer.Drawer; +import com.mikepenz.materialdrawer.DrawerBuilder; import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; @@ -48,14 +49,15 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac static final int ID_KEYS = 1; static final int ID_ENCRYPT_DECRYPT = 2; static final int ID_APPS = 3; - static final int ID_SETTINGS = 4; - static final int ID_HELP = 5; + static final int ID_BACKUP = 4; + static final int ID_SETTINGS = 5; + static final int ID_HELP = 6; // both of these are used for instrumentation testing only public static final String EXTRA_SKIP_FIRST_TIME = "skip_first_time"; public static final String EXTRA_INIT_FRAG = "init_frag"; - public Drawer.Result mDrawerResult; + public Drawer mDrawer; private Toolbar mToolbar; @Override @@ -67,7 +69,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac mToolbar.setTitle(R.string.app_name); setSupportActionBar(mToolbar); - mDrawerResult = new Drawer() + mDrawer = new DrawerBuilder() .withActivity(this) .withHeader(R.layout.main_drawer_header) .withToolbar(mToolbar) @@ -77,7 +79,9 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac new PrimaryDrawerItem().withName(R.string.nav_encrypt_decrypt).withIcon(FontAwesome.Icon.faw_lock) .withIdentifier(ID_ENCRYPT_DECRYPT).withCheckable(false), new PrimaryDrawerItem().withName(R.string.title_api_registered_apps).withIcon(CommunityMaterial.Icon.cmd_apps) - .withIdentifier(ID_APPS).withCheckable(false) + .withIdentifier(ID_APPS).withCheckable(false), + new PrimaryDrawerItem().withName(R.string.nav_backup).withIcon(CommunityMaterial.Icon.cmd_backup_restore) + .withIdentifier(ID_BACKUP).withCheckable(false) ) .addStickyDrawerItems( // display and stick on bottom of drawer @@ -86,7 +90,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac ) .withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() { @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id, IDrawerItem drawerItem) { + public boolean onItemClick(AdapterView<?> parent, View view, int position, long id, IDrawerItem drawerItem) { if (drawerItem != null) { Intent intent = null; switch(drawerItem.getIdentifier()) { @@ -99,6 +103,9 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac case ID_APPS: onAppsSelected(); break; + case ID_BACKUP: + onBackupSelected(); + break; case ID_SETTINGS: intent = new Intent(MainActivity.this, SettingsActivity.class); break; @@ -110,6 +117,8 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac MainActivity.this.startActivity(intent); } } + + return false; } }) .withSelectedItem(-1) @@ -128,6 +137,11 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac getSupportFragmentManager().addOnBackStackChangedListener(this); + // all further initialization steps are saved as instance state + if (savedInstanceState != null) { + return; + } + Intent data = getIntent(); // If we got an EXTRA_RESULT in the intent, show the notification if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { @@ -135,20 +149,18 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac result.createNotify(this).show(); } - if (savedInstanceState == null) { - // always initialize keys fragment to the bottom of the backstack - onKeysSelected(); - - if (data != null && data.hasExtra(EXTRA_INIT_FRAG)) { - // initialize FragmentLayout with KeyListFragment at first - switch (data.getIntExtra(EXTRA_INIT_FRAG, -1)) { - case ID_ENCRYPT_DECRYPT: - onEnDecryptSelected(); - break; - case ID_APPS: - onAppsSelected(); - break; - } + // always initialize keys fragment to the bottom of the backstack + onKeysSelected(); + + if (data != null && data.hasExtra(EXTRA_INIT_FRAG)) { + // initialize FragmentLayout with KeyListFragment at first + switch (data.getIntExtra(EXTRA_INIT_FRAG, -1)) { + case ID_ENCRYPT_DECRYPT: + onEnDecryptSelected(); + break; + case ID_APPS: + onAppsSelected(); + break; } } @@ -170,37 +182,44 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac private void onKeysSelected() { mToolbar.setTitle(R.string.app_name); - mDrawerResult.setSelectionByIdentifier(ID_KEYS, false); + mDrawer.setSelectionByIdentifier(ID_KEYS, false); Fragment frag = new KeyListFragment(); setFragment(frag, false); } private void onEnDecryptSelected() { mToolbar.setTitle(R.string.nav_encrypt_decrypt); - mDrawerResult.setSelectionByIdentifier(ID_ENCRYPT_DECRYPT, false); + mDrawer.setSelectionByIdentifier(ID_ENCRYPT_DECRYPT, false); Fragment frag = new EncryptDecryptOverviewFragment(); setFragment(frag, true); } private void onAppsSelected() { mToolbar.setTitle(R.string.nav_apps); - mDrawerResult.setSelectionByIdentifier(ID_APPS, false); + mDrawer.setSelectionByIdentifier(ID_APPS, false); Fragment frag = new AppsListFragment(); setFragment(frag, true); } + private void onBackupSelected() { + mToolbar.setTitle(R.string.nav_backup); + mDrawer.setSelectionByIdentifier(ID_APPS, false); + Fragment frag = new BackupFragment(); + setFragment(frag, true); + } + @Override protected void onSaveInstanceState(Bundle outState) { // add the values which need to be saved from the drawer to the bundle - outState = mDrawerResult.saveInstanceState(outState); + outState = mDrawer.saveInstanceState(outState); super.onSaveInstanceState(outState); } @Override public void onBackPressed() { // close the drawer first and if the drawer is closed do regular backstack handling - if (mDrawerResult != null && mDrawerResult.isDrawerOpen()) { - mDrawerResult.closeDrawer(); + if (mDrawer != null && mDrawer.isDrawerOpen()) { + mDrawer.closeDrawer(); } else { super.onBackPressed(); } @@ -238,11 +257,17 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac // make sure the selected icon is the one shown at this point if (frag instanceof KeyListFragment) { - mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_KEYS), false); + mToolbar.setTitle(R.string.app_name); + mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_KEYS), false); } else if (frag instanceof EncryptDecryptOverviewFragment) { - mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_ENCRYPT_DECRYPT), false); + mToolbar.setTitle(R.string.nav_encrypt_decrypt); + mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_ENCRYPT_DECRYPT), false); } else if (frag instanceof AppsListFragment) { - mDrawerResult.setSelection(mDrawerResult.getPositionFromIdentifier(ID_APPS), false); + mToolbar.setTitle(R.string.nav_apps); + mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_APPS), false); + } else if (frag instanceof BackupFragment) { + mToolbar.setTitle(R.string.nav_backup); + mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_BACKUP), false); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java index addfb6a23..1584b87ae 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/NfcOperationActivity.java @@ -7,8 +7,13 @@ package org.sufficientlysecure.keychain.ui; import android.content.Intent; +import android.os.AsyncTask; import android.os.Bundle; +import android.view.View; import android.view.WindowManager; +import android.widget.Button; +import android.widget.TextView; +import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -28,27 +33,37 @@ import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.Date; /** * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant * NFC devices. * <p/> * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf + * NOTE: If no CryptoInputParcel is passed via EXTRA_CRYPTO_INPUT, the CryptoInputParcel is created + * internally and is NOT meant to be used by signing operations before adding signature time */ public class NfcOperationActivity extends BaseNfcActivity { public static final String EXTRA_REQUIRED_INPUT = "required_input"; + public static final String EXTRA_CRYPTO_INPUT = "crypto_input"; // passthrough for OpenPgpService public static final String EXTRA_SERVICE_INTENT = "data"; - public static final String RESULT_DATA = "result_data"; + public static final String RESULT_CRYPTO_INPUT = "result_data"; + + public ViewAnimator vAnimator; + public TextView vErrorText; + public Button vErrorTryAgainButton; private RequiredInputParcel mRequiredInput; private Intent mServiceIntent; private static final byte[] BLANK_FINGERPRINT = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + private CryptoInputParcel mInputParcel; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -56,6 +71,34 @@ public class NfcOperationActivity extends BaseNfcActivity { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + mInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT); + + if (mInputParcel == null) { + // for compatibility when used from OpenPgpService + // (or any place other than CryptoOperationHelper) + // NOTE: This CryptoInputParcel cannot be used for signing without adding signature time + mInputParcel = new CryptoInputParcel(); + } + + setTitle(R.string.nfc_text); + + vAnimator = (ViewAnimator) findViewById(R.id.view_animator); + vAnimator.setDisplayedChild(0); + vErrorText = (TextView) findViewById(R.id.nfc_activity_3_error_text); + vErrorTryAgainButton = (Button) findViewById(R.id.nfc_activity_3_error_try_again); + vErrorTryAgainButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + resumeTagHandling(); + + // obtain passphrase for this subkey + if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD) { + obtainYubiKeyPin(mRequiredInput); + } + vAnimator.setDisplayedChild(0); + } + }); + Intent intent = getIntent(); Bundle data = intent.getExtras(); @@ -70,33 +113,44 @@ public class NfcOperationActivity extends BaseNfcActivity { @Override protected void initLayout() { - setContentView(R.layout.nfc_activity); + setContentView(R.layout.nfc_operation_activity); } @Override - protected void onNfcPerform() throws IOException { + public void onNfcPreExecute() { + // start with indeterminate progress + vAnimator.setDisplayedChild(1); + } - CryptoInputParcel inputParcel = new CryptoInputParcel(mRequiredInput.mSignatureTime); + @Override + protected void doNfcInBackground() throws IOException { switch (mRequiredInput.mType) { case NFC_DECRYPT: { - for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) { - byte[] hash = mRequiredInput.mInputHashes[i]; - byte[] decryptedSessionKey = nfcDecryptSessionKey(hash); - inputParcel.addCryptoData(hash, decryptedSessionKey); + for (int i = 0; i < mRequiredInput.mInputData.length; i++) { + byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; + byte[] decryptedSessionKey = nfcDecryptSessionKey(encryptedSessionKey); + mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey); } break; } case NFC_SIGN: { - for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) { - byte[] hash = mRequiredInput.mInputHashes[i]; + if (mInputParcel.getSignatureTime() == null) { + mInputParcel.addSignatureTime(new Date()); + } + for (int i = 0; i < mRequiredInput.mInputData.length; i++) { + byte[] hash = mRequiredInput.mInputData[i]; int algo = mRequiredInput.mSignAlgos[i]; byte[] signedHash = nfcCalculateSignature(hash, algo); - inputParcel.addCryptoData(hash, signedHash); + mInputParcel.addCryptoData(hash, signedHash); } break; } case NFC_MOVE_KEY_TO_CARD: { + // TODO: assume PIN and Admin PIN to be default for this operation + mPin = new Passphrase("123456"); + mAdminPin = new Passphrase("12345678"); + ProviderHelper providerHelper = new ProviderHelper(this); CanonicalizedSecretKeyRing secretKeyRing; try { @@ -107,8 +161,11 @@ public class NfcOperationActivity extends BaseNfcActivity { throw new IOException("Couldn't find subkey for key to card operation."); } - for (int i = 0; i < mRequiredInput.mInputHashes.length; i++) { - byte[] subkeyBytes = mRequiredInput.mInputHashes[i]; + byte[] newPin = mRequiredInput.mInputData[0]; + byte[] newAdminPin = mRequiredInput.mInputData[1]; + + for (int i = 2; i < mRequiredInput.mInputData.length; i++) { + byte[] subkeyBytes = mRequiredInput.mInputData[i]; ByteBuffer buf = ByteBuffer.wrap(subkeyBytes); long subkeyId = buf.getLong(); @@ -155,21 +212,71 @@ public class NfcOperationActivity extends BaseNfcActivity { throw new IOException("Inappropriate key flags for smart card key."); } - inputParcel.addCryptoData(subkeyBytes, cardSerialNumber); + // TODO: Is this really needed? + mInputParcel.addCryptoData(subkeyBytes, cardSerialNumber); } + + // change PINs afterwards + nfcModifyPIN(0x81, newPin); + nfcModifyPIN(0x83, newAdminPin); + + break; + } + default: { + throw new AssertionError("Unhandled mRequiredInput.mType"); } } + } + + @Override + protected void onNfcPostExecute() throws IOException { if (mServiceIntent != null) { - CryptoInputParcelCacheService.addCryptoInputParcel(this, mServiceIntent, inputParcel); + // if we're triggered by OpenPgpService + CryptoInputParcelCacheService.addCryptoInputParcel(this, mServiceIntent, mInputParcel); + mServiceIntent.putExtra(EXTRA_CRYPTO_INPUT, + getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT)); setResult(RESULT_OK, mServiceIntent); } else { Intent result = new Intent(); - result.putExtra(NfcOperationActivity.RESULT_DATA, inputParcel); + result.putExtra(RESULT_CRYPTO_INPUT, mInputParcel); + // send back the CryptoInputParcel we receive, to conform with the pattern in + // CryptoOperationHelper setResult(RESULT_OK, result); } - finish(); + // show finish + vAnimator.setDisplayedChild(2); + + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + // check all 200ms if YubiKey has been taken away + while (true) { + if (isNfcConnected()) { + try { + Thread.sleep(200); + } catch (InterruptedException ignored) { + } + } else { + return null; + } + } + } + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + finish(); + } + }.execute(); + } + + @Override + protected void onNfcError(String error) { + pauseTagHandling(); + + vErrorText.setText(error + "\n\n" + getString(R.string.nfc_try_again_text)); + vAnimator.setDisplayedChild(3); } private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException { @@ -199,8 +306,6 @@ public class NfcOperationActivity extends BaseNfcActivity { // clear (invalid) passphrase PassphraseCacheService.clearCachedPassphrase( this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); - - obtainYubiKeyPin(mRequiredInput); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java new file mode 100644 index 000000000..587044659 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/OrbotRequiredDialogActivity.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * 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.content.Intent; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.util.ParcelableProxy; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; + +/** + * Simply encapsulates a dialog. If orbot is not installed, it shows an install dialog, else a + * dialog to enable orbot. + */ +public class OrbotRequiredDialogActivity extends FragmentActivity { + + // to provide any previous crypto input into which proxy preference is merged + public static final String EXTRA_CRYPTO_INPUT = "extra_crypto_input"; + + public static final String RESULT_CRYPTO_INPUT = "result_crypto_input"; + + private CryptoInputParcel mCryptoInputParcel; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mCryptoInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT); + if (mCryptoInputParcel == null) { + mCryptoInputParcel = new CryptoInputParcel(); + } + showDialog(); + } + + /** + * Displays an install or start orbot dialog depending on orbot's presence and state + */ + public void showDialog() { + DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { + public void run() { + Runnable ignoreTor = new Runnable() { + @Override + public void run() { + Intent intent = new Intent(); + mCryptoInputParcel.addParcelableProxy(ParcelableProxy.getForNoProxy()); + intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel); + setResult(RESULT_OK, intent); + finish(); + } + }; + + Runnable dialogDismissed = new Runnable() { + @Override + public void run() { + finish(); + } + }; + + if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, dialogDismissed, + Preferences.getPreferences(OrbotRequiredDialogActivity.this) + .getProxyPrefs(), + OrbotRequiredDialogActivity.this)) { + // no action required after all + Intent intent = new Intent(); + intent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel); + setResult(RESULT_OK, intent); + } + } + }); + } +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index bb12cd7ff..d7224bd04 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -19,15 +19,16 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; -import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; +import android.support.v7.app.AlertDialog; import android.text.InputType; import android.text.method.PasswordTransformationMethod; import android.view.ContextThemeWrapper; @@ -58,6 +59,7 @@ import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Preferences; @@ -65,17 +67,22 @@ import org.sufficientlysecure.keychain.util.Preferences; /** * We can not directly create a dialog on the application context. * This activity encapsulates a DialogFragment to emulate a dialog. + * NOTE: If no CryptoInputParcel is passed via EXTRA_CRYPTO_INPUT, the CryptoInputParcel is created + * internally and is NOT meant to be used by signing operations before adding a signature time */ public class PassphraseDialogActivity extends FragmentActivity { public static final String RESULT_CRYPTO_INPUT = "result_data"; public static final String EXTRA_REQUIRED_INPUT = "required_input"; public static final String EXTRA_SUBKEY_ID = "secret_key_id"; + public static final String EXTRA_CRYPTO_INPUT = "crypto_input"; // special extra for OpenPgpService public static final String EXTRA_SERVICE_INTENT = "data"; private long mSubKeyId; + private CryptoInputParcel mCryptoInputParcel; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -89,6 +96,15 @@ public class PassphraseDialogActivity extends FragmentActivity { ); } + mCryptoInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT); + + if (mCryptoInputParcel == null) { + // not all usages of PassphraseActivity are from CryptoInputOperation + // NOTE: This CryptoInputParcel cannot be used for signing operations without setting + // signature time + mCryptoInputParcel = new CryptoInputParcel(); + } + // this activity itself has no content view (see manifest) if (getIntent().hasExtra(EXTRA_SUBKEY_ID)) { @@ -112,7 +128,8 @@ public class PassphraseDialogActivity extends FragmentActivity { SecretKeyType.PASSPHRASE_EMPTY) { // also return passphrase back to activity Intent returnIntent = new Intent(); - returnIntent.putExtra(RESULT_CRYPTO_INPUT, new CryptoInputParcel(new Passphrase(""))); + mCryptoInputParcel.mPassphrase = new Passphrase(""); + returnIntent.putExtra(RESULT_CRYPTO_INPUT, mCryptoInputParcel); setResult(RESULT_OK, returnIntent); finish(); return; @@ -136,8 +153,8 @@ public class PassphraseDialogActivity extends FragmentActivity { } @Override - protected void onResume() { - super.onResume(); + protected void onResumeFragments() { + super.onResumeFragments(); /* Show passphrase dialog to cache a new passphrase the user enters for using it later for * encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks @@ -175,14 +192,12 @@ public class PassphraseDialogActivity extends FragmentActivity { private Intent mServiceIntent; + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Activity activity = getActivity(); - // if the dialog is displayed from the application class, design is missing - // hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay - ContextThemeWrapper theme = new ContextThemeWrapper(activity, - R.style.Theme_AppCompat_Light_Dialog); + ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); mSubKeyId = getArguments().getLong(EXTRA_SUBKEY_ID); mServiceIntent = getArguments().getParcelable(EXTRA_SERVICE_INTENT); @@ -299,10 +314,10 @@ public class PassphraseDialogActivity extends FragmentActivity { mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE); mPassphraseEditText.setOnEditorActionListener(this); - if (keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin()) { - mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD); - } else if (keyType == CanonicalizedSecretKey.SecretKeyType.PIN) { - mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD); + if ((keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin()) + || keyType == CanonicalizedSecretKey.SecretKeyType.PIN) { + mPassphraseEditText.setInputType(InputType.TYPE_CLASS_NUMBER); + mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); } else { mPassphraseEditText.setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); } @@ -328,11 +343,16 @@ public class PassphraseDialogActivity extends FragmentActivity { public void onClick(View v) { final Passphrase passphrase = new Passphrase(mPassphraseEditText); + CryptoInputParcel cryptoInputParcel = + ((PassphraseDialogActivity) getActivity()).mCryptoInputParcel; + // Early breakout if we are dealing with a symmetric key if (mSecretRing == null) { - PassphraseCacheService.addCachedPassphrase(getActivity(), - Constants.key.symmetric, Constants.key.symmetric, passphrase, - getString(R.string.passp_cache_notif_pwd)); + if (cryptoInputParcel.mCachePassphrase) { + PassphraseCacheService.addCachedPassphrase(getActivity(), + Constants.key.symmetric, Constants.key.symmetric, passphrase, + getString(R.string.passp_cache_notif_pwd)); + } finishCaching(passphrase); return; @@ -385,15 +405,24 @@ public class PassphraseDialogActivity extends FragmentActivity { return; } - // cache the new passphrase - Log.d(Constants.TAG, "Everything okay! Caching entered passphrase"); + // cache the new passphrase as specified in CryptoInputParcel + Log.d(Constants.TAG, "Everything okay!"); - try { - PassphraseCacheService.addCachedPassphrase(getActivity(), - mSecretRing.getMasterKeyId(), mSubKeyId, passphrase, - mSecretRing.getPrimaryUserIdWithFallback()); - } catch (PgpKeyNotFoundException e) { - Log.e(Constants.TAG, "adding of a passphrase failed", e); + CryptoInputParcel cryptoInputParcel + = ((PassphraseDialogActivity) getActivity()).mCryptoInputParcel; + + if (cryptoInputParcel.mCachePassphrase) { + Log.d(Constants.TAG, "Caching entered passphrase"); + + try { + PassphraseCacheService.addCachedPassphrase(getActivity(), + mSecretRing.getMasterKeyId(), mSubKeyId, passphrase, + mSecretRing.getPrimaryUserIdWithFallback()); + } catch (PgpKeyNotFoundException e) { + Log.e(Constants.TAG, "adding of a passphrase failed", e); + } + } else { + Log.d(Constants.TAG, "Not caching entered passphrase!"); } finishCaching(passphrase); @@ -409,9 +438,12 @@ public class PassphraseDialogActivity extends FragmentActivity { return; } - CryptoInputParcel inputParcel = new CryptoInputParcel(null, passphrase); + CryptoInputParcel inputParcel = + ((PassphraseDialogActivity) getActivity()).mCryptoInputParcel; + inputParcel.mPassphrase = passphrase; if (mServiceIntent != null) { - CryptoInputParcelCacheService.addCryptoInputParcel(getActivity(), mServiceIntent, inputParcel); + CryptoInputParcelCacheService.addCryptoInputParcel(getActivity(), mServiceIntent, + inputParcel); getActivity().setResult(RESULT_OK, mServiceIntent); } else { // also return passphrase back to activity diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java index 2e838535d..e55494145 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseWizardActivity.java @@ -18,7 +18,7 @@ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.app.PendingIntent; import android.content.DialogInterface; import android.content.Intent; @@ -43,7 +43,9 @@ import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -207,7 +209,7 @@ public class PassphraseWizardActivity extends FragmentActivity { // transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit(); } } catch (IOException | FormatException e) { - e.printStackTrace(); + Log.e(Constants.TAG, "Failed to write on NFC tag", e); } } else if (readNFC && AUTHENTICATION.equals(selectedAction)) { @@ -232,7 +234,7 @@ public class PassphraseWizardActivity extends FragmentActivity { } } } catch (IOException | FormatException e) { - e.printStackTrace(); + Log.e(Constants.TAG, "Failed to read NFC tag", e); } } } @@ -263,7 +265,7 @@ public class PassphraseWizardActivity extends FragmentActivity { try { password = readText(ndefRecord); } catch (UnsupportedEncodingException e) { - e.printStackTrace(); + Log.e(Constants.TAG, "Failed to read password from tag", e); } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RetryUploadDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RetryUploadDialogActivity.java new file mode 100644 index 000000000..2a00e8b70 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/RetryUploadDialogActivity.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * 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.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; +import android.view.ContextThemeWrapper; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; +import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; + +public class RetryUploadDialogActivity extends FragmentActivity { + + public static final String EXTRA_CRYPTO_INPUT = "extra_crypto_input"; + + public static final String RESULT_CRYPTO_INPUT = "result_crypto_input"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + UploadRetryDialogFragment.newInstance().show(getSupportFragmentManager(), + "uploadRetryDialog"); + } + + public static class UploadRetryDialogFragment extends DialogFragment { + public static UploadRetryDialogFragment newInstance() { + return new UploadRetryDialogFragment(); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(getActivity()); + + CustomAlertDialogBuilder dialogBuilder = new CustomAlertDialogBuilder(theme); + dialogBuilder.setTitle(R.string.retry_up_dialog_title); + dialogBuilder.setMessage(R.string.retry_up_dialog_message); + + dialogBuilder.setNegativeButton(R.string.retry_up_dialog_btn_cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + getActivity().setResult(RESULT_CANCELED); + getActivity().finish(); + } + }); + + dialogBuilder.setPositiveButton(R.string.retry_up_dialog_btn_reupload, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(); + intent.putExtra(RESULT_CRYPTO_INPUT, getActivity() + .getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT)); + getActivity().setResult(RESULT_OK, intent); + getActivity().finish(); + } + }); + + return dialogBuilder.show(); + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java index a1cb77546..534dbfd05 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SafeSlingerActivity.java @@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.ParcelableFileCache; @@ -81,7 +82,7 @@ public class SafeSlingerActivity extends BaseActivity }); ImageView buttonIcon = (ImageView) findViewById(R.id.safe_slinger_button_image); - buttonIcon.setColorFilter(getResources().getColor(R.color.tertiary_text_light), + buttonIcon.setColorFilter(FormattingUtils.getColorFromAttr(this, R.attr.colorTertiaryText), PorterDuff.Mode.SRC_IN); View button = findViewById(R.id.safe_slinger_button); @@ -144,7 +145,7 @@ public class SafeSlingerActivity extends BaseActivity cache.writeCache(it.size(), it.iterator()); mOperationHelper = - new CryptoOperationHelper(this, this, R.string.progress_importing); + new CryptoOperationHelper(1, this, this, R.string.progress_importing); mKeyList = null; mKeyserver = null; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java index a552e1c55..2fe868b8b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java @@ -18,14 +18,21 @@ package org.sufficientlysecure.keychain.ui; +import android.annotation.TargetApi; +import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; import android.preference.Preference; +import android.preference.PreferenceActivity; import android.preference.PreferenceFragment; import android.preference.PreferenceScreen; import android.support.v7.widget.Toolbar; +import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; @@ -33,8 +40,12 @@ import android.widget.LinearLayout; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference; +import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.util.List; @@ -42,15 +53,21 @@ public class SettingsActivity extends AppCompatPreferenceActivity { public static final String ACTION_PREFS_CLOUD = "org.sufficientlysecure.keychain.ui.PREFS_CLOUD"; public static final String ACTION_PREFS_ADV = "org.sufficientlysecure.keychain.ui.PREFS_ADV"; + public static final String ACTION_PREFS_PROXY = "org.sufficientlysecure.keychain.ui.PREFS_PROXY"; + public static final String ACTION_PREFS_GUI = "org.sufficientlysecure.keychain.ui.PREFS_GUI"; public static final int REQUEST_CODE_KEYSERVER_PREF = 0x00007005; private PreferenceScreen mKeyServerPreference = null; private static Preferences sPreferences; + private ThemeChanger mThemeChanger; @Override protected void onCreate(Bundle savedInstanceState) { sPreferences = Preferences.getPreferences(this); + + mThemeChanger = new ThemeChanger(this); + mThemeChanger.changeTheme(); super.onCreate(savedInstanceState); setupToolbar(); @@ -95,6 +112,21 @@ public class SettingsActivity extends AppCompatPreferenceActivity { initializeUseNumKeypadForYubiKeyPin( (CheckBoxPreference) findPreference(Constants.Pref.USE_NUMKEYPAD_FOR_YUBIKEY_PIN)); + } else if (action != null && action.equals(ACTION_PREFS_GUI)) { + addPreferencesFromResource(R.xml.gui_preferences); + + initializeTheme((ListPreference) findPreference(Constants.Pref.THEME)); + } + } + + @Override + protected void onResume() { + super.onResume(); + + if (mThemeChanger.changeTheme()) { + Intent intent = getIntent(); + finish(); + startActivity(intent); } } @@ -124,27 +156,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity { } @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - switch (requestCode) { - case REQUEST_CODE_KEYSERVER_PREF: { - if (resultCode == RESULT_CANCELED || data == null) { - return; - } - String servers[] = data - .getStringArrayExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS); - sPreferences.setKeyServers(servers); - mKeyServerPreference.setSummary(keyserverSummary(this)); - break; - } - - default: { - super.onActivityResult(requestCode, resultCode, data); - break; - } - } - } - - @Override public void onBuildHeaders(List<Header> target) { super.onBuildHeaders(target); loadHeadersFromResource(R.xml.preference_headers, target); @@ -190,12 +201,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_CODE_KEYSERVER_PREF: { - if (resultCode == RESULT_CANCELED || data == null) { - return; - } - String servers[] = data - .getStringArrayExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS); - sPreferences.setKeyServers(servers); + // update preference, in case it changed mKeyServerPreference.setSummary(keyserverSummary(getActivity())); break; } @@ -234,9 +240,236 @@ public class SettingsActivity extends AppCompatPreferenceActivity { } } + public static class ProxyPrefsFragment extends PreferenceFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + new Initializer(this).initialize(); + + } + + public static class Initializer { + private CheckBoxPreference mUseTor; + private CheckBoxPreference mUseNormalProxy; + private EditTextPreference mProxyHost; + private EditTextPreference mProxyPort; + private ListPreference mProxyType; + private PreferenceActivity mActivity; + private PreferenceFragment mFragment; + + public Initializer(PreferenceFragment fragment) { + mFragment = fragment; + } + + public Initializer(PreferenceActivity activity) { + mActivity = activity; + } + + public Preference automaticallyFindPreference(String key) { + if (mFragment != null) { + return mFragment.findPreference(key); + } else { + return mActivity.findPreference(key); + } + } + + public void initialize() { + // makes android's preference framework write to our file instead of default + // This allows us to use the "persistent" attribute to simplify code + if (mFragment != null) { + Preferences.setPreferenceManagerFileAndMode(mFragment.getPreferenceManager()); + // Load the preferences from an XML resource + mFragment.addPreferencesFromResource(R.xml.proxy_prefs); + } else { + Preferences.setPreferenceManagerFileAndMode(mActivity.getPreferenceManager()); + // Load the preferences from an XML resource + mActivity.addPreferencesFromResource(R.xml.proxy_prefs); + } + + mUseTor = (CheckBoxPreference) automaticallyFindPreference(Constants.Pref.USE_TOR_PROXY); + mUseNormalProxy = (CheckBoxPreference) automaticallyFindPreference(Constants.Pref.USE_NORMAL_PROXY); + mProxyHost = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_HOST); + mProxyPort = (EditTextPreference) automaticallyFindPreference(Constants.Pref.PROXY_PORT); + mProxyType = (ListPreference) automaticallyFindPreference(Constants.Pref.PROXY_TYPE); + initializeUseTorPref(); + initializeUseNormalProxyPref(); + initializeEditTextPreferences(); + initializeProxyTypePreference(); + + if (mUseTor.isChecked()) { + disableNormalProxyPrefs(); + } + else if (mUseNormalProxy.isChecked()) { + disableUseTorPrefs(); + } else { + disableNormalProxySettings(); + } + } + + private void initializeUseTorPref() { + mUseTor.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Activity activity = mFragment != null ? mFragment.getActivity() : mActivity; + if ((Boolean) newValue) { + boolean installed = OrbotHelper.isOrbotInstalled(activity); + if (!installed) { + Log.d(Constants.TAG, "Prompting to install Tor"); + OrbotHelper.getPreferenceInstallDialogFragment().show(activity.getFragmentManager(), + "installDialog"); + // don't let the user check the box until he's installed orbot + return false; + } else { + disableNormalProxyPrefs(); + // let the enable tor box be checked + return true; + } + } else { + // we're unchecking Tor, so enable other proxy + enableNormalProxyCheckbox(); + return true; + } + } + }); + } + + private void initializeUseNormalProxyPref() { + mUseNormalProxy.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if ((Boolean) newValue) { + disableUseTorPrefs(); + enableNormalProxySettings(); + } else { + enableUseTorPrefs(); + disableNormalProxySettings(); + } + return true; + } + }); + } + + private void initializeEditTextPreferences() { + mProxyHost.setSummary(mProxyHost.getText()); + mProxyPort.setSummary(mProxyPort.getText()); + + mProxyHost.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Activity activity = mFragment != null ? mFragment.getActivity() : mActivity; + if (TextUtils.isEmpty((String) newValue)) { + Notify.create( + activity, + R.string.pref_proxy_host_err_invalid, + Notify.Style.ERROR + ).show(); + return false; + } else { + mProxyHost.setSummary((CharSequence) newValue); + return true; + } + } + }); + + mProxyPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Activity activity = mFragment != null ? mFragment.getActivity() : mActivity; + try { + int port = Integer.parseInt((String) newValue); + if (port < 0 || port > 65535) { + Notify.create( + activity, + R.string.pref_proxy_port_err_invalid, + Notify.Style.ERROR + ).show(); + return false; + } + // no issues, save port + mProxyPort.setSummary("" + port); + return true; + } catch (NumberFormatException e) { + Notify.create( + activity, + R.string.pref_proxy_port_err_invalid, + Notify.Style.ERROR + ).show(); + return false; + } + } + }); + } + + private void initializeProxyTypePreference() { + mProxyType.setSummary(mProxyType.getEntry()); + + mProxyType.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + CharSequence entry = mProxyType.getEntries()[mProxyType.findIndexOfValue((String) newValue)]; + mProxyType.setSummary(entry); + return true; + } + }); + } + + private void disableNormalProxyPrefs() { + mUseNormalProxy.setChecked(false); + mUseNormalProxy.setEnabled(false); + disableNormalProxySettings(); + } + + private void enableNormalProxyCheckbox() { + mUseNormalProxy.setEnabled(true); + } + + private void enableNormalProxySettings() { + mProxyHost.setEnabled(true); + mProxyPort.setEnabled(true); + mProxyType.setEnabled(true); + } + + private void disableNormalProxySettings() { + mProxyHost.setEnabled(false); + mProxyPort.setEnabled(false); + mProxyType.setEnabled(false); + } + + private void disableUseTorPrefs() { + mUseTor.setChecked(false); + mUseTor.setEnabled(false); + } + + private void enableUseTorPrefs() { + mUseTor.setEnabled(true); + } + } + } + + /** + * This fragment shows gui preferences. + */ + public static class GuiPrefsFragment extends PreferenceFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.gui_preferences); + + initializeTheme((ListPreference) findPreference(Constants.Pref.THEME)); + } + } + protected boolean isValidFragment(String fragmentName) { return AdvancedPrefsFragment.class.getName().equals(fragmentName) || CloudSearchPrefsFragment.class.getName().equals(fragmentName) + || ProxyPrefsFragment.class.getName().equals(fragmentName) + || GuiPrefsFragment.class.getName().equals(fragmentName) || super.isValidFragment(fragmentName); } @@ -265,6 +498,19 @@ public class SettingsActivity extends AppCompatPreferenceActivity { }); } + private static void initializeTheme(final ListPreference mTheme) { + mTheme.setValue(sPreferences.getTheme()); + mTheme.setSummary(mTheme.getEntry()); + mTheme.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + mTheme.setValue((String) newValue); + mTheme.setSummary(mTheme.getEntry()); + sPreferences.setTheme((String) newValue); + return false; + } + }); + } + private static void initializeSearchKeyserver(final CheckBoxPreference mSearchKeyserver) { Preferences.CloudSearchPrefs prefs = sPreferences.getCloudSearchPrefs(); mSearchKeyserver.setChecked(prefs.searchKeyserver); @@ -295,7 +541,8 @@ public class SettingsActivity extends AppCompatPreferenceActivity { String[] servers = sPreferences.getKeyServers(); String serverSummary = context.getResources().getQuantityString( R.plurals.n_keyservers, servers.length, servers.length); - return serverSummary + "; " + context.getString(R.string.label_preferred) + ": " + sPreferences.getPreferredKeyserver(); + return serverSummary + "; " + context.getString(R.string.label_preferred) + ": " + sPreferences + .getPreferredKeyserver(); } private static void initializeUseDefaultYubiKeyPin(final CheckBoxPreference mUseDefaultYubiKeyPin) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java index dc203756f..f61ada84f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java @@ -17,89 +17,23 @@ package org.sufficientlysecure.keychain.ui; -import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.Messenger; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.base.BaseActivity; -import org.sufficientlysecure.keychain.ui.dialog.AddKeyserverDialogFragment; -import org.sufficientlysecure.keychain.ui.util.Notify; -import org.sufficientlysecure.keychain.ui.widget.Editor; -import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener; -import org.sufficientlysecure.keychain.ui.widget.KeyServerEditor; -import java.util.Vector; - -public class SettingsKeyServerActivity extends BaseActivity implements OnClickListener, - EditorListener { +public class SettingsKeyServerActivity extends BaseActivity { public static final String EXTRA_KEY_SERVERS = "key_servers"; - private LayoutInflater mInflater; - private ViewGroup mEditors; - private View mAdd; - private View mRotate; - private TextView mTitle; - private TextView mSummary; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Inflate a "Done"/"Cancel" custom action bar view - setFullScreenDialogDoneClose(R.string.btn_save, - new View.OnClickListener() { - @Override - public void onClick(View v) { - okClicked(); - } - }, - new View.OnClickListener() { - @Override - public void onClick(View v) { - cancelClicked(); - } - }); - - mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - mTitle = (TextView) findViewById(R.id.title); - mSummary = (TextView) findViewById(R.id.summary); - mSummary.setText(getText(R.string.label_first_keyserver_is_used)); - - mTitle.setText(R.string.label_keyservers); - - mEditors = (ViewGroup) findViewById(R.id.editors); - mAdd = findViewById(R.id.add); - mAdd.setOnClickListener(this); - - mRotate = findViewById(R.id.rotate); - mRotate.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - Vector<String> servers = serverList(); - String first = servers.get(0); - if (first != null) { - servers.remove(0); - servers.add(first); - String[] dummy = {}; - makeServerList(servers.toArray(dummy)); - } - } - }); - Intent intent = getIntent(); String servers[] = intent.getStringArrayExtra(EXTRA_KEY_SERVERS); - makeServerList(servers); + loadFragment(savedInstanceState, servers); } @Override @@ -107,118 +41,22 @@ public class SettingsKeyServerActivity extends BaseActivity implements OnClickLi setContentView(R.layout.key_server_preference); } - private void makeServerList(String[] servers) { - if (servers != null) { - mEditors.removeAllViews(); - for (String serv : servers) { - KeyServerEditor view = (KeyServerEditor) mInflater.inflate( - R.layout.key_server_editor, mEditors, false); - view.setEditorListener(this); - view.setValue(serv); - mEditors.addView(view); - } + private void loadFragment(Bundle savedInstanceState, String[] keyservers) { + // 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; } - } - - public void onDeleted(Editor editor, boolean wasNewItem) { - // nothing to do - } - @Override - public void onEdited() { + SettingsKeyserverFragment fragment = SettingsKeyserverFragment.newInstance(keyservers); - } - - // button to add keyserver clicked - public void onClick(View v) { - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - Bundle data = message.getData(); - switch (message.what) { - case AddKeyserverDialogFragment.MESSAGE_OKAY: { - boolean verified = data.getBoolean(AddKeyserverDialogFragment.MESSAGE_VERIFIED); - if (verified) { - Notify.create(SettingsKeyServerActivity.this, - R.string.add_keyserver_verified, Notify.Style.OK).show(); - } else { - Notify.create(SettingsKeyServerActivity.this, - R.string.add_keyserver_without_verification, - Notify.Style.WARN).show(); - } - String keyserver = data.getString(AddKeyserverDialogFragment.MESSAGE_KEYSERVER); - addKeyserver(keyserver); - break; - } - case AddKeyserverDialogFragment.MESSAGE_VERIFICATION_FAILED: { - AddKeyserverDialogFragment.FailureReason failureReason = - (AddKeyserverDialogFragment.FailureReason) data.getSerializable( - AddKeyserverDialogFragment.MESSAGE_FAILURE_REASON); - switch (failureReason) { - case CONNECTION_FAILED: { - Notify.create(SettingsKeyServerActivity.this, - R.string.add_keyserver_connection_failed, - Notify.Style.ERROR).show(); - break; - } - case INVALID_URL: { - Notify.create(SettingsKeyServerActivity.this, - R.string.add_keyserver_invalid_url, - Notify.Style.ERROR).show(); - break; - } - } - break; - } - } - } - }; - - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - AddKeyserverDialogFragment dialogFragment = AddKeyserverDialogFragment - .newInstance(messenger); - dialogFragment.show(getSupportFragmentManager(), "addKeyserverDialog"); - } - - public void addKeyserver(String keyserverUrl) { - KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor, - mEditors, false); - view.setEditorListener(this); - view.setValue(keyserverUrl); - mEditors.addView(view); - } - - private void cancelClicked() { - setResult(RESULT_CANCELED, null); - finish(); - } - - private Vector<String> serverList() { - Vector<String> servers = new Vector<>(); - for (int i = 0; i < mEditors.getChildCount(); ++i) { - KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i); - String tmp = editor.getValue(); - if (tmp.length() > 0) { - servers.add(tmp); - } - } - return servers; - } - - private void okClicked() { - Intent data = new Intent(); - Vector<String> servers = new Vector<>(); - for (int i = 0; i < mEditors.getChildCount(); ++i) { - KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i); - String tmp = editor.getValue(); - if (tmp.length() > 0) { - servers.add(tmp); - } - } - String[] dummy = new String[0]; - data.putExtra(EXTRA_KEY_SERVERS, servers.toArray(dummy)); - setResult(RESULT_OK, data); - finish(); + // Add the fragment to the 'fragment_container' FrameLayout + // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + getSupportFragmentManager().beginTransaction() + .replace(R.id.keyserver_settings_fragment_container, fragment) + .commitAllowingStateLoss(); + // do it immediately! + getSupportFragmentManager().executePendingTransactions(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java new file mode 100644 index 000000000..2ae64d90b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com> + * + * 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.graphics.Color; +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.view.MotionEventCompat; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.dialog.AddEditKeyserverDialogFragment; +import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperAdapter; +import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperViewHolder; +import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperDragCallback; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener; +import org.sufficientlysecure.keychain.util.Preferences; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class SettingsKeyserverFragment extends Fragment implements RecyclerItemClickListener.OnItemClickListener { + + private static final String ARG_KEYSERVER_ARRAY = "arg_keyserver_array"; + private ItemTouchHelper mItemTouchHelper; + + private ArrayList<String> mKeyservers; + private KeyserverListAdapter mAdapter; + + public static SettingsKeyserverFragment newInstance(String[] keyservers) { + Bundle args = new Bundle(); + args.putStringArray(ARG_KEYSERVER_ARRAY, keyservers); + + SettingsKeyserverFragment fragment = new SettingsKeyserverFragment(); + fragment.setArguments(args); + + return fragment; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle + savedInstanceState) { + + return inflater.inflate(R.layout.settings_keyserver_fragment, null); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + String keyservers[] = getArguments().getStringArray(ARG_KEYSERVER_ARRAY); + mKeyservers = new ArrayList<>(Arrays.asList(keyservers)); + + mAdapter = new KeyserverListAdapter(mKeyservers); + + RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.keyserver_recycler_view); + // recyclerView.setHasFixedSize(true); // the size of the first item changes + recyclerView.setAdapter(mAdapter); + recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + + + ItemTouchHelper.Callback callback = new ItemTouchHelperDragCallback(mAdapter); + mItemTouchHelper = new ItemTouchHelper(callback); + mItemTouchHelper.attachToRecyclerView(recyclerView); + + // for clicks + recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), this)); + + // can't use item decoration because it doesn't move with drag and drop + // recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), null)); + + // We have a menu item to show in action bar. + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { + inflater.inflate(R.menu.keyserver_pref_menu, menu); + + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case R.id.menu_add_keyserver: + startAddKeyserverDialog(); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + private void startAddKeyserverDialog() { + // keyserver and position have no meaning + startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction.ADD, null, -1); + } + + private void startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction action, + String keyserver, final int position) { + Handler returnHandler = new Handler() { + @Override + public void handleMessage(Message message) { + Bundle data = message.getData(); + switch (message.what) { + case AddEditKeyserverDialogFragment.MESSAGE_OKAY: { + boolean deleted = + data.getBoolean(AddEditKeyserverDialogFragment.MESSAGE_KEYSERVER_DELETED + , false); + if (deleted) { + Notify.create(getActivity(), + getActivity().getString( + R.string.keyserver_preference_deleted, mKeyservers.get(position)), + Notify.Style.OK) + .show(); + deleteKeyserver(position); + return; + } + boolean verified = + data.getBoolean(AddEditKeyserverDialogFragment.MESSAGE_VERIFIED); + if (verified) { + Notify.create(getActivity(), + R.string.add_keyserver_verified, Notify.Style.OK).show(); + } else { + Notify.create(getActivity(), + R.string.add_keyserver_without_verification, + Notify.Style.WARN).show(); + } + String keyserver = data.getString( + AddEditKeyserverDialogFragment.MESSAGE_KEYSERVER); + + AddEditKeyserverDialogFragment.DialogAction dialogAction + = (AddEditKeyserverDialogFragment.DialogAction) data.getSerializable( + AddEditKeyserverDialogFragment.MESSAGE_DIALOG_ACTION); + switch (dialogAction) { + case ADD: + addKeyserver(keyserver); + break; + case EDIT: + editKeyserver(keyserver, position); + break; + } + break; + } + case AddEditKeyserverDialogFragment.MESSAGE_VERIFICATION_FAILED: { + AddEditKeyserverDialogFragment.FailureReason failureReason = + (AddEditKeyserverDialogFragment.FailureReason) data.getSerializable( + AddEditKeyserverDialogFragment.MESSAGE_FAILURE_REASON); + switch (failureReason) { + case CONNECTION_FAILED: { + Notify.create(getActivity(), + R.string.add_keyserver_connection_failed, + Notify.Style.ERROR).show(); + break; + } + case INVALID_URL: { + Notify.create(getActivity(), + R.string.add_keyserver_invalid_url, + Notify.Style.ERROR).show(); + break; + } + } + break; + } + } + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(returnHandler); + AddEditKeyserverDialogFragment dialogFragment = AddEditKeyserverDialogFragment + .newInstance(messenger, action, keyserver, position); + dialogFragment.show(getFragmentManager(), "addKeyserverDialog"); + } + + private void addKeyserver(String keyserver) { + mKeyservers.add(keyserver); + mAdapter.notifyItemInserted(mKeyservers.size() - 1); + saveKeyserverList(); + } + + private void editKeyserver(String newKeyserver, int position) { + mKeyservers.set(position, newKeyserver); + mAdapter.notifyItemChanged(position); + saveKeyserverList(); + } + + private void deleteKeyserver(int position) { + if (mKeyservers.size() == 1) { + Notify.create(getActivity(), R.string.keyserver_preference_cannot_delete_last, + Notify.Style.ERROR).show(); + return; + } + mKeyservers.remove(position); + // we use this + mAdapter.notifyItemRemoved(position); + if (position == 0 && mKeyservers.size() > 0) { + // if we deleted the first item, we need the adapter to redraw the new first item + mAdapter.notifyItemChanged(0); + } + saveKeyserverList(); + } + + private void saveKeyserverList() { + String servers[] = mKeyservers.toArray(new String[mKeyservers.size()]); + Preferences.getPreferences(getActivity()).setKeyServers(servers); + } + + @Override + public void onItemClick(View view, int position) { + startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction.EDIT, + mKeyservers.get(position), position); + } + + public class KeyserverListAdapter extends RecyclerView.Adapter<KeyserverListAdapter.ViewHolder> + implements ItemTouchHelperAdapter { + + // to update the ViewHolder associated with first item, for when an item is deleted + private ViewHolder mFirstItem; + + private final List<String> mKeyservers; + + public KeyserverListAdapter(List<String> keyservers) { + mKeyservers = keyservers; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.settings_keyserver_item, parent, false); + ViewHolder viewHolder = new ViewHolder(view); + return viewHolder; + } + + @Override + public void onBindViewHolder(final ViewHolder holder, int position) { + if (position == 0) { + mFirstItem = holder; + } + holder.keyserverUrl.setText(mKeyservers.get(position)); + + // Start a drag whenever the handle view it touched + holder.dragHandleView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) { + mItemTouchHelper.startDrag(holder); + } + return false; + } + }); + + selectUnselectKeyserver(holder, position); + } + + private void selectUnselectKeyserver(ViewHolder holder, int position) { + + if (position == 0) { + holder.showAsSelectedKeyserver(); + } else { + holder.showAsUnselectedKeyserver(); + } + } + + @Override + public void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target, + int fromPosition, int toPosition) { + Collections.swap(mKeyservers, fromPosition, toPosition); + saveKeyserverList(); + selectUnselectKeyserver((ViewHolder) target, fromPosition); + // we don't want source to change color while dragging, therefore we just set + // isSelectedKeyserver instead of selectUnselectKeyserver + ((ViewHolder) source).isSelectedKeyserver = toPosition == 0; + + notifyItemMoved(fromPosition, toPosition); + } + + @Override + public int getItemCount() { + return mKeyservers.size(); + } + + public class ViewHolder extends RecyclerView.ViewHolder implements + ItemTouchHelperViewHolder { + + public final ViewGroup outerLayout; + public final TextView selectedServerLabel; + public final TextView keyserverUrl; + public final ImageView dragHandleView; + + private boolean isSelectedKeyserver = false; + + public ViewHolder(View itemView) { + super(itemView); + outerLayout = (ViewGroup) itemView.findViewById(R.id.outer_layout); + selectedServerLabel = (TextView) itemView.findViewById( + R.id.selected_keyserver_title); + keyserverUrl = (TextView) itemView.findViewById(R.id.keyserver_tv); + dragHandleView = (ImageView) itemView.findViewById(R.id.drag_handle); + + itemView.setClickable(true); + } + + public void showAsSelectedKeyserver() { + isSelectedKeyserver = true; + selectedServerLabel.setVisibility(View.VISIBLE); + outerLayout.setBackgroundColor(getResources().getColor(R.color.android_green_dark)); + } + + public void showAsUnselectedKeyserver() { + isSelectedKeyserver = false; + selectedServerLabel.setVisibility(View.GONE); + outerLayout.setBackgroundColor(Color.WHITE); + } + + @Override + public void onItemSelected() { + selectedServerLabel.setVisibility(View.GONE); + itemView.setBackgroundColor(Color.LTGRAY); + } + + @Override + public void onItemClear() { + if (isSelectedKeyserver) { + showAsSelectedKeyserver(); + } else { + showAsUnselectedKeyserver(); + } + } + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java index 8b49f3b96..0415128a2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java @@ -26,7 +26,6 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.Spinner; -import android.widget.Toast; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -108,7 +107,7 @@ public class UploadKeyActivity extends BaseActivity String server = (String) mKeyServerSpinner.getSelectedItem(); mKeyserver = server; - mUploadOpHelper = new CryptoOperationHelper(this, this, R.string.progress_uploading); + mUploadOpHelper = new CryptoOperationHelper(1, this, this, R.string.progress_uploading); mUploadOpHelper.cryptoOperation(); } @@ -132,8 +131,7 @@ public class UploadKeyActivity extends BaseActivity @Override public void onCryptoOperationSuccess(ExportResult result) { - Toast.makeText(UploadKeyActivity.this, R.string.msg_crt_upload_success, - Toast.LENGTH_SHORT).show(); + result.createNotify(this).show(); } @Override @@ -143,7 +141,7 @@ public class UploadKeyActivity extends BaseActivity @Override public void onCryptoOperationError(ExportResult result) { - // TODO: Implement proper log for key upload then show error + result.createNotify(this).show(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index d3849c892..1d0e085da 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -31,8 +31,6 @@ 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.v4.app.ActivityCompat; import android.support.v4.app.FragmentManager; @@ -52,11 +50,12 @@ import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; + import com.getbase.floatingactionbutton.FloatingActionButton; + import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; -import org.sufficientlysecure.keychain.operations.results.CertifyResult; import org.sufficientlysecure.keychain.operations.results.ImportKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.pgp.KeyRing; @@ -66,13 +65,8 @@ import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.ImportKeyringParcel; -import org.sufficientlysecure.keychain.service.KeychainService; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler.MessageStatus; -import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; -import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; @@ -88,7 +82,6 @@ import org.sufficientlysecure.keychain.util.Preferences; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; public class ViewKeyActivity extends BaseNfcActivity implements LoaderManager.LoaderCallbacks<Cursor>, @@ -99,11 +92,12 @@ public class ViewKeyActivity extends BaseNfcActivity implements public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints"; static final int REQUEST_QR_FINGERPRINT = 1; - static final int REQUEST_DELETE = 2; - static final int REQUEST_EXPORT = 3; + static final int REQUEST_BACKUP = 2; + static final int REQUEST_CERTIFY = 3; + static final int REQUEST_DELETE = 4; + public static final String EXTRA_DISPLAY_RESULT = "display_result"; - ExportHelper mExportHelper; ProviderHelper mProviderHelper; protected Uri mDataUri; @@ -148,13 +142,17 @@ public class ViewKeyActivity extends BaseNfcActivity implements private String mFingerprint; private long mMasterKeyId; + private byte[] mNfcFingerprints; + private String mNfcUserId; + private byte[] mNfcAid; + @SuppressLint("InflateParams") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mExportHelper = new ExportHelper(this); mProviderHelper = new ProviderHelper(this); + mOperationHelper = new CryptoOperationHelper<>(1, this, this, null); setTitle(null); @@ -325,31 +323,11 @@ public class ViewKeyActivity extends BaseNfcActivity implements return true; } case R.id.menu_key_view_export_file: { - try { - if (PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId, mMasterKeyId) != null) { - exportToFile(mDataUri, mExportHelper, mProviderHelper); - return true; - } - - startPassphraseActivity(REQUEST_EXPORT); - } catch (PassphraseCacheService.KeyNotFoundException e) { - // This happens when the master key is stripped - exportToFile(mDataUri, mExportHelper, mProviderHelper); - } + startPassphraseActivity(REQUEST_BACKUP); return true; } case R.id.menu_key_view_delete: { - try { - if (PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId, mMasterKeyId) != null) { - deleteKey(); - return true; - } - - startPassphraseActivity(REQUEST_DELETE); - } catch (PassphraseCacheService.KeyNotFoundException e) { - // This happens when the master key is stripped - deleteKey(); - } + deleteKey(); return true; } case R.id.menu_key_view_advanced: { @@ -382,6 +360,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements public boolean onPrepareOptionsMenu(Menu menu) { MenuItem editKey = menu.findItem(R.id.menu_key_view_edit); editKey.setVisible(mIsSecret); + MenuItem exportKey = menu.findItem(R.id.menu_key_view_export_file); + exportKey.setVisible(mIsSecret); MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint); certifyFingerprint.setVisible(!mIsSecret && !mIsVerified && !mIsExpired && !mIsRevoked); @@ -399,37 +379,14 @@ public class ViewKeyActivity extends BaseNfcActivity implements Intent intent = new Intent(this, CertifyFingerprintActivity.class); intent.setData(dataUri); - startCertifyIntent(intent); + startActivityForResult(intent, REQUEST_CERTIFY); } private void certifyImmediate() { Intent intent = new Intent(this, CertifyKeyActivity.class); intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{mMasterKeyId}); - startCertifyIntent(intent); - } - - private void startCertifyIntent(Intent intent) { - // Message is received after signing is done in KeychainService - ServiceProgressHandler saveHandler = new ServiceProgressHandler(this) { - @Override - public void handleMessage(Message message) { - // handle messages by standard KeychainIntentServiceHandler first - super.handleMessage(message); - - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - Bundle data = message.getData(); - CertifyResult result = data.getParcelable(CertifyResult.EXTRA_RESULT); - - result.createNotify(ViewKeyActivity.this).show(); - } - } - }; - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(saveHandler); - intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - - startActivityForResult(intent, 0); + startActivityForResult(intent, REQUEST_CERTIFY); } private void showQrCodeDialog() { @@ -454,99 +411,95 @@ public class ViewKeyActivity extends BaseNfcActivity implements startActivityForResult(intent, requestCode); } - private void exportToFile(Uri dataUri, ExportHelper exportHelper, ProviderHelper providerHelper) { - try { - Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri); - - HashMap<String, Object> data = providerHelper.getGenericData( - baseUri, - new String[] {KeychainContract.Keys.MASTER_KEY_ID, KeychainContract.KeyRings.HAS_SECRET}, - new int[] {ProviderHelper.FIELD_TYPE_INTEGER, ProviderHelper.FIELD_TYPE_INTEGER}); - - exportHelper.showExportKeysDialog( - new long[] {(Long) data.get(KeychainContract.KeyRings.MASTER_KEY_ID)}, - Constants.Path.APP_DIR_FILE, ((Long) data.get(KeychainContract.KeyRings.HAS_SECRET) != 0) - ); - } catch (ProviderHelper.NotFoundException e) { - Notify.create(this, R.string.error_key_not_found, Notify.Style.ERROR).show(); - Log.e(Constants.TAG, "Key not found", e); - } + private void backupToFile() { + new ExportHelper(this).showExportKeysDialog( + mMasterKeyId, Constants.Path.APP_DIR_FILE, true); } private void deleteKey() { - // Message is received after key is deleted - Handler returnHandler = new Handler() { - @Override - public void handleMessage(Message message) { - if (message.arg1 == MessageStatus.OKAY.ordinal()) { - setResult(RESULT_CANCELED); - finish(); - } - } - }; + Intent deleteIntent = new Intent(this, DeleteKeyDialogActivity.class); + + deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_DELETE_MASTER_KEY_IDS, + new long[]{mMasterKeyId}); + deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_HAS_SECRET, mIsSecret); + if (mIsSecret) { + // for upload in case key is secret + deleteIntent.putExtra(DeleteKeyDialogActivity.EXTRA_KEYSERVER, + Preferences.getPreferences(this).getPreferredKeyserver()); + } - // Create a new Messenger for the communication back - Messenger messenger = new Messenger(returnHandler); - DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger, - new long[] {mMasterKeyId}); - deleteKeyDialog.show(getSupportFragmentManager(), "deleteKeyDialog"); + startActivityForResult(deleteIntent, REQUEST_DELETE); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (mOperationHelper != null) { - mOperationHelper.handleActivityResult(requestCode, resultCode, data); + if (mOperationHelper.handleActivityResult(requestCode, resultCode, data)) { + return; + } + + if (resultCode != Activity.RESULT_OK) { + return; } - if (requestCode == REQUEST_QR_FINGERPRINT && resultCode == Activity.RESULT_OK) { + switch (requestCode) { + case REQUEST_QR_FINGERPRINT: { - // If there is an EXTRA_RESULT, that's an error. Just show it. - if (data.hasExtra(OperationResult.EXTRA_RESULT)) { - OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); - result.createNotify(this).show(); + // If there is an EXTRA_RESULT, that's an error. Just show it. + if (data.hasExtra(OperationResult.EXTRA_RESULT)) { + OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); + result.createNotify(this).show(); + return; + } + + String fp = data.getStringExtra(ImportKeysProxyActivity.EXTRA_FINGERPRINT); + if (fp == null) { + Notify.create(this, R.string.error_scan_fp, Notify.LENGTH_LONG, Style.ERROR).show(); + return; + } + if (mFingerprint.equalsIgnoreCase(fp)) { + certifyImmediate(); + } else { + Notify.create(this, R.string.error_scan_match, Notify.LENGTH_LONG, Style.ERROR).show(); + } return; } - String fp = data.getStringExtra(ImportKeysProxyActivity.EXTRA_FINGERPRINT); - if (fp == null) { - Notify.create(this, "Error scanning fingerprint!", - Notify.LENGTH_LONG, Notify.Style.ERROR).show(); + case REQUEST_BACKUP: { + backupToFile(); return; } - if (mFingerprint.equalsIgnoreCase(fp)) { - certifyImmediate(); - } else { - Notify.create(this, "Fingerprints did not match!", - Notify.LENGTH_LONG, Notify.Style.ERROR).show(); - } - return; - } + case REQUEST_CERTIFY: { + if (data.hasExtra(OperationResult.EXTRA_RESULT)) { + OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); + result.createNotify(this).show(); + } + return; + } - if (requestCode == REQUEST_DELETE && resultCode == Activity.RESULT_OK) { - deleteKey(); + case REQUEST_DELETE: { + setResult(RESULT_OK, data); + finish(); + return; + } } - if (requestCode == REQUEST_EXPORT && resultCode == Activity.RESULT_OK) { - exportToFile(mDataUri, mExportHelper, mProviderHelper); - } + super.onActivityResult(requestCode, resultCode, data); - if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) { - OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT); - result.createNotify(this).show(); - } else { - super.onActivityResult(requestCode, resultCode, data); - } } @Override - protected void onNfcPerform() throws IOException { + protected void doNfcInBackground() throws IOException { + + mNfcFingerprints = nfcGetFingerprints(); + mNfcUserId = nfcGetUserId(); + mNfcAid = nfcGetAid(); + } - final byte[] nfcFingerprints = nfcGetFingerprints(); - final String nfcUserId = nfcGetUserId(); - final byte[] nfcAid = nfcGetAid(); + @Override + protected void onNfcPostExecute() throws IOException { - long yubiKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints); + long yubiKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); try { @@ -557,7 +510,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements // if the master key of that key matches this one, just show the yubikey dialog if (KeyFormattingUtils.convertFingerprintToHex(candidateFp).equals(mFingerprint)) { - showYubiKeyFragment(nfcFingerprints, nfcUserId, nfcAid); + showYubiKeyFragment(mNfcFingerprints, mNfcUserId, mNfcAid); return; } @@ -570,9 +523,9 @@ public class ViewKeyActivity extends BaseNfcActivity implements Intent intent = new Intent( ViewKeyActivity.this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); startActivity(intent); finish(); } @@ -586,15 +539,14 @@ public class ViewKeyActivity extends BaseNfcActivity implements public void onAction() { Intent intent = new Intent( ViewKeyActivity.this, CreateKeyActivity.class); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); startActivity(intent); finish(); } }, R.string.snack_yubikey_import).show(); } - } public void showYubiKeyFragment( @@ -629,7 +581,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements long keyId = new ProviderHelper(this) .getCachedPublicKeyRing(dataUri) .extractOrGetMasterKeyId(); - long[] encryptionKeyIds = new long[] {keyId}; + long[] encryptionKeyIds = new long[]{keyId}; Intent intent; if (text) { intent = new Intent(this, EncryptTextActivity.class); @@ -647,38 +599,6 @@ public class ViewKeyActivity extends BaseNfcActivity implements } } - private void updateFromKeyserver(Uri dataUri, ProviderHelper providerHelper) - throws ProviderHelper.NotFoundException { - - mIsRefreshing = true; - mRefreshItem.setEnabled(false); - mRefreshItem.setActionView(mRefresh); - mRefresh.startAnimation(mRotate); - - byte[] blob = (byte[]) providerHelper.getGenericData( - KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri), - KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); - String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); - - ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null); - ArrayList<ParcelableKeyRing> entries = new ArrayList<>(); - entries.add(keyEntry); - mKeyList = entries; - - // search config - { - Preferences prefs = Preferences.getPreferences(this); - Preferences.CloudSearchPrefs cloudPrefs = - new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); - mKeyserver = cloudPrefs.keyserver; - } - - mOperationHelper = new CryptoOperationHelper<>( - this, this, R.string.progress_importing); - - mOperationHelper.cryptoOperation(); - } - private void editKey(Uri dataUri) { Intent editIntent = new Intent(this, EditKeyActivity.class); editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri)); @@ -735,7 +655,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements // These are the rows that we will retrieve. - static final String[] PROJECTION = new String[] { + static final String[] PROJECTION = new String[]{ KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, @@ -771,6 +691,25 @@ public class ViewKeyActivity extends BaseNfcActivity implements int mPreviousColor = 0; + /** + * Calculate a reasonable color for the status bar based on the given toolbar color. + * Style guides want the toolbar color to be a "700" on the Android scale and the status + * bar should be the same color at "500", this is roughly 17 / 20th of the value in each + * channel. + * http://www.google.com/design/spec/style/color.html#color-color-palette + */ + static public int getStatusBarBackgroundColor(int color) { + int r = (color >> 16) & 0xff; + int g = (color >> 8) & 0xff; + int b = color & 0xff; + + r = r * 17 / 20; + g = g * 17 / 20; + b = b * 17 / 20; + + return (0xff << 24) | (r << 16) | (g << 8) | b; + } + @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { /* TODO better error handling? May cause problems when a key is deleted, @@ -823,7 +762,8 @@ public class ViewKeyActivity extends BaseNfcActivity implements AsyncTask<Long, Void, Bitmap> photoTask = new AsyncTask<Long, Void, Bitmap>() { protected Bitmap doInBackground(Long... mMasterKeyId) { - return ContactHelper.loadPhotoByMasterKeyId(getContentResolver(), mMasterKeyId[0], true); + return ContactHelper.loadPhotoByMasterKeyId(getContentResolver(), + mMasterKeyId[0], true); } protected void onPostExecute(Bitmap photo) { @@ -839,7 +779,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, State.REVOKED, R.color.icons, true); - color = getResources().getColor(R.color.android_red_light); + color = getResources().getColor(R.color.key_flag_red); mActionEncryptFile.setVisibility(View.GONE); mActionEncryptText.setVisibility(View.GONE); @@ -855,7 +795,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, State.EXPIRED, R.color.icons, true); - color = getResources().getColor(R.color.android_red_light); + color = getResources().getColor(R.color.key_flag_red); mActionEncryptFile.setVisibility(View.GONE); mActionEncryptText.setVisibility(View.GONE); @@ -865,7 +805,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements } else if (mIsSecret) { mStatusText.setText(R.string.view_key_my_key); mStatusImage.setVisibility(View.GONE); - color = getResources().getColor(R.color.primary); + color = getResources().getColor(R.color.key_flag_green); // reload qr code only if the fingerprint changed if (!mFingerprint.equals(mQrCodeLoaded)) { loadQrCode(mFingerprint); @@ -903,6 +843,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements mActionNfc.setVisibility(View.GONE); } mFab.setVisibility(View.VISIBLE); + // noinspection deprecation (no getDrawable with theme at current minApi level 15!) mFab.setIconDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp)); } else { mActionEncryptFile.setVisibility(View.VISIBLE); @@ -915,7 +856,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, State.VERIFIED, R.color.icons, true); - color = getResources().getColor(R.color.primary); + color = getResources().getColor(R.color.key_flag_green); photoTask.execute(mMasterKeyId); mFab.setVisibility(View.GONE); @@ -924,20 +865,21 @@ public class ViewKeyActivity extends BaseNfcActivity implements mStatusImage.setVisibility(View.VISIBLE); KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, State.UNVERIFIED, R.color.icons, true); - color = getResources().getColor(R.color.android_orange_light); + color = getResources().getColor(R.color.key_flag_orange); mFab.setVisibility(View.VISIBLE); } } if (mPreviousColor == 0 || mPreviousColor == color) { - mStatusBar.setBackgroundColor(color); + mStatusBar.setBackgroundColor(getStatusBarBackgroundColor(color)); mBigToolbar.setBackgroundColor(color); mPreviousColor = color; } else { ObjectAnimator colorFade1 = ObjectAnimator.ofObject(mStatusBar, "backgroundColor", - new ArgbEvaluator(), mPreviousColor, color); + new ArgbEvaluator(), mPreviousColor, + getStatusBarBackgroundColor(color)); ObjectAnimator colorFade2 = ObjectAnimator.ofObject(mBigToolbar, "backgroundColor", new ArgbEvaluator(), mPreviousColor, color); @@ -965,6 +907,36 @@ public class ViewKeyActivity extends BaseNfcActivity implements // CryptoOperationHelper.Callback functions + + private void updateFromKeyserver(Uri dataUri, ProviderHelper providerHelper) + throws ProviderHelper.NotFoundException { + + mIsRefreshing = true; + mRefreshItem.setEnabled(false); + mRefreshItem.setActionView(mRefresh); + mRefresh.startAnimation(mRotate); + + byte[] blob = (byte[]) providerHelper.getGenericData( + KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri), + KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB); + String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob); + + ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null); + ArrayList<ParcelableKeyRing> entries = new ArrayList<>(); + entries.add(keyEntry); + mKeyList = entries; + + // search config + { + Preferences prefs = Preferences.getPreferences(this); + Preferences.CloudSearchPrefs cloudPrefs = + new Preferences.CloudSearchPrefs(true, true, prefs.getPreferredKeyserver()); + mKeyserver = cloudPrefs.keyserver; + } + + mOperationHelper.cryptoOperation(); + } + @Override public ImportKeyringParcel createOperationInput() { return new ImportKeyringParcel(mKeyList, mKeyserver); @@ -989,6 +961,6 @@ public class ViewKeyActivity extends BaseNfcActivity implements @Override public boolean onCryptoSetProgress(String msg, int progress, int max) { - return false; + return true; } -}
\ No newline at end of file +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java index 6669f2654..673092e61 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvActivity.java @@ -211,18 +211,18 @@ public class ViewKeyAdvActivity extends BaseActivity implements // Note: order is important int color; if (isRevoked || isExpired) { - color = getResources().getColor(R.color.android_red_light); + color = getResources().getColor(R.color.key_flag_red); } else if (isSecret) { - color = getResources().getColor(R.color.primary); + color = getResources().getColor(R.color.android_green_light); } else { if (isVerified) { - color = getResources().getColor(R.color.primary); + color = getResources().getColor(R.color.android_green_light); } else { - color = getResources().getColor(R.color.android_orange_light); + color = getResources().getColor(R.color.key_flag_orange); } } mToolbar.setBackgroundColor(color); - mStatusBar.setBackgroundColor(color); + mStatusBar.setBackgroundColor(ViewKeyActivity.getStatusBarBackgroundColor(color)); mSlidingTabLayout.setBackgroundColor(color); break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java index b44e6dc78..4a46896bc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -25,6 +25,9 @@ import java.io.OutputStreamWriter; import android.app.Activity; import android.app.ActivityOptions; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; @@ -49,16 +52,18 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ClipboardReflection; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; +import org.sufficientlysecure.keychain.util.ExportHelper; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.NfcHelper; @@ -79,6 +84,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements private Uri mDataUri; private byte[] mFingerprint; + private long mMasterKeyId; @Override public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { @@ -101,11 +107,12 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements View vFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share); View vFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard); View vKeyShareButton = view.findViewById(R.id.view_key_action_key_share); + View vKeySafeButton = view.findViewById(R.id.view_key_action_key_export); View vKeyNfcButton = view.findViewById(R.id.view_key_action_key_nfc); View vKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard); ImageButton vKeySafeSlingerButton = (ImageButton) view.findViewById(R.id.view_key_action_key_safeslinger); View vKeyUploadButton = view.findViewById(R.id.view_key_action_upload); - vKeySafeSlingerButton.setColorFilter(getResources().getColor(R.color.tertiary_text_light), + vKeySafeSlingerButton.setColorFilter(FormattingUtils.getColorFromAttr(getActivity(), R.attr.colorTertiaryText), PorterDuff.Mode.SRC_IN); vFingerprintShareButton.setOnClickListener(new View.OnClickListener() { @@ -126,6 +133,12 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements share(false, false); } }); + vKeySafeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + exportToFile(); + } + }); vKeyClipboardButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -161,6 +174,11 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements return root; } + private void exportToFile() { + new ExportHelper(getActivity()).showExportKeysDialog( + mMasterKeyId, Constants.Path.APP_DIR_FILE, false); + } + private void startSafeSlinger(Uri dataUri) { long keyId = 0; try { @@ -197,7 +215,15 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements } if (toClipboard) { - ClipboardReflection.copyToClipboard(activity, content); + ClipboardManager clipMan = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + if (clipMan == null) { + Notify.create(activity, R.string.error_clipboard_copy, Style.ERROR); + return; + } + + ClipData clip = ClipData.newPlainText(Constants.CLIPBOARD_LABEL, content); + clipMan.setPrimaryClip(clip); + Notify.create(activity, fingerprintOnly ? R.string.fingerprint_copied_to_clipboard : R.string.key_copied_to_clipboard, Notify.Style.OK).show(); return; @@ -344,6 +370,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements /** Load QR Code asynchronously and with a fade in animation */ private void setFingerprint(byte[] fingerprintBlob) { mFingerprint = fingerprintBlob; + mMasterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(fingerprintBlob); final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob); mFingerprintView.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint)); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java index 092ab40d6..b118c3ad0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyTrustFragment.java @@ -51,6 +51,9 @@ import org.sufficientlysecure.keychain.service.KeybaseVerificationParcel; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ParcelableProxy; +import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.util.ArrayList; import java.util.Hashtable; @@ -197,8 +200,22 @@ public class ViewKeyTrustFragment extends LoaderFragment implements mStartSearch.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - mStartSearch.setEnabled(false); - new DescribeKey().execute(fingerprint); + final Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(getActivity()) + .getProxyPrefs(); + + Runnable ignoreTor = new Runnable() { + @Override + public void run() { + mStartSearch.setEnabled(false); + new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint); + } + }; + + if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, proxyPrefs, + getActivity())) { + mStartSearch.setEnabled(false); + new DescribeKey(proxyPrefs.parcelableProxy).execute(fingerprint); + } } }); } @@ -229,6 +246,11 @@ public class ViewKeyTrustFragment extends LoaderFragment implements // look for evidence from keybase in the background, make tabular version of result // private class DescribeKey extends AsyncTask<String, Void, ResultPage> { + ParcelableProxy mParcelableProxy; + + public DescribeKey(ParcelableProxy parcelableProxy) { + mParcelableProxy = parcelableProxy; + } @Override protected ResultPage doInBackground(String... args) { @@ -237,7 +259,7 @@ public class ViewKeyTrustFragment extends LoaderFragment implements final ArrayList<CharSequence> proofList = new ArrayList<CharSequence>(); final Hashtable<Integer, ArrayList<Proof>> proofs = new Hashtable<Integer, ArrayList<Proof>>(); try { - User keybaseUser = User.findByFingerprint(fingerprint); + User keybaseUser = User.findByFingerprint(fingerprint, mParcelableProxy.getProxy()); for (Proof proof : keybaseUser.getProofs()) { Integer proofType = proof.getType(); appendIfOK(proofs, proofType, proof); @@ -376,7 +398,7 @@ public class ViewKeyTrustFragment extends LoaderFragment implements mProofVerifyDetail.setVisibility(View.GONE); - mKeybaseOpHelper = new CryptoOperationHelper<>(this, this, + mKeybaseOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_verifying_signature); mKeybaseOpHelper.cryptoOperation(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java index f8c3b59ea..f980f297b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyYubiKeyFragment.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui; + import java.nio.ByteBuffer; import java.util.Arrays; @@ -39,12 +40,12 @@ import org.sufficientlysecure.keychain.operations.results.PromoteKeyResult; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType; import org.sufficientlysecure.keychain.provider.KeychainContract.Keys; import org.sufficientlysecure.keychain.service.PromoteKeyringParcel; -import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment; +import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; public class ViewKeyYubiKeyFragment - extends CryptoOperationFragment<PromoteKeyringParcel, PromoteKeyResult> + extends QueueingCryptoOperationFragment<PromoteKeyringParcel, PromoteKeyResult> implements LoaderCallbacks<Cursor> { public static final String ARG_MASTER_KEY_ID = "master_key_id"; @@ -75,6 +76,10 @@ public class ViewKeyYubiKeyFragment return frag; } + public ViewKeyYubiKeyFragment() { + super(null); + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -210,7 +215,8 @@ public class ViewKeyYubiKeyFragment } @Override - protected void onCryptoOperationResult(PromoteKeyResult result) { + public void onQueuedOperationSuccess(PromoteKeyResult result) { result.createNotify(getActivity()).show(); } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java index 93c6593ab..0be7e8f76 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysAdapter.java @@ -177,9 +177,9 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { } if (entry.isRevoked()) { - KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.REVOKED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.REVOKED, R.color.key_flag_gray); } else if (entry.isExpired()) { - KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.EXPIRED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, State.EXPIRED, R.color.key_flag_gray); } if (entry.isRevoked() || entry.isExpired()) { @@ -188,9 +188,9 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { // no more space for algorithm display holder.algorithm.setVisibility(View.GONE); - holder.mainUserId.setTextColor(getContext().getResources().getColor(R.color.bg_gray)); - holder.mainUserIdRest.setTextColor(getContext().getResources().getColor(R.color.bg_gray)); - holder.keyId.setTextColor(getContext().getResources().getColor(R.color.bg_gray)); + holder.mainUserId.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); + holder.mainUserIdRest.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); + holder.keyId.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); } else { holder.status.setVisibility(View.GONE); holder.algorithm.setVisibility(View.VISIBLE); @@ -198,11 +198,11 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { if (entry.isSecretKey()) { holder.mainUserId.setTextColor(Color.RED); } else { - holder.mainUserId.setTextColor(Color.BLACK); + holder.mainUserId.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText)); } - holder.mainUserIdRest.setTextColor(Color.BLACK); - holder.keyId.setTextColor(Color.BLACK); + holder.mainUserIdRest.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText)); + holder.keyId.setTextColor(FormattingUtils.getColorFromAttr(mActivity, R.attr.colorText)); } if (entry.getUserIds().size() == 1) { @@ -242,9 +242,9 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { uidView.setPadding(0, 0, FormattingUtils.dpToPx(getContext(), 8), 0); if (entry.isRevoked() || entry.isExpired()) { - uidView.setTextColor(getContext().getResources().getColor(R.color.bg_gray)); + uidView.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); } else { - uidView.setTextColor(getContext().getResources().getColor(R.color.black)); + uidView.setTextColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorText)); } holder.userIdsList.addView(uidView); @@ -258,9 +258,9 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> { emailView.setText(highlighter.highlight(email)); if (entry.isRevoked() || entry.isExpired()) { - emailView.setTextColor(getContext().getResources().getColor(R.color.bg_gray)); + emailView.setTextColor(getContext().getResources().getColor(R.color.key_flag_gray)); } else { - emailView.setTextColor(getContext().getResources().getColor(R.color.black)); + emailView.setTextColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorText)); } holder.userIdsList.addView(emailView); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java index af919f3b6..e77c92923 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListCloudLoader.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui.adapter; import android.content.Context; +import android.support.annotation.Nullable; import android.support.v4.content.AsyncTaskLoader; import org.sufficientlysecure.keychain.Constants; @@ -26,8 +27,12 @@ import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry; import org.sufficientlysecure.keychain.keyimport.Keyserver; import org.sufficientlysecure.keychain.operations.results.GetKeyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.ParcelableProxy; import org.sufficientlysecure.keychain.util.Preferences; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; import java.util.ArrayList; @@ -38,15 +43,27 @@ public class ImportKeysListCloudLoader Preferences.CloudSearchPrefs mCloudPrefs; String mServerQuery; + private ParcelableProxy mParcelableProxy; private ArrayList<ImportKeysListEntry> mEntryList = new ArrayList<>(); private AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper; - public ImportKeysListCloudLoader(Context context, String serverQuery, Preferences.CloudSearchPrefs cloudPrefs) { + /** + * Searches a keyserver as specified in cloudPrefs, using an explicit proxy if passed + * + * @param serverQuery string to search on servers for. If is a fingerprint, + * will enforce fingerprint check + * @param cloudPrefs contains keyserver to search on, whether to search on the keyserver, + * and whether to search keybase.io + * @param parcelableProxy explicit proxy to use. If null, will retrieve from preferences + */ + public ImportKeysListCloudLoader(Context context, String serverQuery, Preferences.CloudSearchPrefs cloudPrefs, + @Nullable ParcelableProxy parcelableProxy) { super(context); mContext = context; mServerQuery = serverQuery; mCloudPrefs = cloudPrefs; + mParcelableProxy = parcelableProxy; } @Override @@ -95,9 +112,32 @@ public class ImportKeysListCloudLoader * Query keyserver */ private void queryServer(boolean enforceFingerprint) { + ParcelableProxy parcelableProxy; + + if (mParcelableProxy == null) { + // no explicit proxy specified, fetch from preferences + if (OrbotHelper.isOrbotInRequiredState(mContext)) { + parcelableProxy = Preferences.getPreferences(mContext).getProxyPrefs() + .parcelableProxy; + } else { + // user needs to enable/install orbot + mEntryList.clear(); + GetKeyResult pendingResult = new GetKeyResult(null, + RequiredInputParcel.createOrbotRequiredOperation(), + new CryptoInputParcel()); + mEntryListWrapper = new AsyncTaskResultWrapper<>(mEntryList, pendingResult); + return; + } + } else { + parcelableProxy = mParcelableProxy; + } + try { - ArrayList<ImportKeysListEntry> searchResult - = CloudSearch.search(mServerQuery, mCloudPrefs); + ArrayList<ImportKeysListEntry> searchResult = CloudSearch.search( + mServerQuery, + mCloudPrefs, + parcelableProxy.getProxy() + ); mEntryList.clear(); // add result to data diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java index e545b007b..aba1eb0d8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyAdapter.java @@ -21,7 +21,6 @@ package org.sufficientlysecure.keychain.ui.adapter; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Date; import java.util.List; @@ -43,6 +42,7 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.ui.util.Highlighter; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; @@ -50,6 +50,7 @@ public class KeyAdapter extends CursorAdapter { protected String mQuery; protected LayoutInflater mInflater; + protected Context mContext; // These are the rows that we will retrieve. public static final String[] PROJECTION = new String[]{ @@ -78,6 +79,7 @@ public class KeyAdapter extends CursorAdapter { public KeyAdapter(Context context, Cursor c, int flags) { super(context, c, flags); + mContext = context; mInflater = LayoutInflater.from(context); } @@ -107,7 +109,7 @@ public class KeyAdapter extends CursorAdapter { mCreationDate = (TextView) view.findViewById(R.id.key_list_item_creation); } - public void setData(Context context, KeyItem item, Highlighter highlighter) { + public void setData(Context context, KeyItem item, Highlighter highlighter, boolean enabled) { mDisplayedItem = item; @@ -126,36 +128,39 @@ public class KeyAdapter extends CursorAdapter { } } + // sort of a hack: if this item isn't enabled, we make it clickable + // to intercept its click events + mView.setClickable(!enabled); + { // set edit button and status, specific by key type mMasterKeyId = item.mKeyId; + int textColor; + // Note: order is important! if (item.mIsRevoked) { KeyFormattingUtils - .setStatusImage(context, mStatus, null, State.REVOKED, R.color.bg_gray); + .setStatusImage(context, mStatus, null, State.REVOKED, R.color.key_flag_gray); mStatus.setVisibility(View.VISIBLE); mSlinger.setVisibility(View.GONE); - mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray)); - mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray)); + textColor = context.getResources().getColor(R.color.key_flag_gray); } else if (item.mIsExpired) { - KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.key_flag_gray); mStatus.setVisibility(View.VISIBLE); mSlinger.setVisibility(View.GONE); - mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray)); - mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray)); + textColor = context.getResources().getColor(R.color.key_flag_gray); } else if (item.mIsSecret) { mStatus.setVisibility(View.GONE); if (mSlingerButton.hasOnClickListeners()) { mSlingerButton.setColorFilter( - context.getResources().getColor(R.color.tertiary_text_light), + FormattingUtils.getColorFromAttr(context, R.attr.colorTertiaryText), PorterDuff.Mode.SRC_IN); mSlinger.setVisibility(View.VISIBLE); } else { mSlinger.setVisibility(View.GONE); } - mMainUserId.setTextColor(context.getResources().getColor(R.color.black)); - mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black)); + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); } else { // this is a public key - show if it's verified if (item.mIsVerified) { @@ -166,10 +171,16 @@ public class KeyAdapter extends CursorAdapter { mStatus.setVisibility(View.VISIBLE); } mSlinger.setVisibility(View.GONE); - mMainUserId.setTextColor(context.getResources().getColor(R.color.black)); - mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.black)); + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } + + if (!enabled) { + textColor = context.getResources().getColor(R.color.key_flag_gray); } + mMainUserId.setTextColor(textColor); + mMainUserIdRest.setTextColor(textColor); + if (item.mHasDuplicate) { String dateTime = DateUtils.formatDateTime(context, item.mCreation.getTime(), @@ -179,6 +190,7 @@ public class KeyAdapter extends CursorAdapter { mCreationDate.setText(context.getString(R.string.label_key_created, dateTime)); + mCreationDate.setTextColor(textColor); mCreationDate.setVisibility(View.VISIBLE); } else { mCreationDate.setVisibility(View.GONE); @@ -205,9 +217,11 @@ public class KeyAdapter extends CursorAdapter { @Override public void bindView(View view, Context context, Cursor cursor) { Highlighter highlighter = new Highlighter(context, mQuery); - KeyItemViewHolder h = (KeyItemViewHolder) view.getTag(); KeyItem item = new KeyItem(cursor); - h.setData(context, item, highlighter); + boolean isEnabled = isEnabled(cursor); + + KeyItemViewHolder h = (KeyItemViewHolder) view.getTag(); + h.setData(context, item, highlighter, isEnabled); } public boolean isSecretAvailable(int id) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index a6cb52977..4ea651bb5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -149,11 +149,11 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { boolean enabled; if (cursor.getInt(mIndexIsRevoked) != 0) { h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, State.REVOKED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, State.REVOKED, R.color.key_flag_gray); enabled = false; } else if (cursor.getInt(mIndexIsExpiry) != 0) { h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, State.EXPIRED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, State.EXPIRED, R.color.key_flag_gray); enabled = false; } else { h.statusIcon.setVisibility(View.GONE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java index 87539ea05..24f5f04a1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SubkeysAdapter.java @@ -284,27 +284,27 @@ public class SubkeysAdapter extends CursorAdapter { vStatus.setVisibility(View.VISIBLE); vCertifyIcon.setColorFilter( - mContext.getResources().getColor(R.color.bg_gray), + mContext.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); vSignIcon.setColorFilter( - mContext.getResources().getColor(R.color.bg_gray), + mContext.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); vEncryptIcon.setColorFilter( - mContext.getResources().getColor(R.color.bg_gray), + mContext.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); vAuthenticateIcon.setColorFilter( - mContext.getResources().getColor(R.color.bg_gray), + mContext.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); if (isRevoked) { vStatus.setImageResource(R.drawable.status_signature_revoked_cutout_24dp); vStatus.setColorFilter( - mContext.getResources().getColor(R.color.bg_gray), + mContext.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); } else if (isExpired) { vStatus.setImageResource(R.drawable.status_signature_expired_cutout_24dp); vStatus.setColorFilter( - mContext.getResources().getColor(R.color.bg_gray), + mContext.getResources().getColor(R.color.key_flag_gray), PorterDuff.Mode.SRC_IN); } } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java index d1103ac1f..e2c6b0928 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java @@ -128,7 +128,7 @@ public class UserIdsAdapter extends UserAttributesAdapter { if (isRevoked) { // set revocation icon (can this even be primary?) - KeyFormattingUtils.setStatusImage(mContext, vVerified, null, State.REVOKED, R.color.bg_gray); + KeyFormattingUtils.setStatusImage(mContext, vVerified, null, State.REVOKED, R.color.key_flag_gray); // disable revoked user ids vName.setEnabled(false); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java index 847d76f30..66b784f9b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.ui.base; import android.app.Activity; +import android.content.Intent; import android.os.Bundle; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; @@ -29,6 +30,7 @@ import android.view.ViewGroup; import android.widget.TextView; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; /** * Setups Toolbar @@ -36,14 +38,28 @@ import org.sufficientlysecure.keychain.R; public abstract class BaseActivity extends AppCompatActivity { protected Toolbar mToolbar; protected View mStatusBar; + protected ThemeChanger mThemeChanger; @Override protected void onCreate(Bundle savedInstanceState) { + mThemeChanger = new ThemeChanger(this); + mThemeChanger.changeTheme(); super.onCreate(savedInstanceState); initLayout(); initToolbar(); } + @Override + protected void onResume() { + super.onResume(); + + if (mThemeChanger.changeTheme()) { + Intent intent = getIntent(); + finish(); + startActivity(intent); + } + } + protected void initLayout() { } @@ -87,9 +103,7 @@ public abstract class BaseActivity extends AppCompatActivity { mToolbar.setNavigationOnClickListener(cancelOnClickListener); } - /** - * Close button only - */ + /** Close button only */ protected void setFullScreenDialogClose(View.OnClickListener cancelOnClickListener, boolean white) { if (white) { setActionBarIcon(R.drawable.ic_close_white_24dp); @@ -104,6 +118,17 @@ public abstract class BaseActivity extends AppCompatActivity { setFullScreenDialogClose(cancelOnClickListener, true); } + /** Close button only, with finish-action and given return status, white. */ + protected void setFullScreenDialogClose(final int result, boolean white) { + setFullScreenDialogClose(new View.OnClickListener() { + @Override + public void onClick(View v) { + setResult(result); + finish(); + } + }, white); + } + /** * Inflate custom design with two buttons using drawables. * This does not conform to the Material Design Guidelines, but we deviate here as this is used diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java index bede16b2a..c472eaa4c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java @@ -29,7 +29,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.nfc.NfcAdapter; import android.nfc.Tag; +import android.nfc.TagLostException; import android.nfc.tech.IsoDep; +import android.os.AsyncTask; import android.os.Bundle; import android.widget.Toast; @@ -63,6 +65,8 @@ public abstract class BaseNfcActivity extends BaseActivity { public static final int REQUEST_CODE_PIN = 1; + public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled"; + protected Passphrase mPin; protected Passphrase mAdminPin; protected boolean mPw1ValidForMultipleSignatures; @@ -71,13 +75,123 @@ public abstract class BaseNfcActivity extends BaseActivity { protected boolean mPw3Validated; private NfcAdapter mNfcAdapter; private IsoDep mIsoDep; + private boolean mTagHandlingEnabled; private static final int TIMEOUT = 100000; + private byte[] mNfcFingerprints; + private String mNfcUserId; + private byte[] mNfcAid; + + /** + * Override to change UI before NFC handling (UI thread) + */ + protected void onNfcPreExecute() { + } + + /** + * Override to implement NFC operations (background thread) + */ + protected void doNfcInBackground() throws IOException { + mNfcFingerprints = nfcGetFingerprints(); + mNfcUserId = nfcGetUserId(); + mNfcAid = nfcGetAid(); + } + + /** + * Override to handle result of NFC operations (UI thread) + */ + protected void onNfcPostExecute() throws IOException { + + final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); + + try { + CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing( + KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)); + long masterKeyId = ring.getMasterKeyId(); + + Intent intent = new Intent(this, ViewKeyActivity.class); + intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, mNfcAid); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); + startActivity(intent); + } catch (PgpKeyNotFoundException e) { + Intent intent = new Intent(this, CreateKeyActivity.class); + intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, mNfcAid); + intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); + intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); + startActivity(intent); + } + } + + /** + * Override to use something different than Notify (UI thread) + */ + protected void onNfcError(String error) { + Notify.create(this, error, Style.WARN).show(); + } + + public void handleIntentInBackground(final Intent intent) { + // Actual NFC operations are executed in doInBackground to not block the UI thread + new AsyncTask<Void, Void, Exception>() { + @Override + protected void onPreExecute() { + super.onPreExecute(); + onNfcPreExecute(); + } + + @Override + protected Exception doInBackground(Void... params) { + try { + handleTagDiscoveredIntent(intent); + } catch (CardException e) { + return e; + } catch (IOException e) { + return e; + } + + return null; + } + + @Override + protected void onPostExecute(Exception exception) { + super.onPostExecute(exception); + + if (exception != null) { + handleNfcError(exception); + return; + } + + try { + onNfcPostExecute(); + } catch (IOException e) { + handleNfcError(e); + } + } + }.execute(); + } + + protected void pauseTagHandling() { + mTagHandlingEnabled = false; + } + + protected void resumeTagHandling() { + mTagHandlingEnabled = true; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // Check whether we're recreating a previously destroyed instance + if (savedInstanceState != null) { + // Restore value of members from saved state + mTagHandlingEnabled = savedInstanceState.getBoolean(EXTRA_TAG_HANDLING_ENABLED); + } else { + mTagHandlingEnabled = true; + } + Intent intent = getIntent(); String action = intent.getAction(); if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) { @@ -86,36 +200,43 @@ public abstract class BaseNfcActivity extends BaseActivity { } + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putBoolean(EXTRA_TAG_HANDLING_ENABLED, mTagHandlingEnabled); + } + /** * This activity is started as a singleTop activity. * All new NFC Intents which are delivered to this activity are handled here */ @Override - public void onNewIntent(Intent intent) { - if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { - try { - handleTagDiscoveredIntent(intent); - } catch (CardException e) { - handleNfcError(e); - } catch (IOException e) { - handleNfcError(e); - } + public void onNewIntent(final Intent intent) { + if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction()) + && mTagHandlingEnabled) { + handleIntentInBackground(intent); } } - public void handleNfcError(IOException e) { - + private void handleNfcError(Exception e) { Log.e(Constants.TAG, "nfc error", e); - Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show(); - } - public void handleNfcError(CardException e) { - Log.e(Constants.TAG, "card error", e); + if (e instanceof TagLostException) { + onNfcError(getString(R.string.error_nfc_tag_lost)); + return; + } - short status = e.getResponseCode(); + short status; + if (e instanceof CardException) { + status = ((CardException) e).getResponseCode(); + } else { + status = -1; + } // When entering a PIN, a status of 63CX indicates X attempts remaining. if ((status & (short)0xFFF0) == 0x63C0) { - Notify.create(this, getString(R.string.error_pin, status & 0x000F), Style.WARN).show(); + int tries = status & 0x000F; + onNfcError(getResources().getQuantityString(R.plurals.error_pin, tries, tries)); return; } @@ -124,63 +245,62 @@ public abstract class BaseNfcActivity extends BaseActivity { // These errors should not occur in everyday use; if they are returned, it means we // made a mistake sending data to the card, or the card is misbehaving. case 0x6A80: { - Notify.create(this, getString(R.string.error_nfc_bad_data), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_bad_data)); break; } case 0x6883: { - Notify.create(this, getString(R.string.error_nfc_chaining_error), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_chaining_error)); break; } case 0x6B00: { - Notify.create(this, getString(R.string.error_nfc_header, "P1/P2"), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_header, "P1/P2")); break; } case 0x6D00: { - Notify.create(this, getString(R.string.error_nfc_header, "INS"), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_header, "INS")); break; } case 0x6E00: { - Notify.create(this, getString(R.string.error_nfc_header, "CLA"), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_header, "CLA")); break; } // These error conditions are more likely to be experienced by an end user. case 0x6285: { - Notify.create(this, getString(R.string.error_nfc_terminated), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_terminated)); break; } case 0x6700: { - Notify.create(this, getString(R.string.error_nfc_wrong_length), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_wrong_length)); break; } case 0x6982: { - Notify.create(this, getString(R.string.error_nfc_security_not_satisfied), - Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_security_not_satisfied)); break; } case 0x6983: { - Notify.create(this, getString(R.string.error_nfc_authentication_blocked), - Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_authentication_blocked)); break; } case 0x6985: { - Notify.create(this, getString(R.string.error_nfc_conditions_not_satisfied), - Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_conditions_not_satisfied)); break; } // 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases. case 0x6A88: case 0x6A83: { - Notify.create(this, getString(R.string.error_nfc_data_not_found), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_data_not_found)); break; } // 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an // unhandled exception on the smart card. case 0x6F00: { - Notify.create(this, getString(R.string.error_nfc_unknown), Style.WARN).show(); + onNfcError(getString(R.string.error_nfc_unknown)); + break; + } + default: { + onNfcError(getString(R.string.error_nfc, e.getMessage())); break; } - default: - Notify.create(this, getString(R.string.error_nfc, e.getMessage()), Style.WARN).show(); } } @@ -311,43 +431,12 @@ public abstract class BaseNfcActivity extends BaseActivity { mPw1ValidatedForDecrypt = false; mPw3Validated = false; - // TODO: Handle non-default Admin PIN - mAdminPin = new Passphrase("12345678"); - - onNfcPerform(); - - mIsoDep.close(); - mIsoDep = null; + doNfcInBackground(); } - protected void onNfcPerform() throws IOException { - - final byte[] nfcFingerprints = nfcGetFingerprints(); - final String nfcUserId = nfcGetUserId(); - final byte[] nfcAid = nfcGetAid(); - - final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(nfcFingerprints); - - try { - CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing( - KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)); - long masterKeyId = ring.getMasterKeyId(); - - Intent intent = new Intent(this, ViewKeyActivity.class); - intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_AID, nfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_USER_ID, nfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints); - startActivity(intent); - } catch (PgpKeyNotFoundException e) { - Intent intent = new Intent(this, CreateKeyActivity.class); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, nfcAid); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, nfcUserId); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, nfcFingerprints); - startActivity(intent); - } - + public boolean isNfcConnected() { + return mIsoDep.isConnected(); } /** Return the key id from application specific data stored on tag, or null @@ -569,12 +658,12 @@ public abstract class BaseNfcActivity extends BaseActivity { */ public void nfcVerifyPIN(int mode) throws IOException { if (mPin != null || mode == 0x83) { - byte[] pin; + byte[] pin; if (mode == 0x83) { - pin = new String(mAdminPin.getCharArray()).getBytes(); + pin = mAdminPin.toStringUnsafe().getBytes(); } else { - pin = new String(mPin.getCharArray()).getBytes(); + pin = mPin.toStringUnsafe().getBytes(); } // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. @@ -609,14 +698,13 @@ public abstract class BaseNfcActivity extends BaseActivity { * conformance to the card's requirements for key length. * * @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. - * @param newPinString The new PW1 or PW3. + * @param newPin The new PW1 or PW3. */ - public void nfcModifyPIN(int pw, String newPinString) throws IOException { + public void nfcModifyPIN(int pw, byte[] newPin) throws IOException { final int MAX_PW1_LENGTH_INDEX = 1; final int MAX_PW3_LENGTH_INDEX = 3; byte[] pwStatusBytes = nfcGetPwStatusBytes(); - byte[] newPin = newPinString.getBytes(); if (pw == 0x81) { if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) { @@ -631,11 +719,10 @@ public abstract class BaseNfcActivity extends BaseActivity { } byte[] pin; - if (pw == 0x83) { - pin = new String(mAdminPin.getCharArray()).getBytes(); + pin = mAdminPin.toStringUnsafe().getBytes(); } else { - pin = new String(mPin.getCharArray()).getBytes(); + pin = mPin.toStringUnsafe().getBytes(); } // Command APDU for CHANGE REFERENCE DATA command (page 32) @@ -700,7 +787,7 @@ public abstract class BaseNfcActivity extends BaseActivity { throw new IOException("Invalid key slot"); } - RSAPrivateCrtKey crtSecretKey = null; + RSAPrivateCrtKey crtSecretKey; try { secretKey.unlock(passphrase); crtSecretKey = secretKey.getCrtSecretKey(); @@ -719,7 +806,7 @@ public abstract class BaseNfcActivity extends BaseActivity { } if (!mPw3Validated) { - nfcVerifyPIN(0x83); // (Verify PW1 with mode 83) + nfcVerifyPIN(0x83); // (Verify PW3 with mode 83) } byte[] header= Hex.decode( diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java index 17e4e6ede..06361e8cb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CachingCryptoOperationFragment.java @@ -8,7 +8,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult; public abstract class CachingCryptoOperationFragment <T extends Parcelable, S extends OperationResult> - extends CryptoOperationFragment<T, S> { + extends QueueingCryptoOperationFragment<T, S> { public static final String ARG_CACHED_ACTIONS = "cached_actions"; @@ -31,8 +31,12 @@ public abstract class CachingCryptoOperationFragment <T extends Parcelable, S ex } @Override - protected void onCryptoOperationResult(S result) { - super.onCryptoOperationResult(result); + public void onQueuedOperationSuccess(S result) { + mCachedActionsParcel = null; + } + + @Override + public void onQueuedOperationError(S result) { mCachedActionsParcel = null; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java index 19d808ba6..52507f3e9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java @@ -18,78 +18,112 @@ package org.sufficientlysecure.keychain.ui.base; +import android.content.Context; import android.content.Intent; +import android.os.Bundle; import android.os.Parcelable; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.service.KeychainService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -/** - * All fragments executing crypto operations need to extend this class. +/** This is a base class for fragments which implement a cryptoOperation. + * + * Subclasses of this class can call the cryptoOperation method to execute an + * operation in KeychainService which takes a parcelable of type T as its input + * and returns an OperationResult of type S as a result. + * + * The input (of type T) is not given directly to the cryptoOperation method, + * but must be provided by the overriden createOperationInput method to be + * available upon request during execution of the cryptoOperation. + * + * After running cryptoOperation, one of the onCryptoOperation*() methods will + * be called, depending on the success status of the operation. The subclass + * must override at least onCryptoOperationSuccess to proceed after a + * successful operation. + * + * @see KeychainService + * */ -public abstract class CryptoOperationFragment<T extends Parcelable, S extends OperationResult> +abstract class CryptoOperationFragment<T extends Parcelable, S extends OperationResult> extends Fragment implements CryptoOperationHelper.Callback<T, S> { - private CryptoOperationHelper<T, S> mOperationHelper; - - public CryptoOperationFragment() { + final private CryptoOperationHelper<T, S> mOperationHelper; - mOperationHelper = new CryptoOperationHelper<>(this, this); + public CryptoOperationFragment(Integer initialProgressMsg) { + mOperationHelper = new CryptoOperationHelper<>(1, this, this, initialProgressMsg); } - public void setProgressMessageResource(int id) { - mOperationHelper.setProgressMessageResource(id); + public CryptoOperationFragment() { + mOperationHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_processing); } - @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { mOperationHelper.handleActivityResult(requestCode, resultCode, data); } - @Override - public abstract T createOperationInput(); - + /** Starts execution of the cryptographic operation. + * + * During this process, the createOperationInput() method will be called, + * this input will be handed to KeychainService, where it is executed in + * the appropriate *Operation class. If the result is a PendingInputResult, + * it is handled accordingly. Otherwise, it is returned in one of the + * onCryptoOperation* callbacks. + */ protected void cryptoOperation() { - cryptoOperation(new CryptoInputParcel()); + mOperationHelper.cryptoOperation(); } protected void cryptoOperation(CryptoInputParcel cryptoInput) { - cryptoOperation(cryptoInput); + mOperationHelper.cryptoOperation(cryptoInput); } - protected void cryptoOperation(CryptoInputParcel cryptoInput, boolean showProgress) { - mOperationHelper.cryptoOperation(cryptoInput, showProgress); - } + @Override @Nullable + /** Creates input for the crypto operation. Called internally after the + * crypto operation is started by a call to cryptoOperation(). Silently + * cancels operation if this method returns null. */ + public abstract T createOperationInput(); + /** Returns false, indicating that we did not handle progress ourselves. */ public boolean onCryptoSetProgress(String msg, int progress, int max) { return false; } - @Override - public void onCryptoOperationError(S result) { - onCryptoOperationResult(result); - result.createNotify(getActivity()).show(); + public void setProgressMessageResource(int id) { + mOperationHelper.setProgressMessageResource(id); } @Override - public void onCryptoOperationCancelled() { - } + /** Called when the cryptoOperation() was successful. No default behavior + * here, this should always be implemented by a subclass! */ + abstract public void onCryptoOperationSuccess(S result); @Override - public void onCryptoOperationSuccess(S result) { - onCryptoOperationResult(result); + abstract public void onCryptoOperationError(S result); + + @Override + public void onCryptoOperationCancelled() { } - /** - * - * To be overriden by subclasses, if desired. Provides a way to access the method by the - * same name in CryptoOperationHelper, if super.onCryptoOperationSuccess and - * super.onCryptoOperationError are called at the start of the respective functions in the - * subclass overriding them - * @param result - */ - protected void onCryptoOperationResult(S result) { + public void hideKeyboard() { + if (getActivity() == null) { + return; + } + InputMethodManager inputManager = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + + // check if no view has focus + View v = getActivity().getCurrentFocus(); + if (v == null) + return; + + inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java index 240dd0972..b33128978 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java @@ -28,10 +28,9 @@ import android.os.Messenger; import android.os.Parcelable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; - import android.support.v4.app.FragmentManager; + import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.InputPendingResult; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.service.KeychainService; @@ -39,7 +38,9 @@ import org.sufficientlysecure.keychain.service.ServiceProgressHandler; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.NfcOperationActivity; +import org.sufficientlysecure.keychain.ui.OrbotRequiredDialogActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; +import org.sufficientlysecure.keychain.ui.RetryUploadDialogActivity; import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment; import org.sufficientlysecure.keychain.util.Log; @@ -52,23 +53,33 @@ import org.sufficientlysecure.keychain.util.Log; */ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResult> { - public interface Callback <T extends Parcelable, S extends OperationResult> { + public interface Callback<T extends Parcelable, S extends OperationResult> { T createOperationInput(); + void onCryptoOperationSuccess(S result); + void onCryptoOperationCancelled(); + void onCryptoOperationError(S result); + boolean onCryptoSetProgress(String msg, int progress, int max); } - public static final int REQUEST_CODE_PASSPHRASE = 0x00008001; - public static final int REQUEST_CODE_NFC = 0x00008002; + // request codes from CryptoOperationHelper are created essentially + // a static property, used to identify requestCodes meant for this + // particular helper. a request code looks as follows: + // (id << 9) + (1<<8) + REQUEST_CODE_X + // that is, starting from LSB, there are 8 bits request code, 1 + // fixed bit set, then 7 bit operator-id code. the first two + // summands are stored in the mId for easy operation. + private final int mId; - // keeps track of request code used to start an activity from this CryptoOperationHelper. - // this is necessary when multiple CryptoOperationHelpers are used in the same fragment/activity - // otherwise all CryptoOperationHandlers may respond to the same onActivityResult - private int mRequestedCode = -1; + public static final int REQUEST_CODE_PASSPHRASE = 1; + public static final int REQUEST_CODE_NFC = 2; + public static final int REQUEST_CODE_ENABLE_ORBOT = 3; + public static final int REQUEST_CODE_RETRY_UPLOAD = 4; - private int mProgressMessageResource; + private Integer mProgressMessageResource; private FragmentActivity mActivity; private Fragment mFragment; @@ -78,10 +89,10 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu /** * If OperationHelper is being integrated into an activity - * - * @param activity */ - public CryptoOperationHelper(FragmentActivity activity, Callback<T, S> callback, int progressMessageString) { + public CryptoOperationHelper(int id, FragmentActivity activity, Callback<T, S> callback, + Integer progressMessageString) { + mId = (id << 9) + (1<<8); mActivity = activity; mUseFragment = false; mCallback = callback; @@ -90,48 +101,33 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu /** * if OperationHelper is being integrated into a fragment - * - * @param fragment */ - public CryptoOperationHelper(Fragment fragment, Callback<T, S> callback, int progressMessageString) { + public CryptoOperationHelper(int id, Fragment fragment, Callback<T, S> callback, Integer progressMessageString) { + mId = (id << 9) + (1<<8); mFragment = fragment; mUseFragment = true; mProgressMessageResource = progressMessageString; mCallback = callback; } - /** - * if OperationHelper is being integrated into a fragment with default message for the progress dialog - * - * @param fragment - */ - public CryptoOperationHelper(Fragment fragment, Callback<T, S> callback) { - mFragment = fragment; - mUseFragment = true; - mProgressMessageResource = R.string.progress_building_key; - mCallback = callback; - } - public void setProgressMessageResource(int id) { mProgressMessageResource = id; } - private void initiateInputActivity(RequiredInputParcel requiredInput) { + private void initiateInputActivity(RequiredInputParcel requiredInput, + CryptoInputParcel cryptoInputParcel) { Activity activity = mUseFragment ? mFragment.getActivity() : mActivity; switch (requiredInput.mType) { + // always use CryptoOperationHelper.startActivityForResult! case NFC_MOVE_KEY_TO_CARD: case NFC_DECRYPT: case NFC_SIGN: { Intent intent = new Intent(activity, NfcOperationActivity.class); intent.putExtra(NfcOperationActivity.EXTRA_REQUIRED_INPUT, requiredInput); - mRequestedCode = REQUEST_CODE_NFC; - if (mUseFragment) { - mFragment.startActivityForResult(intent, mRequestedCode); - } else { - mActivity.startActivityForResult(intent, mRequestedCode); - } + intent.putExtra(NfcOperationActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel); + startActivityForResult(intent, REQUEST_CODE_NFC); return; } @@ -139,37 +135,55 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu case PASSPHRASE_SYMMETRIC: { Intent intent = new Intent(activity, PassphraseDialogActivity.class); intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, requiredInput); - mRequestedCode = REQUEST_CODE_PASSPHRASE; - if (mUseFragment) { - mFragment.startActivityForResult(intent, mRequestedCode); - } else { - mActivity.startActivityForResult(intent, mRequestedCode); - } + intent.putExtra(PassphraseDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel); + startActivityForResult(intent, REQUEST_CODE_PASSPHRASE); return; } + + case ENABLE_ORBOT: { + Intent intent = new Intent(activity, OrbotRequiredDialogActivity.class); + intent.putExtra(OrbotRequiredDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel); + startActivityForResult(intent, REQUEST_CODE_ENABLE_ORBOT); + return; + } + + case UPLOAD_FAIL_RETRY: { + Intent intent = new Intent(activity, RetryUploadDialogActivity.class); + intent.putExtra(RetryUploadDialogActivity.EXTRA_CRYPTO_INPUT, cryptoInputParcel); + startActivityForResult(intent, REQUEST_CODE_RETRY_UPLOAD); + return; + } + + default: { + throw new RuntimeException("Unhandled pending result!"); + } } + } - throw new RuntimeException("Unhandled pending result!"); + protected void startActivityForResult(Intent intent, int requestCode) { + if (mUseFragment) { + mFragment.startActivityForResult(intent, mId + requestCode); + } else { + mActivity.startActivityForResult(intent, mId + requestCode); + } } /** - * Attempts the result of an activity started by this helper. Returns true if requestCode is recognized, - * false otherwise. - * - * @param requestCode - * @param resultCode - * @param data + * Attempts the result of an activity started by this helper. Returns true if requestCode is + * recognized, false otherwise. * @return true if requestCode was recognized, false otherwise */ public boolean handleActivityResult(int requestCode, int resultCode, Intent data) { Log.d(Constants.TAG, "received activity result in OperationHelper"); - if (mRequestedCode != requestCode) { + if ((requestCode & mId) != mId) { // this wasn't meant for us to handle return false; - } else { - mRequestedCode = -1; } + Log.d(Constants.TAG, "handling activity result in OperationHelper"); + // filter out mId from requestCode + requestCode ^= mId; + if (resultCode == Activity.RESULT_CANCELED) { mCallback.onCryptoOperationCancelled(); return true; @@ -181,7 +195,6 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu CryptoInputParcel cryptoInput = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT); cryptoOperation(cryptoInput); - return true; } break; } @@ -189,17 +202,33 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu case REQUEST_CODE_NFC: { if (resultCode == Activity.RESULT_OK && data != null) { CryptoInputParcel cryptoInput = - data.getParcelableExtra(NfcOperationActivity.RESULT_DATA); + data.getParcelableExtra(NfcOperationActivity.RESULT_CRYPTO_INPUT); cryptoOperation(cryptoInput); - return true; } break; } - default: { - return false; + case REQUEST_CODE_ENABLE_ORBOT: { + if (resultCode == Activity.RESULT_OK && data != null) { + CryptoInputParcel cryptoInput = + data.getParcelableExtra( + OrbotRequiredDialogActivity.RESULT_CRYPTO_INPUT); + cryptoOperation(cryptoInput); + } + break; + } + + case REQUEST_CODE_RETRY_UPLOAD: { + if (resultCode == Activity.RESULT_OK) { + CryptoInputParcel cryptoInput = + data.getParcelableExtra( + RetryUploadDialogActivity.RESULT_CRYPTO_INPUT); + cryptoOperation(cryptoInput); + } + break; } } + return true; } @@ -225,7 +254,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu } - public void cryptoOperation(CryptoInputParcel cryptoInput, boolean showProgress) { + public void cryptoOperation(final CryptoInputParcel cryptoInput) { FragmentActivity activity = mUseFragment ? mFragment.getActivity() : mActivity; @@ -264,7 +293,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu @Override protected void onSetProgress(String msg, int progress, int max) { // allow handling of progress in fragment, or delegate upwards - if ( ! mCallback.onCryptoSetProgress(msg, progress, max)) { + if (!mCallback.onCryptoSetProgress(msg, progress, max)) { super.onSetProgress(msg, progress, max); } } @@ -274,7 +303,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu Messenger messenger = new Messenger(saveHandler); intent.putExtra(KeychainService.EXTRA_MESSENGER, messenger); - if (showProgress) { + if (mProgressMessageResource != null) { saveHandler.showProgressDialog( activity.getString(mProgressMessageResource), ProgressDialog.STYLE_HORIZONTAL, false); @@ -283,31 +312,18 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu activity.startService(intent); } - public void cryptoOperation(CryptoInputParcel cryptoInputParcel) { - cryptoOperation(cryptoInputParcel, true); - } - public void cryptoOperation() { cryptoOperation(new CryptoInputParcel()); } - protected void onCryptoOperationResult(S result) { - if (result.success()) { - mCallback.onCryptoOperationSuccess(result); - } else { - mCallback.onCryptoOperationError(result); - } - } - public void onHandleResult(OperationResult result) { Log.d(Constants.TAG, "Handling result in OperationHelper success: " + result.success()); if (result instanceof InputPendingResult) { InputPendingResult pendingResult = (InputPendingResult) result; if (pendingResult.isPending()) { - RequiredInputParcel requiredInput = pendingResult.getRequiredInputParcel(); - initiateInputActivity(requiredInput); + initiateInputActivity(requiredInput, pendingResult.mCryptoInputParcel); return; } } @@ -315,12 +331,16 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu dismissProgress(); try { - // noinspection unchecked, because type erasure :( - onCryptoOperationResult((S) result); + if (result.success()) { + // noinspection unchecked, because type erasure :( + mCallback.onCryptoOperationSuccess((S) result); + } else { + // noinspection unchecked, because type erasure :( + mCallback.onCryptoOperationError((S) result); + } } catch (ClassCastException e) { throw new AssertionError("bad return class (" + result.getClass().getSimpleName() + "), this is a programming error!"); } - } }
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/QueueingCryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/QueueingCryptoOperationFragment.java new file mode 100644 index 000000000..65e0ce941 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/QueueingCryptoOperationFragment.java @@ -0,0 +1,93 @@ +package org.sufficientlysecure.keychain.ui.base; + + +import android.os.Bundle; +import android.os.Parcelable; + +import org.sufficientlysecure.keychain.operations.results.OperationResult; + + +/** CryptoOperationFragment which calls crypto operation results only while + * attached to Activity. + * + * This subclass of CryptoOperationFragment substitutes the onCryptoOperation* + * methods for onQueuedOperation* ones, which are ensured to be called while + * the fragment is attached to an Activity, possibly delaying the call until + * the Fragment is re-attached. + * + * TODO merge this functionality into CryptoOperationFragment? + * + * @see CryptoOperationFragment + */ +public abstract class QueueingCryptoOperationFragment<T extends Parcelable, S extends OperationResult> + extends CryptoOperationFragment<T,S> { + + public static final String ARG_QUEUED_RESULT = "queued_result"; + private S mQueuedResult; + + public QueueingCryptoOperationFragment() { + super(); + } + + public QueueingCryptoOperationFragment(Integer initialProgressMsg) { + super(initialProgressMsg); + } + + @Override + public void onViewStateRestored(Bundle savedInstanceState) { + super.onViewStateRestored(savedInstanceState); + + if (mQueuedResult != null) { + try { + if (mQueuedResult.success()) { + onQueuedOperationSuccess(mQueuedResult); + } else { + onQueuedOperationError(mQueuedResult); + } + } finally { + mQueuedResult = null; + } + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + outState.putParcelable(ARG_QUEUED_RESULT, mQueuedResult); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState != null) { + mQueuedResult = savedInstanceState.getParcelable(ARG_QUEUED_RESULT); + } + } + + public abstract void onQueuedOperationSuccess(S result); + + public void onQueuedOperationError(S result) { + hideKeyboard(); + result.createNotify(getActivity()).show(); + } + + @Override + final public void onCryptoOperationSuccess(S result) { + if (getActivity() == null) { + mQueuedResult = result; + return; + } + onQueuedOperationSuccess(result); + } + + @Override + final public void onCryptoOperationError(S result) { + if (getActivity() == null) { + mQueuedResult = result; + return; + } + onQueuedOperationError(result); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddKeyserverDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java index 2c1714b67..691cc009d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddKeyserverDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java @@ -24,7 +24,7 @@ import java.net.URI; import java.net.URISyntaxException; import android.app.Activity; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; import android.content.Context; @@ -48,13 +48,23 @@ import android.widget.EditText; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.TlsHelper; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; -public class AddKeyserverDialogFragment extends DialogFragment implements OnEditorActionListener { - private static final String ARG_MESSENGER = "messenger"; +import java.net.Proxy; + +public class AddEditKeyserverDialogFragment extends DialogFragment implements OnEditorActionListener { + private static final String ARG_MESSENGER = "arg_messenger"; + private static final String ARG_ACTION = "arg_dialog_action"; + private static final String ARG_POSITION = "arg_position"; + private static final String ARG_KEYSERVER = "arg_keyserver"; public static final int MESSAGE_OKAY = 1; public static final int MESSAGE_VERIFICATION_FAILED = 2; @@ -62,20 +72,37 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit public static final String MESSAGE_KEYSERVER = "new_keyserver"; public static final String MESSAGE_VERIFIED = "verified"; public static final String MESSAGE_FAILURE_REASON = "failure_reason"; + public static final String MESSAGE_KEYSERVER_DELETED = "keyserver_deleted"; + public static final String MESSAGE_DIALOG_ACTION = "message_dialog_action"; + public static final String MESSAGE_EDIT_POSITION = "keyserver_edited_position"; private Messenger mMessenger; + private DialogAction mDialogAction; + private int mPosition; + private EditText mKeyserverEditText; private CheckBox mVerifyKeyserverCheckBox; + public enum DialogAction { + ADD, + EDIT + } + public enum FailureReason { INVALID_URL, CONNECTION_FAILED } - public static AddKeyserverDialogFragment newInstance(Messenger messenger) { - AddKeyserverDialogFragment frag = new AddKeyserverDialogFragment(); + public static AddEditKeyserverDialogFragment newInstance(Messenger messenger, + DialogAction action, + String keyserver, + int position) { + AddEditKeyserverDialogFragment frag = new AddEditKeyserverDialogFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_MESSENGER, messenger); + args.putSerializable(ARG_ACTION, action); + args.putString(ARG_KEYSERVER, keyserver); + args.putInt(ARG_POSITION, position); frag.setArguments(args); @@ -88,11 +115,11 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit final Activity activity = getActivity(); mMessenger = getArguments().getParcelable(ARG_MESSENGER); + mDialogAction = (DialogAction) getArguments().getSerializable(ARG_ACTION); + mPosition = getArguments().getInt(ARG_POSITION); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); - alert.setTitle(R.string.add_keyserver_dialog_title); - LayoutInflater inflater = activity.getLayoutInflater(); View view = inflater.inflate(R.layout.add_keyserver_dialog, null); alert.setView(view); @@ -100,14 +127,26 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit mKeyserverEditText = (EditText) view.findViewById(R.id.keyserver_url_edit_text); mVerifyKeyserverCheckBox = (CheckBox) view.findViewById(R.id.verify_keyserver_checkbox); - // we don't want dialog to be dismissed on click, thereby requiring the hack seen below - // and in onStart + switch (mDialogAction) { + case ADD: { + alert.setTitle(R.string.add_keyserver_dialog_title); + break; + } + case EDIT: { + alert.setTitle(R.string.edit_keyserver_dialog_title); + mKeyserverEditText.setText(getArguments().getString(ARG_KEYSERVER)); + break; + } + } + + // we don't want dialog to be dismissed on click for keyserver addition or edit, + // thereby requiring the hack seen below and in onStart alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { // we need to have an empty listener to prevent errors on some devices as mentioned // at http://stackoverflow.com/q/13746412/3000919 - // actual listener set in onStart + // actual listener set in onStart for adding keyservers or editing them } }); @@ -119,6 +158,23 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit } }); + switch (mDialogAction) { + case EDIT: { + alert.setNeutralButton(R.string.label_keyserver_dialog_delete, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + deleteKeyserver(mPosition); + } + }); + break; + } + case ADD: { + // do nothing + break; + } + } + // Hack to open keyboard. // This is the only method that I found to work across all Android versions // http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/ @@ -155,25 +211,53 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit positiveButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - String keyserverUrl = mKeyserverEditText.getText().toString(); + // behaviour same for edit and add + final String keyserverUrl = mKeyserverEditText.getText().toString(); if (mVerifyKeyserverCheckBox.isChecked()) { - verifyConnection(keyserverUrl); + final Preferences.ProxyPrefs proxyPrefs = Preferences.getPreferences(getActivity()) + .getProxyPrefs(); + Runnable ignoreTor = new Runnable() { + @Override + public void run() { + verifyConnection(keyserverUrl, null); + } + }; + + if (OrbotHelper.putOrbotInRequiredState(R.string.orbot_ignore_tor, ignoreTor, proxyPrefs, + getActivity())) { + verifyConnection(keyserverUrl, proxyPrefs.parcelableProxy.getProxy()); + } } else { dismiss(); // return unverified keyserver back to activity - addKeyserver(keyserverUrl, false); + keyserverEdited(keyserverUrl, false); } } }); } } - public void addKeyserver(String keyserver, boolean verified) { + public void keyserverEdited(String keyserver, boolean verified) { dismiss(); Bundle data = new Bundle(); + data.putSerializable(MESSAGE_DIALOG_ACTION, mDialogAction); data.putString(MESSAGE_KEYSERVER, keyserver); data.putBoolean(MESSAGE_VERIFIED, verified); + if (mDialogAction == DialogAction.EDIT) { + data.putInt(MESSAGE_EDIT_POSITION, mPosition); + } + + sendMessageToHandler(MESSAGE_OKAY, data); + } + + public void deleteKeyserver(int position) { + dismiss(); + Bundle data = new Bundle(); + data.putSerializable(MESSAGE_DIALOG_ACTION, DialogAction.EDIT); + data.putInt(MESSAGE_EDIT_POSITION, position); + data.putBoolean(MESSAGE_KEYSERVER_DELETED, true); + sendMessageToHandler(MESSAGE_OKAY, data); } @@ -184,7 +268,7 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit sendMessageToHandler(MESSAGE_VERIFICATION_FAILED, data); } - public void verifyConnection(String keyserver) { + public void verifyConnection(String keyserver, final Proxy proxy) { new AsyncTask<String, Void, FailureReason>() { ProgressDialog mProgressDialog; @@ -218,10 +302,11 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit } URI newKeyserver = new URI(scheme, schemeSpecificPart, fragment); - Log.d(Constants.TAG, "Converted URL" + newKeyserver); + Log.d("Converted URL", newKeyserver.toString()); - // just see if we can get a connection, then immediately close - TlsHelper.openConnection(newKeyserver.toURL()).getInputStream().close(); + OkHttpClient client = HkpKeyserver.getClient(newKeyserver.toURL(), proxy); + TlsHelper.pinCertificateIfNecessary(client, newKeyserver.toURL()); + client.newCall(new Request.Builder().url(newKeyserver.toURL()).build()).execute(); } catch (TlsHelper.TlsHelperException e) { reason = FailureReason.CONNECTION_FAILED; } catch (MalformedURLException | URISyntaxException e) { @@ -238,7 +323,7 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit protected void onPostExecute(FailureReason failureReason) { mProgressDialog.dismiss(); if (failureReason == null) { - addKeyserver(mKeyserver, true); + keyserverEdited(mKeyserver, true); } else { verificationFailed(failureReason); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java index 5b91b9d37..c55c75b55 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEmailDialogFragment.java @@ -18,7 +18,7 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.app.Activity; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java index 0b1d39fc1..b51d081e1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java @@ -18,12 +18,12 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.annotation.TargetApi; -import android.app.AlertDialog; import android.app.Dialog; import android.os.Build; import android.os.Bundle; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; +import android.support.v7.app.AlertDialog; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java index fe4ba0262..bc82feb70 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java @@ -18,7 +18,7 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.app.Activity; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AdvancedAppSettingsDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AdvancedAppSettingsDialogFragment.java index d2fa37cf7..d96879119 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AdvancedAppSettingsDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AdvancedAppSettingsDialogFragment.java @@ -27,7 +27,7 @@ import org.sufficientlysecure.keychain.R; public class AdvancedAppSettingsDialogFragment extends DialogFragment { private static final String ARG_PACKAGE_NAME = "package_name"; - private static final String ARG_SIGNATURE = "signature"; + private static final String ARG_CERTIFICATE = "certificate"; /** * Creates new instance of this fragment @@ -36,7 +36,7 @@ public class AdvancedAppSettingsDialogFragment extends DialogFragment { AdvancedAppSettingsDialogFragment frag = new AdvancedAppSettingsDialogFragment(); Bundle args = new Bundle(); args.putString(ARG_PACKAGE_NAME, packageName); - args.putString(ARG_SIGNATURE, digest); + args.putString(ARG_CERTIFICATE, digest); frag.setArguments(args); return frag; @@ -62,10 +62,10 @@ public class AdvancedAppSettingsDialogFragment extends DialogFragment { }); String packageName = getArguments().getString(ARG_PACKAGE_NAME); - String signature = getArguments().getString(ARG_SIGNATURE); + String certificate = getArguments().getString(ARG_CERTIFICATE); alert.setMessage(getString(R.string.api_settings_package_name) + ": " + packageName + "\n\n" - + getString(R.string.api_settings_package_signature) + ": " + signature); + + getString(R.string.api_settings_package_certificate) + ": " + certificate); return alert.show(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java index 794af5b15..840b95a3c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/CustomAlertDialogBuilder.java @@ -17,42 +17,15 @@ package org.sufficientlysecure.keychain.ui.dialog; -import android.app.AlertDialog; import android.content.Context; -import android.view.View; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.R; +import android.support.v7.app.AlertDialog; /** - * This class extends AlertDiaog.Builder, styling the header using emphasis color. - * Note that this class is a huge hack, because dialog boxes aren't easily stylable. - * Also, the dialog NEEDS to be called with show() directly, not create(), otherwise - * the order of internal operations will lead to a crash! + * Uses support lib's dialog builder. We can apply a theme here later! */ public class CustomAlertDialogBuilder extends AlertDialog.Builder { public CustomAlertDialogBuilder(Context context) { super(context); } - - @Override - public AlertDialog show() { - AlertDialog dialog = super.show(); - - int dividerId = dialog.getContext().getResources().getIdentifier("android:id/titleDivider", null, null); - View divider = dialog.findViewById(dividerId); - if (divider != null) { - divider.setBackgroundColor(dialog.getContext().getResources().getColor(R.color.header_text)); - } - - int textViewId = dialog.getContext().getResources().getIdentifier("android:id/alertTitle", null, null); - TextView tv = (TextView) dialog.findViewById(textViewId); - if (tv != null) { - tv.setTextColor(dialog.getContext().getResources().getColor(R.color.header_text)); - } - - return dialog; - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java deleted file mode 100644 index 7fc9da9ec..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteKeyDialogFragment.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2013-2014 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.dialog; - -import android.app.Dialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; -import android.support.v4.app.DialogFragment; -import android.support.v4.app.FragmentActivity; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.operations.results.DeleteResult; -import org.sufficientlysecure.keychain.operations.results.OperationResult; -import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.ProviderHelper; -import org.sufficientlysecure.keychain.service.DeleteKeyringParcel; -import org.sufficientlysecure.keychain.service.ServiceProgressHandler; -import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; -import org.sufficientlysecure.keychain.util.Log; - -import java.util.HashMap; - -public class DeleteKeyDialogFragment extends DialogFragment - implements CryptoOperationHelper.Callback<DeleteKeyringParcel, DeleteResult> { - private static final String ARG_MESSENGER = "messenger"; - private static final String ARG_DELETE_MASTER_KEY_IDS = "delete_master_key_ids"; - - public static final int MESSAGE_OKAY = 1; - public static final int MESSAGE_ERROR = 0; - - private TextView mMainMessage; - private View mInflateView; - - private Messenger mMessenger; - - // for CryptoOperationHelper.Callback - private long[] mMasterKeyIds; - private boolean mHasSecret; - private CryptoOperationHelper<DeleteKeyringParcel, DeleteResult> mDeleteOpHelper; - - /** - * Creates new instance of this delete file dialog fragment - */ - public static DeleteKeyDialogFragment newInstance(Messenger messenger, long[] masterKeyIds) { - DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment(); - Bundle args = new Bundle(); - - args.putParcelable(ARG_MESSENGER, messenger); - args.putLongArray(ARG_DELETE_MASTER_KEY_IDS, masterKeyIds); - - frag.setArguments(args); - - return frag; - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (mDeleteOpHelper != null) { - mDeleteOpHelper.handleActivityResult(requestCode, resultCode, data); - } - super.onActivityResult(requestCode, resultCode, data); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final FragmentActivity activity = getActivity(); - mMessenger = getArguments().getParcelable(ARG_MESSENGER); - - final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS); - - CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(activity); - - // Setup custom View to display in AlertDialog - LayoutInflater inflater = activity.getLayoutInflater(); - mInflateView = inflater.inflate(R.layout.view_key_delete_fragment, null); - builder.setView(mInflateView); - - mMainMessage = (TextView) mInflateView.findViewById(R.id.mainMessage); - - final boolean hasSecret; - - // If only a single key has been selected - if (masterKeyIds.length == 1) { - long masterKeyId = masterKeyIds[0]; - - try { - HashMap<String, Object> data = new ProviderHelper(activity).getUnifiedData( - masterKeyId, new String[]{ - KeyRings.USER_ID, - KeyRings.HAS_ANY_SECRET - }, new int[]{ - ProviderHelper.FIELD_TYPE_STRING, - ProviderHelper.FIELD_TYPE_INTEGER - } - ); - String name; - KeyRing.UserId mainUserId = KeyRing.splitUserId((String) data.get(KeyRings.USER_ID)); - if (mainUserId.name != null) { - name = mainUserId.name; - } else { - name = getString(R.string.user_id_no_name); - } - hasSecret = ((Long) data.get(KeyRings.HAS_ANY_SECRET)) == 1; - - if (hasSecret) { - // show title only for secret key deletions, - // see http://www.google.com/design/spec/components/dialogs.html#dialogs-behavior - builder.setTitle(getString(R.string.title_delete_secret_key, name)); - mMainMessage.setText(getString(R.string.secret_key_deletion_confirmation, name)); - } else { - mMainMessage.setText(getString(R.string.public_key_deletetion_confirmation, name)); - } - } catch (ProviderHelper.NotFoundException e) { - dismiss(); - return null; - } - } else { - mMainMessage.setText(R.string.key_deletion_confirmation_multi); - hasSecret = false; - } - - builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - - mMasterKeyIds = masterKeyIds; - mHasSecret = hasSecret; - - mDeleteOpHelper = new CryptoOperationHelper<> - (DeleteKeyDialogFragment.this, DeleteKeyDialogFragment.this, - R.string.progress_deleting); - mDeleteOpHelper.cryptoOperation(); - // do NOT dismiss here, it'll give - // OperationHelper a null fragmentManager - // dismiss(); - } - }); - builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int id) { - dismiss(); - } - }); - - return builder.show(); - } - - @Override - public DeleteKeyringParcel createOperationInput() { - return new DeleteKeyringParcel(mMasterKeyIds, mHasSecret); - } - - @Override - public void onCryptoOperationSuccess(DeleteResult result) { - handleResult(result); - } - - @Override - public void onCryptoOperationCancelled() { - - } - - @Override - public void onCryptoOperationError(DeleteResult result) { - handleResult(result); - } - - @Override - public boolean onCryptoSetProgress(String msg, int progress, int max) { - return false; - } - - public void handleResult(DeleteResult result) { - try { - Bundle data = new Bundle(); - data.putParcelable(OperationResult.EXTRA_RESULT, result); - Message msg = Message.obtain(); - msg.arg1 = ServiceProgressHandler.MessageStatus.OKAY.ordinal(); - msg.setData(data); - mMessenger.send(msg); - } catch (RemoteException e) { - Log.e(Constants.TAG, "messenger error", e); - } - dismiss(); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java index eafa129f0..b51648740 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyDialogFragment.java @@ -35,7 +35,7 @@ public class EditSubkeyDialogFragment extends DialogFragment { public static final int MESSAGE_CHANGE_EXPIRY = 1; public static final int MESSAGE_REVOKE = 2; public static final int MESSAGE_STRIP = 3; - public static final int MESSAGE_KEYTOCARD = 4; + public static final int MESSAGE_MOVE_KEY_TO_CARD = 4; private Messenger mMessenger; @@ -78,7 +78,7 @@ public class EditSubkeyDialogFragment extends DialogFragment { sendMessageToHandler(MESSAGE_STRIP, null); break; case 3: - sendMessageToHandler(MESSAGE_KEYTOCARD, null); + sendMessageToHandler(MESSAGE_MOVE_KEY_TO_CARD, null); break; default: break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java index 37e05a61d..c9fb990a2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditSubkeyExpiryDialogFragment.java @@ -24,6 +24,7 @@ import android.os.Bundle; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.text.format.DateFormat; import android.view.LayoutInflater; @@ -36,6 +37,8 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.Notify; +import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.Log; import java.util.Calendar; @@ -72,17 +75,19 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment { /** * Creates dialog */ + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - final Activity activity = getActivity(); + Activity activity = getActivity(); + mMessenger = getArguments().getParcelable(ARG_MESSENGER); long creation = getArguments().getLong(ARG_CREATION); long expiry = getArguments().getLong(ARG_EXPIRY); - Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - creationCal.setTime(new Date(creation * 1000)); - final Calendar expiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - expiryCal.setTime(new Date(expiry * 1000)); + final Calendar creationCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + creationCal.setTimeInMillis(creation * 1000); + Calendar expiryCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + expiryCal.setTimeInMillis(expiry * 1000); // date picker works with default time zone, we need to convert from UTC to default timezone creationCal.setTimeZone(TimeZone.getDefault()); @@ -123,11 +128,8 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment { noExpiry.setChecked(false); expiryLayout.setVisibility(View.VISIBLE); - // convert from UTC to time zone of device - Calendar expiryCalTimeZone = (Calendar) expiryCal.clone(); - expiryCalTimeZone.setTimeZone(TimeZone.getDefault()); currentExpiry.setText(DateFormat.getDateFormat( - getActivity()).format(expiryCalTimeZone.getTime())); + getActivity()).format(expiryCal.getTime())); } // date picker works based on default time zone @@ -175,10 +177,13 @@ public class EditSubkeyExpiryDialogFragment extends DialogFragment { selectedCal.setTimeZone(TimeZone.getTimeZone("UTC")); long numDays = (selectedCal.getTimeInMillis() / 86400000) - - (expiryCal.getTimeInMillis() / 86400000); + - (creationCal.getTimeInMillis() / 86400000); if (numDays <= 0) { - Log.e(Constants.TAG, "Should not happen! Expiry num of days <= 0!"); - throw new RuntimeException(); + Activity activity = getActivity(); + if (activity != null) { + Notify.create(activity, R.string.error_expiry_past, Style.ERROR).show(); + } + return; } expiry = selectedCal.getTime().getTime() / 1000; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java index 63b6d26ac..5ef8618ce 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java @@ -136,6 +136,7 @@ public class FileDialogFragment extends DialogFragment { mCheckBox.setEnabled(true); mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setText(checkboxText); + mCheckBox.setChecked(true); } alert.setView(view); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java new file mode 100644 index 000000000..d1d22b6d7 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/OrbotStartDialogFragment.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2012-2014 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.dialog; + +import android.app.Activity; +import android.support.v7.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.support.v4.app.DialogFragment; +import android.view.ContextThemeWrapper; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.orbot.OrbotHelper; + +/** + * displays a dialog asking the user to enable Tor + */ +public class OrbotStartDialogFragment extends DialogFragment { + private static final String ARG_MESSENGER = "messenger"; + private static final String ARG_TITLE = "title"; + private static final String ARG_MESSAGE = "message"; + private static final String ARG_MIDDLE_BUTTON = "middleButton"; + + public static final int MESSAGE_MIDDLE_BUTTON = 1; + public static final int MESSAGE_DIALOG_DISMISSED = 2; // for either cancel or enable pressed + + public static OrbotStartDialogFragment newInstance(Messenger messenger, int title, int message, int middleButton) { + Bundle args = new Bundle(); + args.putParcelable(ARG_MESSENGER, messenger); + args.putInt(ARG_TITLE, title); + args.putInt(ARG_MESSAGE, message); + args.putInt(ARG_MIDDLE_BUTTON, middleButton); + + OrbotStartDialogFragment fragment = new OrbotStartDialogFragment(); + fragment.setArguments(args); + + return fragment; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + final Messenger messenger = getArguments().getParcelable(ARG_MESSENGER); + int title = getArguments().getInt(ARG_TITLE); + final int message = getArguments().getInt(ARG_MESSAGE); + int middleButton = getArguments().getInt(ARG_MIDDLE_BUTTON); + final Activity activity = getActivity(); + + ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); + + CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme); + builder.setTitle(title).setMessage(message); + + builder.setNegativeButton(R.string.orbot_start_dialog_cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Message msg = Message.obtain(); + msg.what = MESSAGE_DIALOG_DISMISSED; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + + } + }); + + builder.setPositiveButton(R.string.orbot_start_dialog_start, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + getActivity().startActivityForResult(OrbotHelper.getOrbotStartIntent(), 1); + + Message msg = Message.obtain(); + msg.what = MESSAGE_DIALOG_DISMISSED; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + }); + + builder.setNeutralButton(middleButton, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Message msg = new Message(); + msg.what = MESSAGE_MIDDLE_BUTTON; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + }); + + return builder.show(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PreferenceInstallDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PreferenceInstallDialogFragment.java new file mode 100644 index 000000000..3f8bce28b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PreferenceInstallDialogFragment.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012-2014 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.dialog; + +import android.app.Dialog; +import android.os.Bundle; +import android.os.Messenger; +import android.app.DialogFragment; + +import org.sufficientlysecure.keychain.ui.util.InstallDialogFragmentHelper; + +public class PreferenceInstallDialogFragment extends DialogFragment { + + public static final int MESSAGE_MIDDLE_CLICKED = 1; + public static final int MESSAGE_DIALOG_DISMISSED = 2; + + /** + * Creates a dialog which prompts the user to install an application. Consists of two default buttons ("Install" + * and "Cancel") and an optional third button. Callbacks are provided only for the middle button, if set. + * + * @param messenger required only for callback from middle button if it has been set + * @param title + * @param message content of dialog + * @param packageToInstall package name of application to install + * @param middleButton if not null, adds a third button to the app with a call back + * @return The dialog to display + */ + public static PreferenceInstallDialogFragment newInstance(Messenger messenger, int title, int message, + String packageToInstall, int middleButton, boolean + useMiddleButton) { + PreferenceInstallDialogFragment frag = new PreferenceInstallDialogFragment(); + Bundle args = new Bundle(); + + InstallDialogFragmentHelper.wrapIntoArgs(messenger, title, message, packageToInstall, middleButton, + useMiddleButton, args); + + frag.setArguments(args); + + return frag; + } + + /** + * To create a DialogFragment with only two buttons + * + * @param title + * @param message + * @param packageToInstall + * @return + */ + public static PreferenceInstallDialogFragment newInstance(int title, int message, + String packageToInstall) { + return newInstance(null, title, message, packageToInstall, -1, false); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return InstallDialogFragmentHelper.getInstallDialogFromArgs(getArguments(), getActivity(), + MESSAGE_MIDDLE_CLICKED, MESSAGE_DIALOG_DISMISSED); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java index 52a90b323..764291dd0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/ProgressDialogFragment.java @@ -35,6 +35,7 @@ import android.widget.Button; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.service.KeychainService; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; /** * meant to be used @@ -98,10 +99,7 @@ public class ProgressDialogFragment extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { final Activity activity = getActivity(); - // if the progress dialog is displayed from the application class, design is missing - // hack to get holo design (which is not automatically applied due to activity's Theme.NoDisplay - ContextThemeWrapper context = new ContextThemeWrapper(activity, - R.style.Theme_AppCompat_Light); + ContextThemeWrapper context = ThemeChanger.getDialogThemeWrapper(activity); ProgressDialog dialog = new ProgressDialog(context); dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java index 4eb253825..a990682f6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SetPassphraseDialogFragment.java @@ -18,7 +18,7 @@ package org.sufficientlysecure.keychain.ui.dialog; import android.app.Activity; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java new file mode 100644 index 000000000..b2b71b364 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/SupportInstallDialogFragment.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2012-2014 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.dialog; + +import android.app.Dialog; +import android.os.Bundle; +import android.os.Messenger; +import android.support.v4.app.DialogFragment; + +import org.sufficientlysecure.keychain.ui.util.InstallDialogFragmentHelper; + +public class SupportInstallDialogFragment extends DialogFragment { + + public static final int MESSAGE_MIDDLE_CLICKED = 1; + public static final int MESSAGE_DIALOG_DISMISSED = 2; + + /** + * Creates a dialog which prompts the user to install an application. Consists of two default buttons ("Install" + * and "Cancel") and an optional third button. Callbacks are provided only for the middle button, if set. + * + * @param messenger required only for callback from middle button if it has been set + * @param title + * @param message content of dialog + * @param packageToInstall package name of application to install + * @param middleButton if not null, adds a third button to the app with a call back + * @return The dialog to display + */ + public static SupportInstallDialogFragment newInstance(Messenger messenger, int title, int message, + String packageToInstall, int middleButton, boolean + useMiddleButton) { + SupportInstallDialogFragment frag = new SupportInstallDialogFragment(); + Bundle args = new Bundle(); + + InstallDialogFragmentHelper.wrapIntoArgs(messenger, title, message, packageToInstall, middleButton, + useMiddleButton, args); + + frag.setArguments(args); + + return frag; + } + + /** + * To create a DialogFragment with only two buttons + * + * @param title + * @param message + * @param packageToInstall + * @return + */ + public static SupportInstallDialogFragment newInstance(int title, int message, + String packageToInstall) { + return newInstance(null, title, message, packageToInstall, -1, false); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + return InstallDialogFragmentHelper.getInstallDialogFromArgs(getArguments(), getActivity(), + MESSAGE_MIDDLE_CLICKED, MESSAGE_DIALOG_DISMISSED); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java index eb5c3df45..902a7ec56 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/FormattingUtils.java @@ -18,9 +18,11 @@ package org.sufficientlysecure.keychain.ui.util; import android.content.Context; +import android.content.res.Resources.Theme; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.StrikethroughSpan; +import android.util.TypedValue; public class FormattingUtils { @@ -32,4 +34,10 @@ public class FormattingUtils { return (int) ((px / context.getResources().getDisplayMetrics().density) + 0.5f); } + public static int getColorFromAttr(Context context, int attr) { + TypedValue typedValue = new TypedValue(); + Theme theme = context.getTheme(); + theme.resolveAttribute(attr, typedValue, true); + return typedValue.data; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java index 69338aa3e..ac34d5526 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Highlighter.java @@ -22,6 +22,7 @@ import android.text.Spannable; import android.text.style.ForegroundColorSpan; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -44,9 +45,12 @@ public class Highlighter { Pattern pattern = Pattern.compile("(?i)(" + mQuery.trim().replaceAll("\\s+", "|") + ")"); Matcher matcher = pattern.matcher(text); + + int colorEmphasis = FormattingUtils.getColorFromAttr(mContext, R.attr.colorEmphasis); + while (matcher.find()) { highlight.setSpan( - new ForegroundColorSpan(mContext.getResources().getColor(R.color.emphasis)), + new ForegroundColorSpan(colorEmphasis), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java new file mode 100644 index 000000000..b2213ed10 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/InstallDialogFragmentHelper.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2012-2014 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.util; + +import android.app.Activity; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.support.v7.app.AlertDialog; +import android.view.ContextThemeWrapper; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder; +import org.sufficientlysecure.keychain.ui.util.ThemeChanger; +import org.sufficientlysecure.keychain.util.Log; + +public class InstallDialogFragmentHelper { + private static final String ARG_MESSENGER = "messenger"; + private static final String ARG_TITLE = "title"; + private static final String ARG_MESSAGE = "message"; + private static final String ARG_MIDDLE_BUTTON = "middleButton"; + private static final String ARG_INSTALL_PATH = "installPath"; + private static final String ARG_USE_MIDDLE_BUTTON = "useMiddleButton"; + + private static final String PLAY_STORE_PATH = "market://search?q=pname:"; + + public static void wrapIntoArgs(Messenger messenger, int title, int message, String packageToInstall, + int middleButton, boolean useMiddleButton, Bundle args) { + args.putParcelable(ARG_MESSENGER, messenger); + + args.putInt(ARG_TITLE, title); + args.putInt(ARG_MESSAGE, message); + args.putInt(ARG_MIDDLE_BUTTON, middleButton); + args.putString(ARG_INSTALL_PATH, PLAY_STORE_PATH + packageToInstall); + args.putBoolean(ARG_USE_MIDDLE_BUTTON, useMiddleButton); + } + + public static AlertDialog getInstallDialogFromArgs(Bundle args, final Activity activity, + final int messengerMiddleButtonClicked, + final int messengerDialogDimissed) { + final Messenger messenger = args.getParcelable(ARG_MESSENGER); + + final int title = args.getInt(ARG_TITLE); + final int message = args.getInt(ARG_MESSAGE); + final int middleButton = args.getInt(ARG_MIDDLE_BUTTON); + final String installPath = args.getString(ARG_INSTALL_PATH); + final boolean useMiddleButton = args.getBoolean(ARG_USE_MIDDLE_BUTTON); + + ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity); + CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(theme); + + builder.setTitle(title).setMessage(message); + + builder.setNegativeButton(R.string.orbot_install_dialog_cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Message msg = Message.obtain(); + msg.what = messengerDialogDimissed; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + }); + + builder.setPositiveButton(R.string.orbot_install_dialog_install, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Uri uri = Uri.parse(installPath); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + activity.startActivity(intent); + + Message msg = Message.obtain(); + msg.what = messengerDialogDimissed; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + } + ); + + if (useMiddleButton) { + builder.setNeutralButton(middleButton, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Message msg = Message.obtain(); + msg.what = messengerMiddleButtonClicked; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); + } catch (NullPointerException e) { + Log.w(Constants.TAG, "Messenger is null!", e); + } + } + } + ); + } + + return builder.show(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index a3cd63d13..224e0085b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Curve; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.util.Log; import java.nio.ByteBuffer; @@ -449,11 +450,11 @@ public class KeyFormattingUtils { if (signatureResult != null && signatureResult.isSignatureOnly()) { encIcon = R.drawable.status_lock_open_24dp; encText = R.string.decrypt_result_not_encrypted; - encColor = R.color.android_red_light; + encColor = R.color.key_flag_red; } else { encIcon = R.drawable.status_lock_closed_24dp; encText = R.string.decrypt_result_encrypted; - encColor = R.color.android_green_light; + encColor = R.color.key_flag_green; } int encColorRes = context.getResources().getColor(encColor); @@ -470,7 +471,7 @@ public class KeyFormattingUtils { sigText = R.string.decrypt_result_no_signature; sigIcon = R.drawable.status_signature_invalid_cutout_24dp; - sigColor = R.color.bg_gray; + sigColor = R.color.key_flag_gray; // won't be used, but makes compiler happy sigActionText = 0; @@ -481,7 +482,7 @@ public class KeyFormattingUtils { case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED: { sigText = R.string.decrypt_result_signature_certified; sigIcon = R.drawable.status_signature_verified_cutout_24dp; - sigColor = R.color.android_green_light; + sigColor = R.color.key_flag_green; sigActionText = R.string.decrypt_result_action_show; sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; @@ -491,7 +492,7 @@ public class KeyFormattingUtils { case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED: { sigText = R.string.decrypt_result_signature_uncertified; sigIcon = R.drawable.status_signature_unverified_cutout_24dp; - sigColor = R.color.android_orange_light; + sigColor = R.color.key_flag_orange; sigActionText = R.string.decrypt_result_action_show; sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; @@ -501,7 +502,7 @@ public class KeyFormattingUtils { case OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED: { sigText = R.string.decrypt_result_signature_revoked_key; sigIcon = R.drawable.status_signature_revoked_cutout_24dp; - sigColor = R.color.android_red_light; + sigColor = R.color.key_flag_red; sigActionText = R.string.decrypt_result_action_show; sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; @@ -511,7 +512,7 @@ public class KeyFormattingUtils { case OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED: { sigText = R.string.decrypt_result_signature_expired_key; sigIcon = R.drawable.status_signature_expired_cutout_24dp; - sigColor = R.color.android_red_light; + sigColor = R.color.key_flag_red; sigActionText = R.string.decrypt_result_action_show; sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; @@ -521,7 +522,7 @@ public class KeyFormattingUtils { case OpenPgpSignatureResult.SIGNATURE_KEY_MISSING: { sigText = R.string.decrypt_result_signature_missing_key; sigIcon = R.drawable.status_signature_unknown_cutout_24dp; - sigColor = R.color.android_red_light; + sigColor = R.color.key_flag_red; sigActionText = R.string.decrypt_result_action_Lookup; sigActionIcon = R.drawable.ic_file_download_grey_24dp; @@ -532,7 +533,7 @@ public class KeyFormattingUtils { case OpenPgpSignatureResult.SIGNATURE_ERROR: { sigText = R.string.decrypt_result_invalid_signature; sigIcon = R.drawable.status_signature_invalid_cutout_24dp; - sigColor = R.color.android_red_light; + sigColor = R.color.key_flag_red; sigActionText = R.string.decrypt_result_action_show; sigActionIcon = R.drawable.ic_vpn_key_grey_24dp; @@ -595,7 +596,7 @@ public class KeyFormattingUtils { context.getResources().getDrawable(R.drawable.status_signature_verified_cutout_24dp)); } if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_green_light; + color = R.color.key_flag_green; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -608,7 +609,7 @@ public class KeyFormattingUtils { statusIcon.setImageDrawable( context.getResources().getDrawable(R.drawable.status_lock_closed_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_green_light; + color = R.color.key_flag_green; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -627,7 +628,7 @@ public class KeyFormattingUtils { context.getResources().getDrawable(R.drawable.status_signature_unverified_cutout_24dp)); } if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_orange_light; + color = R.color.key_flag_orange; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -640,7 +641,7 @@ public class KeyFormattingUtils { statusIcon.setImageDrawable( context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_red_light; + color = R.color.key_flag_red; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -659,7 +660,7 @@ public class KeyFormattingUtils { context.getResources().getDrawable(R.drawable.status_signature_revoked_cutout_24dp)); } if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_red_light; + color = R.color.key_flag_red; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -677,7 +678,7 @@ public class KeyFormattingUtils { context.getResources().getDrawable(R.drawable.status_signature_expired_cutout_24dp)); } if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_red_light; + color = R.color.key_flag_red; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -690,7 +691,7 @@ public class KeyFormattingUtils { statusIcon.setImageDrawable( context.getResources().getDrawable(R.drawable.status_lock_open_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_red_light; + color = R.color.key_flag_red; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -703,7 +704,7 @@ public class KeyFormattingUtils { statusIcon.setImageDrawable( context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_red_light; + color = R.color.key_flag_red; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -716,7 +717,7 @@ public class KeyFormattingUtils { statusIcon.setImageDrawable( context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.android_red_light; + color = R.color.key_flag_red; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); @@ -730,7 +731,7 @@ public class KeyFormattingUtils { statusIcon.setImageDrawable( context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24dp)); if (color == KeyFormattingUtils.DEFAULT_COLOR) { - color = R.color.bg_gray; + color = R.color.key_flag_gray; } statusIcon.setColorFilter(context.getResources().getColor(color), PorterDuff.Mode.SRC_IN); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ThemeChanger.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ThemeChanger.java new file mode 100644 index 000000000..f53e43528 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/ThemeChanger.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2015 Thialfihar <thi@thialfihar.org> + * + * 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.util; + +import android.content.Context; +import android.view.ContextThemeWrapper; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.Preferences; + +public class ThemeChanger { + private Context mContext; + private Preferences mPreferences; + private String mCurrentTheme = null; + + static public ContextThemeWrapper getDialogThemeWrapper(Context context) { + Preferences preferences = Preferences.getPreferences(context); + + // if the dialog is displayed from the application class, design is missing. + // hack to get holo design (which is not automatically applied due to activity's + // Theme.NoDisplay) + if (Constants.Pref.Theme.DARK.equals(preferences.getTheme())) { + return new ContextThemeWrapper(context, R.style.Theme_AppCompat_Dialog); + } else { + return new ContextThemeWrapper(context, R.style.Theme_AppCompat_Light_Dialog); + } + } + + public ThemeChanger(Context context) { + mContext = context; + mPreferences = Preferences.getPreferences(mContext); + } + + /** + * Apply the theme set in preferences if it isn't equal to mCurrentTheme + * anymore or mCurrentTheme hasn't been set yet. + * If a new theme is applied in this method, then return true, so + * the caller can re-create the activity, if need be. + */ + public boolean changeTheme() { + String newTheme = mPreferences.getTheme(); + if (mCurrentTheme != null && mCurrentTheme.equals(newTheme)) { + return false; + } + + int themeId = R.style.LightTheme; + if (Constants.Pref.Theme.DARK.equals(newTheme)) { + themeId = R.style.DarkTheme; + } + + ContextThemeWrapper w = new ContextThemeWrapper(mContext, themeId); + mContext.getTheme().setTo(w.getTheme()); + mCurrentTheme = newTheme; + + return true; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java new file mode 100644 index 000000000..95199bcd5 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.ui.util.recyclerview; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +public class DividerItemDecoration extends RecyclerView.ItemDecoration { + + private static final int[] ATTRS = new int[]{ + android.R.attr.listDivider + }; + + public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; + + public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; + + private Drawable mDivider; + + private int mOrientation; + + public DividerItemDecoration(Context context, int orientation) { + final TypedArray a = context.obtainStyledAttributes(ATTRS); + mDivider = a.getDrawable(0); + a.recycle(); + setOrientation(orientation); + } + + public void setOrientation(int orientation) { + if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { + throw new IllegalArgumentException("invalid orientation"); + } + mOrientation = orientation; + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + if (mOrientation == VERTICAL_LIST) { + drawVertical(c, parent); + } else { + drawHorizontal(c, parent); + } + } + + public void drawVertical(Canvas c, RecyclerView parent) { + final int left = parent.getPaddingLeft(); + final int right = parent.getWidth() - parent.getPaddingRight(); + + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child + .getLayoutParams(); + final int top = child.getBottom() + params.bottomMargin; + final int bottom = top + mDivider.getIntrinsicHeight(); + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(c); + } + } + + public void drawHorizontal(Canvas c, RecyclerView parent) { + final int top = parent.getPaddingTop(); + final int bottom = parent.getHeight() - parent.getPaddingBottom(); + + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child + .getLayoutParams(); + final int left = child.getRight() + params.rightMargin; + final int right = left + mDivider.getIntrinsicHeight(); + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(c); + } + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, + RecyclerView.State state) { + if (mOrientation == VERTICAL_LIST) { + outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); + } else { + outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); + } + } +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java new file mode 100644 index 000000000..c691182bf --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 Paul Burke + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.ui.util.recyclerview; + +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; + +/** + * Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}. + */ +public interface ItemTouchHelperAdapter { + + /** + * Called when an item has been dragged far enough to trigger a move. This is called every time + * an item is shifted, and <strong>not</strong> at the end of a "drop" event.<br/> + * <br/> + * Implementations should call {@link RecyclerView.Adapter#notifyItemMoved(int, int)} after + * adjusting the underlying data to reflect this move. + * + * @param fromPosition The start position of the moved item. + * @param toPosition Then resolved position of the moved item. + * @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder) + * @see RecyclerView.ViewHolder#getAdapterPosition() + */ + void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target, + int fromPosition, int toPosition); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java new file mode 100644 index 000000000..0fd24581d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2015 Paul Burke + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.ui.util.recyclerview; + +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; + +/** + * An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and + * swipe-to-dismiss. Drag events are automatically started by an item long-press.<br/> + * </br/> + * Expects the <code>RecyclerView.Adapter</code> to listen for {@link + * ItemTouchHelperAdapter} callbacks and the <code>RecyclerView.ViewHolder</code> to implement + * {@link ItemTouchHelperViewHolder}. + */ +public class ItemTouchHelperDragCallback extends ItemTouchHelper.Callback { + + private final ItemTouchHelperAdapter mAdapter; + + public ItemTouchHelperDragCallback(ItemTouchHelperAdapter adapter) { + mAdapter = adapter; + } + + @Override + public boolean isLongPressDragEnabled() { + return false; + } + + @Override + public boolean isItemViewSwipeEnabled() { + return false; + } + + @Override + public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + // Enable drag and swipe in both directions + final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; + final int swipeFlags = 0; + return makeMovementFlags(dragFlags, swipeFlags); + } + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, + RecyclerView.ViewHolder target) { + if (source.getItemViewType() != target.getItemViewType()) { + return false; + } + + // Notify the adapter of the move + mAdapter.onItemMove(source, target, source.getAdapterPosition(), target.getAdapterPosition()); + return true; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) { + // we don't support swipe + } + + @Override + public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { + if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { + // Let the view holder know that this item is being moved or dragged + ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; + itemViewHolder.onItemSelected(); + } + + super.onSelectedChanged(viewHolder, actionState); + } + + @Override + public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { + super.clearView(recyclerView, viewHolder); + + // Tell the view holder it's time to restore the idle state + ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; + itemViewHolder.onItemClear(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java new file mode 100644 index 000000000..97e70d71e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015 Paul Burke + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sufficientlysecure.keychain.ui.util.recyclerview; + +import android.support.v7.widget.helper.ItemTouchHelper; + +/** + * Interface to notify an item ViewHolder of relevant callbacks from {@link + * android.support.v7.widget.helper.ItemTouchHelper.Callback}. + */ +public interface ItemTouchHelperViewHolder { + + /** + * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped. + * Implementations should update the item view to indicate it's active state. + */ + void onItemSelected(); + + + /** + * Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active item + * state should be cleared. + */ + void onItemClear(); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java new file mode 100644 index 000000000..7efcbb30c --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 Jacob Tabak + * + * 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.util.recyclerview; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +/** + * based on http://stackoverflow.com/a/26196831/3000919 + */ +public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener { + private OnItemClickListener mListener; + private boolean mIgnoreTouch = false; + + public interface OnItemClickListener { + void onItemClick(View view, int position); + } + + GestureDetector mGestureDetector; + + public RecyclerItemClickListener(Context context, OnItemClickListener listener) { + mListener = listener; + mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onSingleTapUp(MotionEvent e) { + return true; + } + }); + } + + @Override + public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) { + if (mIgnoreTouch) { + return false; + } + View childView = view.findChildViewUnder(e.getX(), e.getY()); + if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) { + mListener.onItemClick(childView, view.getChildAdapterPosition(childView)); + return true; + } + return false; + } + + @Override + public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { + // TODO: should we move mListener.onItemClick here + } + + @Override + public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { + mIgnoreTouch = disallowIntercept; + } +}
\ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java index 0fed46544..6cd33aada 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/CertifyKeySpinner.java @@ -109,14 +109,18 @@ public class CertifyKeySpinner extends KeySpinner { @Override boolean isItemEnabled(Cursor cursor) { + // "none" entry is always enabled! + if (cursor.getPosition() == 0) { + return true; + } + if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) { return false; } if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) { return false; } - // don't invalidate the "None" entry, which is also null! - if (cursor.getPosition() != 0 && cursor.isNull(mIndexHasCertify)) { + if (cursor.isNull(mIndexHasCertify)) { return false; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java deleted file mode 100644 index ec91b9fe4..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/Editor.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> - * - * 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.widget; - -public interface Editor { - public interface EditorListener { - public void onDeleted(Editor editor, boolean wasNewItem); - public void onEdited(); - } - - public void setEditorListener(EditorListener listener); - public boolean needsSaving(); -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java index e55f6b1ad..494ccb6d3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/EmailEditText.java @@ -75,10 +75,10 @@ public class EmailEditText extends AppCompatAutoCompleteTextView { Matcher emailMatcher = Patterns.EMAIL_ADDRESS.matcher(email); if (emailMatcher.matches()) { EmailEditText.this.setCompoundDrawablesWithIntrinsicBounds(0, 0, - R.drawable.uid_mail_ok, 0); + R.drawable.ic_stat_retyped_ok, 0); } else { EmailEditText.this.setCompoundDrawablesWithIntrinsicBounds(0, 0, - R.drawable.uid_mail_bad, 0); + R.drawable.ic_stat_retyped_bad, 0); } } else { // remove drawable if email is empty diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java deleted file mode 100644 index 3fd01958a..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeyServerEditor.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org> - * - * 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.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.R; - -public class KeyServerEditor extends LinearLayout implements Editor, OnClickListener { - private EditorListener mEditorListener = null; - - ImageButton mDeleteButton; - TextView mServer; - - public KeyServerEditor(Context context) { - super(context); - } - - public KeyServerEditor(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - setDrawingCacheEnabled(true); - setAlwaysDrawnWithCacheEnabled(true); - - mServer = (TextView) findViewById(R.id.server); - - mDeleteButton = (ImageButton) findViewById(R.id.delete); - mDeleteButton.setOnClickListener(this); - - super.onFinishInflate(); - } - - public void setValue(String value) { - mServer.setText(value); - } - - public String getValue() { - return mServer.getText().toString().trim(); - } - - public void onClick(View v) { - final ViewGroup parent = (ViewGroup) getParent(); - if (v == mDeleteButton) { - parent.removeView(this); - if (mEditorListener != null) { - mEditorListener.onDeleted(this, false); - } - } - } - - @Override - public boolean needsSaving() { - return false; - } - - public void setEditorListener(EditorListener listener) { - mEditorListener = listener; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java index 5050c01af..1884daf12 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/KeySpinner.java @@ -84,7 +84,8 @@ public abstract class KeySpinner extends AppCompatSpinner implements @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { if (mListener != null) { - mListener.onKeyChanged(id); + long keyId = getSelectedKeyId(getItemAtPosition(position)); + mListener.onKeyChanged(keyId); } } @@ -137,6 +138,10 @@ public abstract class KeySpinner extends AppCompatSpinner implements public long getSelectedKeyId() { Object item = getSelectedItem(); + return getSelectedKeyId(item); + } + + public long getSelectedKeyId(Object item) { if (item instanceof KeyItem) { return ((KeyItem) item).mKeyId; } @@ -186,7 +191,7 @@ public abstract class KeySpinner extends AppCompatSpinner implements } @Override - public Object getItem(int position) { + public KeyItem getItem(int position) { if (position == 0) { return null; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java index 0ec145657..a7ead8039 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/PasswordStrengthView.java @@ -31,7 +31,9 @@ import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; +import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.util.Log; /** * Created by Matt Allen @@ -97,9 +99,9 @@ public class PasswordStrengthView extends View { public PasswordStrengthView(Context context, AttributeSet attrs) { super(context, attrs); - int COLOR_FAIL = getResources().getColor(R.color.android_red_light); - int COLOR_WEAK = getResources().getColor(R.color.android_orange_light); - int COLOR_STRONG = getResources().getColor(R.color.android_green_light); + int COLOR_FAIL = getResources().getColor(R.color.password_strength_low); + int COLOR_WEAK = getResources().getColor(R.color.password_strength_medium); + int COLOR_STRONG = getResources().getColor(R.color.password_strength_high); TypedArray style = context.getTheme().obtainStyledAttributes( attrs, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java index c59ad7a12..8fb9e38aa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SignKeySpinner.java @@ -72,13 +72,18 @@ public class SignKeySpinner extends KeySpinner { @Override boolean isItemEnabled(Cursor cursor) { + // "none" entry is always enabled! + if (cursor.getPosition() == 0) { + return true; + } + if (cursor.getInt(KeyAdapter.INDEX_IS_REVOKED) != 0) { return false; } if (cursor.getInt(KeyAdapter.INDEX_IS_EXPIRED) != 0) { return false; } - if (cursor.getInt(mIndexHasSign) == 0) { + if (cursor.isNull(mIndexHasSign)) { return false; } |