aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui
diff options
context:
space:
mode:
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java77
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java550
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java22
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java23
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerBackupFragment.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java)37
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java6
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java248
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java36
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java47
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java5
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java7
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java26
13 files changed, 849 insertions, 239 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java
new file mode 100644
index 000000000..ff120c9b5
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ *
+ * 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;
+
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.view.MenuItem;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.base.BaseActivity;
+
+
+public class BackupActivity extends BaseActivity {
+
+ public static final String EXTRA_MASTER_KEY_IDS = "master_key_ids";
+ public static final String EXTRA_SECRET = "export_secret";
+
+ @Override
+ protected void initLayout() {
+ setContentView(R.layout.backup_activity);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // noinspection ConstantConditions, we know this activity has an action bar
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ if (savedInstanceState == null) {
+ Intent intent = getIntent();
+ boolean exportSecret = intent.getBooleanExtra(EXTRA_SECRET, false);
+ long[] masterKeyIds = intent.getLongArrayExtra(EXTRA_MASTER_KEY_IDS);
+
+ Fragment frag = BackupCodeFragment.newInstance(masterKeyIds, exportSecret);
+
+ FragmentManager fragMan = getSupportFragmentManager();
+ fragMan.beginTransaction()
+ .setCustomAnimations(0, 0)
+ .replace(R.id.content_frame, frag)
+ .commit();
+ }
+
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ FragmentManager fragMan = getSupportFragmentManager();
+ // pop from back stack, or if nothing was on there finish activity
+ if ( ! fragMan.popBackStackImmediate()) {
+ finish();
+ }
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java
new file mode 100644
index 000000000..be1399a30
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2015 Vincent Breitmoser <v.breitmoser@mugenguild.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 java.io.File;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Random;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.operations.results.ExportResult;
+import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
+import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.Notify.ActionListener;
+import org.sufficientlysecure.keychain.ui.util.Notify.Style;
+import org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator;
+import org.sufficientlysecure.keychain.util.FileHelper;
+import org.sufficientlysecure.keychain.util.Passphrase;
+
+
+public class BackupCodeFragment extends CryptoOperationFragment<ExportKeyringParcel,ExportResult>
+ implements OnBackStackChangedListener {
+
+ public static final String ARG_BACKUP_CODE = "backup_code";
+ public static final String BACK_STACK_INPUT = "state_display";
+ public static final String ARG_EXPORT_SECRET = "export_secret";
+ public static final String ARG_MASTER_KEY_IDS = "master_key_ids";
+
+ public static final String ARG_CURRENT_STATE = "current_state";
+
+ public static final int REQUEST_SAVE = 1;
+ public static final String ARG_BACK_STACK = "back_stack";
+
+ // argument variables
+ private boolean mExportSecret;
+ private long[] mMasterKeyIds;
+ String mBackupCode;
+
+ private EditText[] mCodeEditText;
+ private ToolableViewAnimator mStatusAnimator, mTitleAnimator, mCodeFieldsAnimator;
+
+ private Integer mBackStackLevel;
+
+ private Uri mCachedExportUri;
+ private boolean mShareNotSave;
+
+ public static BackupCodeFragment newInstance(long[] masterKeyIds, boolean exportSecret) {
+ BackupCodeFragment frag = new BackupCodeFragment();
+
+ Bundle args = new Bundle();
+ args.putString(ARG_BACKUP_CODE, generateRandomCode());
+ args.putLongArray(ARG_MASTER_KEY_IDS, masterKeyIds);
+ args.putBoolean(ARG_EXPORT_SECRET, exportSecret);
+ frag.setArguments(args);
+
+ return frag;
+ }
+
+ enum BackupCodeState {
+ STATE_UNINITIALIZED, STATE_DISPLAY, STATE_INPUT, STATE_INPUT_ERROR, STATE_OK
+ }
+
+ BackupCodeState mCurrentState = BackupCodeState.STATE_UNINITIALIZED;
+
+ void switchState(BackupCodeState state, boolean animate) {
+
+ switch (state) {
+ case STATE_UNINITIALIZED:
+ throw new AssertionError("can't switch to uninitialized state, this is a bug!");
+
+ case STATE_DISPLAY:
+ mTitleAnimator.setDisplayedChild(0, animate);
+ mStatusAnimator.setDisplayedChild(0, animate);
+ mCodeFieldsAnimator.setDisplayedChild(0, animate);
+
+ break;
+
+ case STATE_INPUT:
+ mTitleAnimator.setDisplayedChild(1, animate);
+ mStatusAnimator.setDisplayedChild(1, animate);
+ mCodeFieldsAnimator.setDisplayedChild(1, animate);
+
+ for (EditText editText : mCodeEditText) {
+ editText.setText("");
+ }
+
+ pushBackStackEntry();
+
+ break;
+
+ case STATE_INPUT_ERROR: {
+ mTitleAnimator.setDisplayedChild(1, false);
+ mStatusAnimator.setDisplayedChild(2, animate);
+ mCodeFieldsAnimator.setDisplayedChild(1, false);
+
+ hideKeyboard();
+
+ if (animate) {
+ @ColorInt int black = mCodeEditText[0].getCurrentTextColor();
+ @ColorInt int red = getResources().getColor(R.color.android_red_dark);
+ animateFlashText(mCodeEditText, black, red, false);
+ }
+
+ break;
+ }
+
+ case STATE_OK: {
+ mTitleAnimator.setDisplayedChild(2, animate);
+ mStatusAnimator.setDisplayedChild(3, animate);
+ mCodeFieldsAnimator.setDisplayedChild(1, false);
+
+ hideKeyboard();
+
+ for (EditText editText : mCodeEditText) {
+ editText.setEnabled(false);
+ }
+
+ @ColorInt int green = getResources().getColor(R.color.android_green_dark);
+ if (animate) {
+ @ColorInt int black = mCodeEditText[0].getCurrentTextColor();
+ animateFlashText(mCodeEditText, black, green, true);
+ } else {
+ for (TextView textView : mCodeEditText) {
+ textView.setTextColor(green);
+ }
+ }
+
+ popBackStackNoAction();
+
+ break;
+ }
+
+ }
+
+ mCurrentState = state;
+
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.backup_code_fragment, container, false);
+
+ Bundle args = getArguments();
+ mBackupCode = args.getString(ARG_BACKUP_CODE);
+ mMasterKeyIds = args.getLongArray(ARG_MASTER_KEY_IDS);
+ mExportSecret = args.getBoolean(ARG_EXPORT_SECRET);
+
+ mCodeEditText = new EditText[4];
+ mCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1);
+ mCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2);
+ mCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3);
+ mCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4);
+
+ {
+ TextView[] codeDisplayText = new TextView[4];
+ codeDisplayText[0] = (TextView) view.findViewById(R.id.backup_code_display_1);
+ codeDisplayText[1] = (TextView) view.findViewById(R.id.backup_code_display_2);
+ codeDisplayText[2] = (TextView) view.findViewById(R.id.backup_code_display_3);
+ codeDisplayText[3] = (TextView) view.findViewById(R.id.backup_code_display_4);
+
+ // set backup code in code TextViews
+ char[] backupCode = mBackupCode.toCharArray();
+ for (int i = 0; i < codeDisplayText.length; i++) {
+ codeDisplayText[i].setText(backupCode, i * 7, 6);
+ }
+
+ // set background to null in TextViews - this will retain padding from EditText style!
+ for (TextView textView : codeDisplayText) {
+ // noinspection deprecation, setBackground(Drawable) is API level >=16
+ textView.setBackgroundDrawable(null);
+ }
+ }
+
+ setupEditTextFocusNext(mCodeEditText);
+ setupEditTextSuccessListener(mCodeEditText);
+
+ mStatusAnimator = (ToolableViewAnimator) view.findViewById(R.id.status_animator);
+ mTitleAnimator = (ToolableViewAnimator) view.findViewById(R.id.title_animator);
+ mCodeFieldsAnimator = (ToolableViewAnimator) view.findViewById(R.id.code_animator);
+
+ View backupInput = view.findViewById(R.id.button_backup_input);
+ backupInput.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ switchState(BackupCodeState.STATE_INPUT, true);
+ }
+ });
+
+ view.findViewById(R.id.button_backup_save).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mShareNotSave = false;
+ startBackup();
+ }
+ });
+
+ view.findViewById(R.id.button_backup_share).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mShareNotSave = true;
+ startBackup();
+ }
+ });
+
+ view.findViewById(R.id.button_backup_back).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ FragmentManager fragMan = getFragmentManager();
+ if (fragMan != null) {
+ fragMan.popBackStack();
+ }
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ if (savedInstanceState != null) {
+ int savedBackStack = savedInstanceState.getInt(ARG_BACK_STACK);
+ if (savedBackStack >= 0) {
+ mBackStackLevel = savedBackStack;
+ // unchecked use, we know that this one is available in onViewCreated
+ getFragmentManager().addOnBackStackChangedListener(this);
+ }
+ BackupCodeState savedState = BackupCodeState.values()[savedInstanceState.getInt(ARG_CURRENT_STATE)];
+ switchState(savedState, false);
+ } else if (mCurrentState == BackupCodeState.STATE_UNINITIALIZED) {
+ switchState(BackupCodeState.STATE_DISPLAY, true);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(ARG_CURRENT_STATE, mCurrentState.ordinal());
+ outState.putInt(ARG_BACK_STACK, mBackStackLevel == null ? -1 : mBackStackLevel);
+ }
+
+ private void setupEditTextSuccessListener(final EditText[] backupCodes) {
+ for (EditText backupCode : backupCodes) {
+
+ backupCode.addTextChangedListener(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) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (s.length() > 6) {
+ throw new AssertionError("max length of each field is 6!");
+ }
+
+ boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT
+ || mCurrentState == BackupCodeState.STATE_INPUT_ERROR;
+ boolean partIsComplete = s.length() == 6;
+ if (!inInputState || !partIsComplete) {
+ return;
+ }
+
+ checkIfCodeIsCorrect();
+ }
+ });
+
+ }
+ }
+
+ private void checkIfCodeIsCorrect() {
+
+ StringBuilder backupCodeInput = new StringBuilder(26);
+ for (EditText editText : mCodeEditText) {
+ if (editText.getText().length() < 6) {
+ return;
+ }
+ backupCodeInput.append(editText.getText());
+ backupCodeInput.append('-');
+ }
+ backupCodeInput.deleteCharAt(backupCodeInput.length() - 1);
+
+ // if they don't match, do nothing
+ if (backupCodeInput.toString().equals(mBackupCode)) {
+ switchState(BackupCodeState.STATE_OK, true);
+ return;
+ }
+
+ // TODO remove debug code
+ if (backupCodeInput.toString().startsWith("ABC")) {
+ switchState(BackupCodeState.STATE_OK, true);
+ return;
+ }
+
+ switchState(BackupCodeState.STATE_INPUT_ERROR, true);
+
+ }
+
+ private static void animateFlashText(
+ final TextView[] textViews, int color1, int color2, boolean staySecondColor) {
+
+ ValueAnimator anim = ValueAnimator.ofObject(new ArgbEvaluator(), color1, color2);
+ anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ for (TextView textView : textViews) {
+ textView.setTextColor((Integer) animator.getAnimatedValue());
+ }
+ }
+ });
+ anim.setRepeatMode(ValueAnimator.REVERSE);
+ anim.setRepeatCount(staySecondColor ? 4 : 5);
+ anim.setDuration(180);
+ anim.setInterpolator(new AccelerateInterpolator());
+ anim.start();
+
+ }
+
+ private static void setupEditTextFocusNext(final EditText[] backupCodes) {
+ for (int i = 0; i < backupCodes.length -1; i++) {
+
+ final int next = i+1;
+
+ backupCodes[i].addTextChangedListener(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) {
+ boolean inserting = before < count;
+ boolean cursorAtEnd = (start + count) == 6;
+
+ if (inserting && cursorAtEnd) {
+ backupCodes[next].requestFocus();
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+ });
+
+ }
+ }
+
+ private void pushBackStackEntry() {
+ if (mBackStackLevel != null) {
+ return;
+ }
+ FragmentManager fragMan = getFragmentManager();
+ mBackStackLevel = fragMan.getBackStackEntryCount();
+ fragMan.beginTransaction().addToBackStack(BACK_STACK_INPUT).commit();
+ fragMan.addOnBackStackChangedListener(this);
+ }
+
+ private void popBackStackNoAction() {
+ FragmentManager fragMan = getFragmentManager();
+ fragMan.removeOnBackStackChangedListener(this);
+ fragMan.popBackStackImmediate(BACK_STACK_INPUT, FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ mBackStackLevel = null;
+ }
+
+ @Override
+ public void onBackStackChanged() {
+ FragmentManager fragMan = getFragmentManager();
+ if (mBackStackLevel != null && fragMan.getBackStackEntryCount() == mBackStackLevel) {
+ fragMan.removeOnBackStackChangedListener(this);
+ switchState(BackupCodeState.STATE_DISPLAY, true);
+ mBackStackLevel = null;
+ }
+ }
+
+ private void startBackup() {
+
+ FragmentActivity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ if (mCachedExportUri == null) {
+ mCachedExportUri = TemporaryStorageProvider.createFile(activity);
+ cryptoOperation();
+ return;
+ }
+
+ if (mShareNotSave) {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("application/octet-stream");
+ intent.putExtra(Intent.EXTRA_STREAM, mCachedExportUri);
+ startActivity(intent);
+ } else {
+ saveFile(false);
+ }
+
+ }
+
+ private void saveFile(boolean overwrite) {
+ FragmentActivity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ String date = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
+
+ // for kitkat and above, we have the document api
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ String filename = "openkeychain_backup_" + date + (mExportSecret ? ".gpg" : ".pub.gpg");
+ FileHelper.saveDocument(this, "application/octet-stream", filename, REQUEST_SAVE);
+ return;
+ }
+
+ File file = new File(Constants.Path.APP_DIR, "backup_" + date + (mExportSecret ? ".gpg" : ".pub.gpg"));
+
+ if (!overwrite && file.exists()) {
+ Notify.create(activity, R.string.snack_backup_exists, Style.WARN, new ActionListener() {
+ @Override
+ public void onAction() {
+ saveFile(true);
+ }
+ }, R.string.snack_btn_overwrite).show();
+ return;
+ }
+
+ try {
+ FileHelper.copyUriData(activity, mCachedExportUri, Uri.fromFile(file));
+ Notify.create(activity, R.string.snack_backup_saved_dir, Style.OK).show();
+ } catch (IOException e) {
+ Notify.create(activity, R.string.snack_backup_error_saving, Style.ERROR).show();
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode != REQUEST_SAVE) {
+ super.onActivityResult(requestCode, resultCode, data);
+ return;
+ }
+
+ if (resultCode != FragmentActivity.RESULT_OK) {
+ return;
+ }
+
+ FragmentActivity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+ try {
+ Uri outputUri = data.getData();
+ FileHelper.copyUriData(activity, mCachedExportUri, outputUri);
+ Notify.create(activity, R.string.snack_backup_saved, Style.OK).show();
+ } catch (IOException e) {
+ Notify.create(activity, R.string.snack_backup_error_saving, Style.ERROR).show();
+ }
+ }
+
+ @Nullable
+ @Override
+ public ExportKeyringParcel createOperationInput() {
+ // TODO replace debug code with real thing
+ // return new ExportKeyringParcel(new Passphrase(mBackupCode), mMasterKeyIds, mExportSecret, mCachedExportUri);
+ return new ExportKeyringParcel(new Passphrase("abc"), mMasterKeyIds, mExportSecret, mCachedExportUri);
+ }
+
+ @Override
+ public void onCryptoOperationSuccess(ExportResult result) {
+ startBackup();
+ }
+
+ @Override
+ public void onCryptoOperationError(ExportResult result) {
+ result.createNotify(getActivity()).show();
+ mCachedExportUri = null;
+ }
+
+ @Override
+ public void onCryptoOperationCancelled() {
+ mCachedExportUri = null;
+ }
+
+ @NonNull
+ private static String generateRandomCode() {
+
+ Random r = new SecureRandom();
+
+ // simple generation of a 20 character backup code
+ StringBuilder code = new StringBuilder(28);
+ for (int i = 0; i < 24; i++) {
+ if (i == 6 || i == 12 || i == 18) {
+ code.append('-');
+ }
+ code.append((char) ('A' + r.nextInt(26)));
+ }
+
+ return code.toString();
+
+ }
+
+
+}
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 739eb3e35..b79e4454d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyFinalFragment.java
@@ -37,17 +37,17 @@ import org.spongycastle.bcpg.sig.KeyFlags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
-import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.operations.results.UploadResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
-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.UploadKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
@@ -69,7 +69,7 @@ public class CreateKeyFinalFragment extends Fragment {
SaveKeyringParcel mSaveKeyringParcel;
- private CryptoOperationHelper<ExportKeyringParcel, ExportResult> mUploadOpHelper;
+ private CryptoOperationHelper<UploadKeyringParcel, UploadResult> mUploadOpHelper;
private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mCreateOpHelper;
private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mMoveToCardOpHelper;
@@ -407,20 +407,20 @@ public class CreateKeyFinalFragment extends Fragment {
}
// set data uri as path to keyring
- final Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(saveKeyResult.mMasterKeyId);
+ final long masterKeyId = saveKeyResult.mMasterKeyId;
// upload to favorite keyserver
final String keyserver = Preferences.getPreferences(activity).getPreferredKeyserver();
- CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult> callback
- = new CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult>() {
+ CryptoOperationHelper.Callback<UploadKeyringParcel, UploadResult> callback
+ = new CryptoOperationHelper.Callback<UploadKeyringParcel, UploadResult>() {
@Override
- public ExportKeyringParcel createOperationInput() {
- return new ExportKeyringParcel(keyserver, blobUri);
+ public UploadKeyringParcel createOperationInput() {
+ return new UploadKeyringParcel(keyserver, masterKeyId);
}
@Override
- public void onCryptoOperationSuccess(ExportResult result) {
+ public void onCryptoOperationSuccess(UploadResult result) {
handleResult(result);
}
@@ -430,11 +430,11 @@ public class CreateKeyFinalFragment extends Fragment {
}
@Override
- public void onCryptoOperationError(ExportResult result) {
+ public void onCryptoOperationError(UploadResult result) {
handleResult(result);
}
- public void handleResult(ExportResult result) {
+ public void handleResult(UploadResult result) {
saveKeyResult.getLog().add(result, 0);
finishWithResult(saveKeyResult);
}
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 a0650f8b1..13838e77c 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
@@ -24,6 +24,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ClipDescription;
import android.content.Context;
@@ -36,6 +37,7 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v7.widget.DefaultItemAnimator;
@@ -249,6 +251,7 @@ public class DecryptListFragment
}
}
+ @TargetApi(VERSION_CODES.KITKAT)
private void saveFileDialog(InputDataResult result, int index) {
Activity activity = getActivity();
@@ -260,13 +263,13 @@ public class DecryptListFragment
mCurrentSaveFileUri = result.getOutputUris().get(index);
String filename = metadata.getFilename();
- if (filename == null) {
+ if (TextUtils.isEmpty(filename)) {
String ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(metadata.getMimeType());
filename = "decrypted" + (ext != null ? "."+ext : "");
}
- FileHelper.saveDocument(this, filename, metadata.getMimeType(),
- REQUEST_CODE_OUTPUT);
+ // requires >=kitkat
+ FileHelper.saveDocument(this, metadata.getMimeType(), filename, REQUEST_CODE_OUTPUT);
}
private void saveFile(Uri saveUri) {
@@ -374,6 +377,9 @@ public class DecryptListFragment
if (ClipDescription.compareMimeTypes(type, "text/plain")) {
// noinspection deprecation, this should be called from Context, but not available in minSdk
icon = getResources().getDrawable(R.drawable.ic_chat_black_24dp);
+ } else if (ClipDescription.compareMimeTypes(type, "application/pgp-keys")) {
+ // noinspection deprecation, this should be called from Context, but not available in minSdk
+ icon = getResources().getDrawable(R.drawable.ic_key_plus_grey600_24dp);
} else if (ClipDescription.compareMimeTypes(type, "image/*")) {
int px = FormattingUtils.dpToPx(context, 32);
Bitmap bitmap = FileHelper.getThumbnail(context, outputUri, new Point(px, px));
@@ -764,11 +770,14 @@ public class DecryptListFragment
String filename;
if (metadata == null) {
filename = getString(R.string.filename_unknown);
- } else if (TextUtils.isEmpty(metadata.getFilename())) {
- filename = getString("text/plain".equals(metadata.getMimeType())
- ? R.string.filename_unknown_text : R.string.filename_unknown);
- } else {
+ } else if ( ! TextUtils.isEmpty(metadata.getFilename())) {
filename = metadata.getFilename();
+ } else if (ClipDescription.compareMimeTypes(metadata.getMimeType(), "application/pgp-keys")) {
+ filename = getString(R.string.filename_keys);
+ } else if (ClipDescription.compareMimeTypes(metadata.getMimeType(), "text/plain")) {
+ filename = getString(R.string.filename_unknown_text);
+ } else {
+ filename = getString(R.string.filename_unknown);
}
fileHolder.vFilename.setText(filename);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerBackupFragment.java
index a3ea8ad9a..cf47dfc94 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DrawerBackupFragment.java
@@ -18,11 +18,7 @@
package org.sufficientlysecure.keychain.ui;
-import java.io.File;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Date;
-import java.util.Locale;
import android.app.Activity;
import android.content.ContentResolver;
@@ -37,13 +33,11 @@ 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 {
+public class DrawerBackupFragment extends Fragment {
// This ids for multiple key export.
private ArrayList<Long> mIdsForRepeatAskPassphrase;
@@ -51,24 +45,10 @@ public class BackupFragment extends Fragment {
private int mIndex;
static final int REQUEST_REPEAT_PASSPHRASE = 1;
- private ExportHelper mExportHelper;
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- // we won't get attached to a non-fragment activity, so the cast should be safe
- mExportHelper = new ExportHelper((FragmentActivity) activity);
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- mExportHelper = null;
- }
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.backup_fragment, container, false);
+ View view = inflater.inflate(R.layout.drawer_backup_fragment, container, false);
View backupAll = view.findViewById(R.id.backup_all);
View backupPublicKeys = view.findViewById(R.id.backup_public_keys);
@@ -187,14 +167,11 @@ public class BackupFragment extends Fragment {
}
private void startBackup(boolean exportSecret) {
- File filename;
- String date = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
- if (exportSecret) {
- filename = new File(Constants.Path.APP_DIR, "keys_" + date + ".asc");
- } else {
- filename = new File(Constants.Path.APP_DIR, "keys_" + date + ".pub.asc");
- }
- mExportHelper.showExportKeysDialog(null, filename, exportSecret);
+
+ Intent intent = new Intent(getActivity(), BackupActivity.class);
+ intent.putExtra(BackupActivity.EXTRA_SECRET, exportSecret);
+ startActivity(intent);
+
}
}
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 19603a549..bfe9ea290 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java
@@ -25,6 +25,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
@@ -34,6 +35,7 @@ import android.graphics.Bitmap;
import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v7.widget.DefaultItemAnimator;
@@ -215,6 +217,7 @@ public class EncryptFilesFragment
mSelectedFiles.requestFocus();
}
+ @TargetApi(VERSION_CODES.KITKAT)
private void showOutputFileDialog() {
if (mFilesAdapter.getModelCount() != 1) {
throw new IllegalStateException();
@@ -223,8 +226,7 @@ public class EncryptFilesFragment
String targetName =
(mEncryptFilenames ? "1" : FileHelper.getFilename(getActivity(), model.inputUri))
+ (mUseArmor ? Constants.FILE_EXTENSION_ASC : Constants.FILE_EXTENSION_PGP_MAIN);
- FileHelper.saveDocument(this, targetName,
- REQUEST_CODE_OUTPUT);
+ FileHelper.saveDocument(this, targetName, REQUEST_CODE_OUTPUT);
}
public void addFile(Intent data) {
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 c578bcf15..7f7532ddf 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java
@@ -21,8 +21,8 @@ import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Message;
import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
@@ -33,10 +33,8 @@ import org.sufficientlysecure.keychain.intents.OpenKeychainIntents;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
-import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
-import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
@@ -78,10 +76,8 @@ public class ImportKeysActivity extends BaseNfcActivity
public static final String EXTRA_PENDING_INTENT_DATA = "data";
private Intent mPendingIntentData;
- // view
- private ImportKeysListFragment mListFragment;
- private Fragment mTopFragment;
- private View mImportButton;
+ public static final String TAG_FRAG_LIST = "frag_list";
+ public static final String TAG_FRAG_TOP = "frag_top";
// for CryptoOperationHelper.Callback
private String mKeyserver;
@@ -94,15 +90,22 @@ public class ImportKeysActivity extends BaseNfcActivity
super.onCreate(savedInstanceState);
setFullScreenDialogClose(Activity.RESULT_CANCELED, true);
- mImportButton = findViewById(R.id.import_import);
- mImportButton.setOnClickListener(new OnClickListener() {
+ findViewById(R.id.import_import).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- importKeys();
+ importSelectedKeys();
}
});
- handleActions(savedInstanceState, getIntent());
+ // only used for OpenPgpService
+ if (getIntent().hasExtra(EXTRA_PENDING_INTENT_DATA)) {
+ mPendingIntentData = getIntent().getParcelableExtra(EXTRA_PENDING_INTENT_DATA);
+ }
+
+ // if we aren't being restored, initialize fragments
+ if (savedInstanceState == null) {
+ handleActions(getIntent());
+ }
}
@Override
@@ -110,7 +113,7 @@ public class ImportKeysActivity extends BaseNfcActivity
setContentView(R.layout.import_keys_activity);
}
- protected void handleActions(Bundle savedInstanceState, Intent intent) {
+ protected void handleActions(Intent intent) {
String action = intent.getAction();
Bundle extras = intent.getExtras();
Uri dataUri = intent.getData();
@@ -120,14 +123,8 @@ public class ImportKeysActivity extends BaseNfcActivity
extras = new Bundle();
}
- if (action == null) {
- startCloudFragment(savedInstanceState, null, false, null);
- startListFragment(savedInstanceState, null, null, null, null);
- return;
- }
-
if (Intent.ACTION_VIEW.equals(action)) {
- if (scheme.equals("http") || scheme.equals("https")) {
+ if ("http".equals(scheme) || "https".equals(scheme)) {
action = ACTION_SEARCH_KEYSERVER_FROM_URL;
} else {
// Android's Action when opening file associated to Keychain (see AndroidManifest.xml)
@@ -135,20 +132,24 @@ public class ImportKeysActivity extends BaseNfcActivity
action = ACTION_IMPORT_KEY;
}
}
+ if (action == null) {
+ // -> switch to default below
+ action = "";
+ }
switch (action) {
case ACTION_IMPORT_KEY: {
- /* Keychain's own Actions */
- startFileFragment(savedInstanceState);
-
if (dataUri != null) {
// action: directly load data
- startListFragment(savedInstanceState, null, dataUri, null, null);
+ startListFragment(null, dataUri, null, null);
} else if (extras.containsKey(EXTRA_KEY_BYTES)) {
byte[] importData = extras.getByteArray(EXTRA_KEY_BYTES);
// action: directly load data
- startListFragment(savedInstanceState, importData, null, null, null);
+ startListFragment(importData, null, null, null);
+ } else {
+ startTopFileFragment();
+ startListFragment(null, null, null, null);
}
break;
}
@@ -156,10 +157,6 @@ public class ImportKeysActivity extends BaseNfcActivity
case ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE:
case ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT: {
- // only used for OpenPgpService
- if (extras.containsKey(EXTRA_PENDING_INTENT_DATA)) {
- mPendingIntentData = extras.getParcelable(EXTRA_PENDING_INTENT_DATA);
- }
if (extras.containsKey(EXTRA_QUERY) || extras.containsKey(EXTRA_KEY_ID)) {
/* simple search based on query or key id */
@@ -175,10 +172,10 @@ public class ImportKeysActivity extends BaseNfcActivity
if (query != null && query.length() > 0) {
// display keyserver fragment with query
- startCloudFragment(savedInstanceState, query, false, null);
+ startTopCloudFragment(query, false, null);
// action: search immediately
- startListFragment(savedInstanceState, null, null, query, null);
+ startListFragment(null, null, query, null);
} else {
Log.e(Constants.TAG, "Query is empty!");
return;
@@ -194,10 +191,10 @@ public class ImportKeysActivity extends BaseNfcActivity
String query = "0x" + fingerprint;
// display keyserver fragment with query
- startCloudFragment(savedInstanceState, query, true, null);
+ startTopCloudFragment(query, true, null);
// action: search immediately
- startListFragment(savedInstanceState, null, null, query, null);
+ startListFragment(null, null, query, null);
}
} else {
Log.e(Constants.TAG,
@@ -208,14 +205,6 @@ public class ImportKeysActivity extends BaseNfcActivity
}
break;
}
- case ACTION_IMPORT_KEY_FROM_FILE: {
- // NOTE: this only displays the appropriate fragment, no actions are taken
- startFileFragment(savedInstanceState);
-
- // no immediate actions!
- startListFragment(savedInstanceState, null, null, null, null);
- break;
- }
case ACTION_SEARCH_KEYSERVER_FROM_URL: {
// need to process URL to get search query and keyserver authority
String query = dataUri.getQueryParameter("search");
@@ -223,120 +212,88 @@ public class ImportKeysActivity extends BaseNfcActivity
// if query not specified, we still allow users to search the keyserver in the link
if (query == null) {
Notify.create(this, R.string.import_url_warn_no_search_parameter, Notify.LENGTH_INDEFINITE,
- Notify.Style.WARN).show(mTopFragment);
+ Notify.Style.WARN).show();
// we just set the keyserver
- startCloudFragment(savedInstanceState, null, false, keyserver);
+ startTopCloudFragment(null, false, keyserver);
// 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);
+ startListFragment(null, null, null, null);
} else {
// we allow our users to edit the query if they wish
- startCloudFragment(savedInstanceState, query, false, keyserver);
+ startTopCloudFragment(query, false, keyserver);
// search immediately
- startListFragment(savedInstanceState, null, null, query, keyserver);
+ startListFragment(null, null, query, keyserver);
}
break;
}
+ case ACTION_IMPORT_KEY_FROM_FILE:
case ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN: {
// NOTE: this only displays the appropriate fragment, no actions are taken
- startFileFragment(savedInstanceState);
-
- // no immediate actions!
- startListFragment(savedInstanceState, null, null, null, null);
+ startTopFileFragment();
+ startListFragment(null, null, null, null);
break;
}
default: {
- startCloudFragment(savedInstanceState, null, false, null);
- startListFragment(savedInstanceState, null, null, null, null);
+ startTopCloudFragment(null, false, null);
+ startListFragment(null, null, null, null);
break;
}
}
}
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+
+ // the only thing we need to take care of for restoring state is
+ // that the top layout is shown iff it contains a fragment
+ Fragment topFragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAG_TOP);
+ boolean hasTopFragment = topFragment != null;
+ findViewById(R.id.import_keys_top_layout).setVisibility(hasTopFragment ? View.VISIBLE : View.GONE);
+ }
/**
* if the fragment is started with non-null bytes/dataUri/serverQuery, it will immediately
* load content
*
- * @param savedInstanceState
* @param bytes bytes containing list of keyrings to import
* @param dataUri uri to file to import keyrings from
* @param serverQuery query to search for on the keyserver
* @param keyserver keyserver authority to search on. If null will use keyserver from
* user preferences
*/
- private void startListFragment(Bundle savedInstanceState, byte[] bytes, Uri dataUri,
- String serverQuery, 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.
- if (mListFragment != null) {
- return;
- }
-
- mListFragment = ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery, false,
- keyserver);
-
- // Add the fragment to the 'fragment_container' FrameLayout
- // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
+ private void startListFragment(byte[] bytes, Uri dataUri, String serverQuery, String keyserver) {
+ Fragment listFragment =
+ ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery, false, keyserver);
getSupportFragmentManager().beginTransaction()
- .replace(R.id.import_keys_list_container, mListFragment)
- .commitAllowingStateLoss();
- // do it immediately!
- getSupportFragmentManager().executePendingTransactions();
+ .replace(R.id.import_keys_list_container, listFragment, TAG_FRAG_LIST)
+ .commit();
}
- private void startFileFragment(Bundle savedInstanceState) {
- // 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 (mTopFragment != null) {
- return;
- }
-
- // Create an instance of the fragment
- mTopFragment = ImportKeysFileFragment.newInstance();
-
- // Add the fragment to the 'fragment_container' FrameLayout
- // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
+ private void startTopFileFragment() {
+ findViewById(R.id.import_keys_top_layout).setVisibility(View.VISIBLE);
+ Fragment importFileFragment = ImportKeysFileFragment.newInstance();
getSupportFragmentManager().beginTransaction()
- .replace(R.id.import_keys_top_container, mTopFragment)
- .commitAllowingStateLoss();
- // do it immediately!
- getSupportFragmentManager().executePendingTransactions();
+ .replace(R.id.import_keys_top_container, importFileFragment, TAG_FRAG_TOP)
+ .commit();
}
/**
* loads the CloudFragment, which consists of the search bar, search button and settings icon
* visually.
*
- * @param savedInstanceState
* @param query search query
* @param disableQueryEdit if true, user will not be able to edit the search query
* @param keyserver keyserver authority to use for search, if null will use keyserver
* specified in user preferences
*/
-
- 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.
- if (mTopFragment != null) {
- return;
- }
-
- // Create an instance of the fragment
- mTopFragment = ImportKeysCloudFragment.newInstance(query, disableQueryEdit, keyserver);
-
- // Add the fragment to the 'fragment_container' FrameLayout
- // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
+ private void startTopCloudFragment(String query, boolean disableQueryEdit, String keyserver) {
+ findViewById(R.id.import_keys_top_layout).setVisibility(View.VISIBLE);
+ Fragment importCloudFragment = ImportKeysCloudFragment.newInstance(query, disableQueryEdit, keyserver);
getSupportFragmentManager().beginTransaction()
- .replace(R.id.import_keys_top_container, mTopFragment)
- .commitAllowingStateLoss();
- // do it immediately!
- getSupportFragmentManager().executePendingTransactions();
+ .replace(R.id.import_keys_top_container, importCloudFragment, TAG_FRAG_TOP)
+ .commit();
}
private boolean isFingerprintValid(String fingerprint) {
@@ -350,63 +307,32 @@ public class ImportKeysActivity extends BaseNfcActivity
}
public void loadCallback(final ImportKeysListFragment.LoaderState loaderState) {
- mListFragment.loadNew(loaderState);
+ FragmentManager fragMan = getSupportFragmentManager();
+ ImportKeysListFragment keyListFragment = (ImportKeysListFragment) fragMan.findFragmentByTag(TAG_FRAG_LIST);
+ keyListFragment.loadNew(loaderState);
}
- private void handleMessage(Message message) {
- if (message.arg1 == ServiceProgressHandler.MessageStatus.OKAY.ordinal()) {
- // get returned data bundle
- Bundle returnData = message.getData();
- if (returnData == null) {
- return;
- }
- final ImportKeyResult result =
- returnData.getParcelable(OperationResult.EXTRA_RESULT);
- if (result == null) {
- Log.e(Constants.TAG, "result == null");
- return;
- }
+ private void importSelectedKeys() {
- 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();
- intent.putExtra(ImportKeyResult.EXTRA_RESULT, result);
- ImportKeysActivity.this.setResult(RESULT_OK, intent);
- ImportKeysActivity.this.finish();
- return;
- }
- if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(getIntent().getAction())) {
- ImportKeysActivity.this.setResult(RESULT_OK, mPendingIntentData);
- ImportKeysActivity.this.finish();
- return;
- }
-
- result.createNotify(ImportKeysActivity.this)
- .show((ViewGroup) findViewById(R.id.import_snackbar));
- }
- }
+ FragmentManager fragMan = getSupportFragmentManager();
+ ImportKeysListFragment keyListFragment = (ImportKeysListFragment) fragMan.findFragmentByTag(TAG_FRAG_LIST);
- /**
- * Import keys with mImportData
- */
- public void importKeys() {
-
- if (mListFragment.getSelectedEntries().size() == 0) {
+ if (keyListFragment.getSelectedEntries().size() == 0) {
Notify.create(this, R.string.error_nothing_import_selected, Notify.Style.ERROR)
.show((ViewGroup) findViewById(R.id.import_snackbar));
return;
}
- mOperationHelper = new CryptoOperationHelper<ImportKeyringParcel, ImportKeyResult>(
+ mOperationHelper = new CryptoOperationHelper<>(
1, this, this, R.string.progress_importing
);
- ImportKeysListFragment.LoaderState ls = mListFragment.getLoaderState();
+ ImportKeysListFragment.LoaderState ls = keyListFragment.getLoaderState();
if (ls instanceof ImportKeysListFragment.BytesLoaderState) {
Log.d(Constants.TAG, "importKeys started");
// get DATA from selected key entries
- IteratorWithSize<ParcelableKeyRing> selectedEntries = mListFragment.getSelectedData();
+ IteratorWithSize<ParcelableKeyRing> selectedEntries = keyListFragment.getSelectedData();
// instead of giving the entries by Intent extra, cache them into a
// file to prevent Java Binder problems on heavy imports
@@ -435,7 +361,7 @@ public class ImportKeysActivity extends BaseNfcActivity
ArrayList<ParcelableKeyRing> keys = new ArrayList<>();
{
// change the format into ParcelableKeyRing
- ArrayList<ImportKeysListEntry> entries = mListFragment.getSelectedEntries();
+ ArrayList<ImportKeysListEntry> entries = keyListFragment.getSelectedEntries();
for (ImportKeysListEntry entry : entries) {
keys.add(new ParcelableKeyRing(
entry.getFingerprintHex(), entry.getKeyIdHex(), entry.getExtraData())
@@ -458,24 +384,28 @@ public class ImportKeysActivity extends BaseNfcActivity
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (mOperationHelper == null ||
- !mOperationHelper.handleActivityResult(requestCode, resultCode, data)) {
- super.onActivityResult(requestCode, resultCode, data);
+ if (mOperationHelper != null &&
+ mOperationHelper.handleActivityResult(requestCode, resultCode, data)) {
+ return;
}
+ super.onActivityResult(requestCode, resultCode, data);
}
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())) {
+ String intentAction = getIntent().getAction();
+
+ if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT.equals(intentAction)
+ || ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN.equals(intentAction)) {
Intent intent = new Intent();
intent.putExtra(ImportKeyResult.EXTRA_RESULT, result);
- ImportKeysActivity.this.setResult(RESULT_OK, intent);
- ImportKeysActivity.this.finish();
+ setResult(RESULT_OK, intent);
+ finish();
return;
}
- if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(getIntent().getAction())) {
- ImportKeysActivity.this.setResult(RESULT_OK, mPendingIntentData);
- ImportKeysActivity.this.finish();
+
+ if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_TO_SERVICE.equals(intentAction)) {
+ setResult(RESULT_OK, mPendingIntentData);
+ finish();
return;
}
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 6f5d98afd..a5bd84d7e 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/MainActivity.java
@@ -204,7 +204,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
private void onBackupSelected() {
mToolbar.setTitle(R.string.nav_backup);
mDrawer.setSelectionByIdentifier(ID_APPS, false);
- Fragment frag = new BackupFragment();
+ Fragment frag = new DrawerBackupFragment();
setFragment(frag, true);
}
@@ -265,7 +265,7 @@ public class MainActivity extends BaseNfcActivity implements FabContainer, OnBac
} else if (frag instanceof AppsListFragment) {
mToolbar.setTitle(R.string.nav_apps);
mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_APPS), false);
- } else if (frag instanceof BackupFragment) {
+ } else if (frag instanceof DrawerBackupFragment) {
mToolbar.setTitle(R.string.nav_backup);
mDrawer.setSelection(mDrawer.getPositionFromIdentifier(ID_BACKUP), false);
}
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 0415128a2..f38e4928d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UploadKeyActivity.java
@@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.ui;
+
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -29,10 +30,12 @@ import android.widget.Spinner;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
-import org.sufficientlysecure.keychain.operations.results.ExportResult;
+import org.sufficientlysecure.keychain.operations.results.UploadResult;
+import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
-import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.service.UploadKeyringParcel;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.util.Log;
@@ -42,7 +45,7 @@ import org.sufficientlysecure.keychain.util.Preferences;
* Sends the selected public key to a keyserver
*/
public class UploadKeyActivity extends BaseActivity
- implements CryptoOperationHelper.Callback<ExportKeyringParcel, ExportResult> {
+ implements CryptoOperationHelper.Callback<UploadKeyringParcel, UploadResult> {
private View mUploadButton;
private Spinner mKeyServerSpinner;
@@ -50,8 +53,8 @@ public class UploadKeyActivity extends BaseActivity
// CryptoOperationHelper.Callback vars
private String mKeyserver;
- private Uri mUnifiedKeyringUri;
- private CryptoOperationHelper<ExportKeyringParcel, ExportResult> mUploadOpHelper;
+ private long mMasterKeyId;
+ private CryptoOperationHelper<UploadKeyringParcel, UploadResult> mUploadOpHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -85,6 +88,16 @@ public class UploadKeyActivity extends BaseActivity
finish();
return;
}
+
+ try {
+ mMasterKeyId = new ProviderHelper(this).getCachedPublicKeyRing(
+ KeyRings.buildUnifiedKeyRingUri(mDataUri)).getMasterKeyId();
+ } catch (PgpKeyNotFoundException e) {
+ Log.e(Constants.TAG, "Intent data pointed to bad key!");
+ finish();
+ return;
+ }
+
}
@Override
@@ -101,13 +114,10 @@ public class UploadKeyActivity extends BaseActivity
}
private void uploadKey() {
- Uri blobUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
- mUnifiedKeyringUri = blobUri;
-
String server = (String) mKeyServerSpinner.getSelectedItem();
mKeyserver = server;
- mUploadOpHelper = new CryptoOperationHelper(1, this, this, R.string.progress_uploading);
+ mUploadOpHelper = new CryptoOperationHelper<>(1, this, this, R.string.progress_uploading);
mUploadOpHelper.cryptoOperation();
}
@@ -125,12 +135,12 @@ public class UploadKeyActivity extends BaseActivity
}
@Override
- public ExportKeyringParcel createOperationInput() {
- return new ExportKeyringParcel(mKeyserver, mUnifiedKeyringUri);
+ public UploadKeyringParcel createOperationInput() {
+ return new UploadKeyringParcel(mKeyserver, mMasterKeyId);
}
@Override
- public void onCryptoOperationSuccess(ExportResult result) {
+ public void onCryptoOperationSuccess(UploadResult result) {
result.createNotify(this).show();
}
@@ -140,7 +150,7 @@ public class UploadKeyActivity extends BaseActivity
}
@Override
- public void onCryptoOperationError(ExportResult result) {
+ public void onCryptoOperationError(UploadResult result) {
result.createNotify(this).show();
}
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 de859724b..48edbcbd6 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java
@@ -65,12 +65,14 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.ViewKeyFragment.PostponeType;
import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
@@ -345,7 +347,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
startActivity(homeIntent);
return true;
}
- case R.id.menu_key_view_export_file: {
+ case R.id.menu_key_view_backup: {
startPassphraseActivity(REQUEST_BACKUP);
return true;
}
@@ -395,7 +397,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
MenuItem editKey = menu.findItem(R.id.menu_key_view_edit);
editKey.setVisible(mIsSecret);
- MenuItem exportKey = menu.findItem(R.id.menu_key_view_export_file);
+ MenuItem exportKey = menu.findItem(R.id.menu_key_view_backup);
exportKey.setVisible(mIsSecret);
MenuItem addLinked = menu.findItem(R.id.menu_key_view_add_linked_identity);
@@ -450,15 +452,40 @@ public class ViewKeyActivity extends BaseNfcActivity implements
}
private void startPassphraseActivity(int requestCode) {
- Intent intent = new Intent(this, PassphraseDialogActivity.class);
- intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mMasterKeyId);
- startActivityForResult(intent, requestCode);
+
+ if (keyHasPassphrase()) {
+ Intent intent = new Intent(this, PassphraseDialogActivity.class);
+ intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, mMasterKeyId);
+ startActivityForResult(intent, requestCode);
+ } else {
+ startBackupActivity();
+ }
+ }
+
+ private boolean keyHasPassphrase() {
+ try {
+ SecretKeyType secretKeyType =
+ mProviderHelper.getCachedPublicKeyRing(mMasterKeyId).getSecretKeyType(mMasterKeyId);
+ switch (secretKeyType) {
+ // all of these make no sense to ask
+ case PASSPHRASE_EMPTY:
+ case GNU_DUMMY:
+ case DIVERT_TO_CARD:
+ case UNAVAILABLE:
+ return false;
+ default:
+ return true;
+ }
+ } catch (NotFoundException e) {
+ return false;
+ }
}
- private void backupToFile() {
- new ExportHelper(this).showExportKeysDialog(
- mMasterKeyId, new File(Constants.Path.APP_DIR,
- KeyFormattingUtils.convertKeyIdToHex(mMasterKeyId) + ".sec.asc"), true);
+ private void startBackupActivity() {
+ Intent intent = new Intent(this, BackupActivity.class);
+ intent.putExtra(BackupActivity.EXTRA_MASTER_KEY_IDS, new long[] { mMasterKeyId });
+ intent.putExtra(BackupActivity.EXTRA_SECRET, true);
+ startActivity(intent);
}
private void deleteKey() {
@@ -514,7 +541,7 @@ public class ViewKeyActivity extends BaseNfcActivity implements
return;
}
- backupToFile();
+ startBackupActivity();
return;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java
index 44323543f..a320ea3b2 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/linked/LinkedIdCreateHttpsStep2Fragment.java
@@ -133,10 +133,7 @@ public class LinkedIdCreateHttpsStep2Fragment extends LinkedIdCreateFinalFragmen
String targetName = "pgpkey.txt";
// TODO: not supported on Android < 4.4
- FileHelper.saveDocument(this,
- targetName,
- "text/plain",
- REQUEST_CODE_OUTPUT);
+ FileHelper.saveDocument(this, targetName, "text/plain", REQUEST_CODE_OUTPUT);
}
private void saveFile(Uri uri) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java
index 7dfd56430..71f6ecc1a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/Notify.java
@@ -37,7 +37,7 @@ import org.sufficientlysecure.keychain.util.FabContainer;
*/
public class Notify {
- public static enum Style {
+ public enum Style {
OK (R.color.android_green_light), WARN(R.color.android_orange_light), ERROR(R.color.android_red_light);
public final int mLineColor;
@@ -142,6 +142,11 @@ public class Notify {
return create(activity, text, LENGTH_LONG, style);
}
+ public static Showable create(Activity activity, int textResId, Style style,
+ ActionListener actionListener, int actionResId) {
+ return create(activity, textResId, LENGTH_LONG, style, actionListener, actionResId);
+ }
+
public static Showable create(Activity activity, int textResId, int duration, Style style,
ActionListener actionListener, int actionResId) {
return create(activity, activity.getString(textResId), duration, style, actionListener, actionResId);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java
index 18e830139..a8274e45a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/ToolableViewAnimator.java
@@ -31,6 +31,7 @@ import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
+import android.view.animation.Animation;
import android.widget.ViewAnimator;
import org.sufficientlysecure.keychain.R;
@@ -73,4 +74,29 @@ public class ToolableViewAnimator extends ViewAnimator {
}
super.addView(child, index, params);
}
+
+ @Override
+ public void setDisplayedChild(int whichChild) {
+ if (whichChild != getDisplayedChild()) {
+ super.setDisplayedChild(whichChild);
+ }
+ }
+
+ public void setDisplayedChild(int whichChild, boolean animate) {
+ if (animate) {
+ setDisplayedChild(whichChild);
+ return;
+ }
+
+ Animation savedInAnim = getInAnimation();
+ Animation savedOutAnim = getOutAnimation();
+ setInAnimation(null);
+ setOutAnimation(null);
+
+ setDisplayedChild(whichChild);
+
+ setInAnimation(savedInAnim);
+ setOutAnimation(savedOutAnim);
+ }
+
}