Simplify backup code fragment

This commit is contained in:
Vincent Breitmoser 2018-05-08 15:14:19 +02:00
parent 32005b94d9
commit cb6913f6dd
4 changed files with 108 additions and 534 deletions

View file

@ -24,32 +24,19 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
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.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
@ -60,48 +47,39 @@ import org.sufficientlysecure.keychain.service.BackupKeyringParcel;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
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.Numeric9x4PassphraseUtil;
import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.Numeric9x4PassphraseUtil;
import org.sufficientlysecure.keychain.util.Passphrase;
public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringParcel, ExportResult>
implements OnBackStackChangedListener {
public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringParcel, ExportResult> {
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_EXECUTE_BACKUP_OPERATION = "execute_backup_operation";
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;
Passphrase mBackupCode;
private boolean mExecuteBackupOperation;
private TextView[] mCodeEditText;
private ToolableViewAnimator mStatusAnimator, mTitleAnimator, mCodeFieldsAnimator;
private Integer mBackStackLevel;
private Uri mCachedBackupUri;
private boolean mShareNotSave;
private boolean mDebugModeAcceptAnyCode;
private View buttonSave;
private View buttonShare;
private View buttonExport;
public static BackupCodeFragment newInstance(long[] masterKeyIds, boolean exportSecret,
boolean executeBackupOperation) {
BackupCodeFragment frag = new BackupCodeFragment();
Passphrase backupCode = Numeric9x4PassphraseUtil.generateNumeric9x4Passphrase();
Bundle args = new Bundle();
args.putParcelable(ARG_BACKUP_CODE, Numeric9x4PassphraseUtil.generateNumeric9x4Passphrase());
args.putParcelable(ARG_BACKUP_CODE, backupCode);
args.putLongArray(ARG_MASTER_KEY_IDS, masterKeyIds);
args.putBoolean(ARG_EXPORT_SECRET, exportSecret);
args.putBoolean(ARG_EXECUTE_BACKUP_OPERATION, executeBackupOperation);
@ -110,142 +88,8 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
return frag;
}
enum BackupCodeState {
STATE_UNINITIALIZED, STATE_DISPLAY, STATE_INPUT, STATE_INPUT_ERROR, STATE_OK
}
BackupCodeState mCurrentState = BackupCodeState.STATE_UNINITIALIZED;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Constants.DEBUG) {
setHasOptionsMenu(true);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
if (Constants.DEBUG) {
inflater.inflate(R.menu.backup_fragment_debug_menu, menu);
}
}
@SuppressLint("SetTextI18n")
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (Constants.DEBUG && item.getItemId() == R.id.debug_accept_any_log) {
boolean newCheckedState = !item.isChecked();
item.setChecked(newCheckedState);
mDebugModeAcceptAnyCode = newCheckedState;
if (newCheckedState && TextUtils.isEmpty(mCodeEditText[0].getText())) {
mCodeEditText[0].setText("1234");
mCodeEditText[1].setText("5678");
mCodeEditText[2].setText("9012");
mCodeEditText[3].setText("3456");
mCodeEditText[4].setText("7890");
mCodeEditText[5].setText("1234");
mCodeEditText[6].setText("5678");
mCodeEditText[7].setText("9012");
mCodeEditText[8].setText("3456");
Notify.create(getActivity(), "Actual backup code is all '1's", Style.WARN).show();
}
return true;
}
return super.onOptionsItemSelected(item);
}
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 (TextView 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);
mCodeFieldsAnimator.setDisplayedChild(1, false);
if (mExecuteBackupOperation) {
mStatusAnimator.setDisplayedChild(3, animate);
} else {
mStatusAnimator.setDisplayedChild(1, animate);
}
hideKeyboard();
for (TextView 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();
// special case for remote API, see RemoteBackupActivity
if (!mExecuteBackupOperation) {
// wait for animation to finish...
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
startBackup();
}
}, 2000);
}
break;
}
}
mCurrentState = state;
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.backup_code_fragment, container, false);
Bundle args = getArguments();
@ -254,8 +98,6 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
mExportSecret = args.getBoolean(ARG_EXPORT_SECRET);
mExecuteBackupOperation = args.getBoolean(ARG_EXECUTE_BACKUP_OPERATION, true);
mCodeEditText = getTransferCodeTextViews(view, R.id.transfer_code_input);
{
TextView[] codeDisplayText = getTransferCodeTextViews(view, R.id.transfer_code_display);
@ -272,53 +114,34 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
}
}
setupEditTextFocusNext(mCodeEditText);
setupEditTextSuccessListener(mCodeEditText);
buttonSave = view.findViewById(R.id.button_backup_save);
buttonShare = view.findViewById(R.id.button_backup_share);
buttonExport = view.findViewById(R.id.button_backup_export);
mStatusAnimator = view.findViewById(R.id.button_bar_animator);
mTitleAnimator = view.findViewById(R.id.title_animator);
mCodeFieldsAnimator = 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) {
if (mExecuteBackupOperation) {
buttonSave.setOnClickListener(v -> {
mShareNotSave = false;
startBackup();
}
});
});
view.findViewById(R.id.button_backup_share).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
buttonShare.setOnClickListener(v -> {
mShareNotSave = true;
startBackup();
}
});
});
} else {
view.findViewById(R.id.button_bar_backup).setVisibility(View.GONE);
buttonExport.setVisibility(View.VISIBLE);
buttonExport.setOnClickListener(v -> 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();
}
}
});
((CheckBox) view.findViewById(R.id.check_backup_code_written)).setOnCheckedChangeListener(
(buttonView, isChecked) -> {
buttonSave.setEnabled(isChecked);
buttonShare.setEnabled(isChecked);
buttonExport.setEnabled(isChecked);
});
view.findViewById(R.id.button_faq).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
showFaq();
}
});
view.findViewById(R.id.button_faq).setOnClickListener(v -> showFaq());
return view;
}
@ -342,168 +165,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
HelpActivity.startHelpActivity(getActivity(), HelpActivity.TAB_FAQ);
}
@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 TextView[] backupCodes) {
for (TextView 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() > 4) {
throw new AssertionError("max length of each field is 4!");
}
boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT
|| mCurrentState == BackupCodeState.STATE_INPUT_ERROR;
boolean partIsComplete = s.length() == 4;
if (!inInputState || !partIsComplete) {
return;
}
checkIfCodeIsCorrect();
}
});
}
}
private void checkIfCodeIsCorrect() {
if (Constants.DEBUG && mDebugModeAcceptAnyCode) {
switchState(BackupCodeState.STATE_OK, true);
return;
}
StringBuilder backupCodeInput = new StringBuilder(26);
for (TextView editText : mCodeEditText) {
if (editText.getText().length() < 4) {
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;
}
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 TextView[] 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) == 4;
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;
@ -514,15 +176,10 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
+ (mExportSecret ? Constants.FILE_EXTENSION_ENCRYPTED_BACKUP_SECRET
: Constants.FILE_EXTENSION_ENCRYPTED_BACKUP_PUBLIC);
Passphrase passphrase = new Passphrase(mBackupCode.getCharArray());
if (Constants.DEBUG && mDebugModeAcceptAnyCode) {
passphrase = new Passphrase("1111-1111-1111-1111-1111-1111-1111-1111-1111");
}
// if we don't want to execute the actual operation outside of this activity, drop out here
if (!mExecuteBackupOperation) {
((BackupActivity) getActivity()).handleBackupOperation(
CryptoInputParcel.createCryptoInputParcel(passphrase));
CryptoInputParcel.createCryptoInputParcel(mBackupCode));
return;
}
@ -530,7 +187,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
mCachedBackupUri = TemporaryFileProvider.createFile(activity, filename,
Constants.MIME_TYPE_ENCRYPTED_ALTERNATE);
cryptoOperation(CryptoInputParcel.createCryptoInputParcel(passphrase));
cryptoOperation(CryptoInputParcel.createCryptoInputParcel(mBackupCode));
return;
}
@ -565,12 +222,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar
File file = new File(Constants.Path.APP_DIR, filename);
if (!overwrite && file.exists()) {
Notify.create(activity, R.string.snack_backup_exists, Style.WARN, new ActionListener() {
@Override
public void onAction() {
saveFile(filename, true);
}
}, R.string.snack_btn_overwrite).show();
Notify.create(activity, R.string.snack_backup_exists, Style.WARN, () -> saveFile(filename, true), R.string.snack_btn_overwrite).show();
return;
}

View file

@ -1,168 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="20dp">
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:id="@+id/title_animator"
android:layout_width="match_parent"
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:inAnimation="@anim/fade_in"
android:outAnimation="@anim/fade_out"
custom:initialView="0">
android:gravity="center_horizontal"
android:padding="10dp"
android:text="@string/backup_code_explanation" />
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:padding="10dp"
android:text="@string/backup_code_explanation" />
<include layout="@layout/transfer_code_display" android:id="@+id/transfer_code_display" />
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:padding="10dp"
android:text="@string/backup_code_enter" />
<CheckBox
android:id="@+id/check_backup_code_written"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_margin="8dp"
android:text="@string/backup_code_checkbox"
android:padding="8dp"
/>
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:padding="10dp"
android:text="@string/backup_code_ok" />
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:id="@+id/code_animator"
android:layout_width="match_parent"
<LinearLayout
style="?android:buttonBarStyle"
android:id="@+id/button_bar_backup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="15dp"
android:layout_marginTop="15dp"
android:inAnimation="@anim/fade_in"
android:outAnimation="@anim/fade_out"
custom:initialView="1">
<include layout="@layout/transfer_code_display" android:id="@+id/transfer_code_display" />
<include layout="@layout/transfer_code_input" android:id="@+id/transfer_code_input" />
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:id="@+id/button_bar_animator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:inAnimation="@anim/fade_in_delayed"
android:outAnimation="@anim/fade_out"
custom:initialView="2">
<Button
android:id="@+id/button_backup_input"
style="?android:buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="10dp"
android:drawableLeft="@drawable/ic_mode_edit_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_code_wrotedown" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="vertical">
<TextView
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="@string/backup_code_wrong" />
<Button
android:id="@+id/button_backup_back"
style="?android:buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="10dp"
android:drawableLeft="@drawable/ic_repeat_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_backup_back" />
</LinearLayout>
android:orientation="vertical">
<LinearLayout
style="?android:buttonBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="vertical">
<LinearLayout
style="?android:buttonBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
<Button
android:id="@+id/button_backup_share"
style="?android:buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:drawableLeft="@drawable/ic_share_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_backup_share" />
<Button
android:id="@+id/button_backup_save"
style="?android:buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:drawableLeft="@drawable/ic_save_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_backup_save" />
</LinearLayout>
android:layout_gravity="center_horizontal">
<Button
android:id="@+id/button_faq"
android:id="@+id/button_backup_share"
style="?android:buttonBarButtonStyle"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/how_to_import" />
android:layout_margin="10dp"
android:layout_weight="1"
android:drawableLeft="@drawable/ic_share_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_backup_share"
android:enabled="false"
/>
<Button
android:id="@+id/button_backup_save"
style="?android:buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight="1"
android:drawableLeft="@drawable/ic_save_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_backup_save"
android:enabled="false"
/>
</LinearLayout>
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
<Button
android:id="@+id/button_faq"
style="?android:buttonBarButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/how_to_import" />
</LinearLayout>
<Button
android:id="@+id/button_backup_export"
style="?android:buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_gravity="center_horizontal"
android:drawableLeft="@drawable/ic_share_grey_24dp"
android:drawablePadding="8dp"
android:padding="12dp"
android:text="@string/btn_backup_export"
android:enabled="false"
android:visibility="gone"
/>
</LinearLayout>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/debug_accept_any_log"
android:title="Debug / Accept any code"
android:checkable="true"
/>
</menu>

View file

@ -1722,6 +1722,7 @@
<string name="backup_code_wrong">"The backup code you entered is wrong!\nDid you write it down correctly?"</string>
<string name="btn_backup_share">"Share backup"</string>
<string name="btn_backup_save">"Save backup"</string>
<string name="btn_backup_export">"Export backup"</string>
<string name="snack_backup_error_saving">"Error saving backup!"</string>
<string name="snack_backup_saved">"Backup saved"</string>
<string name="snack_backup_exists">"Backup already exists!"</string>
@ -2015,4 +2016,5 @@
<string name="key_import_text">To use an end-to-end key, it has to be imported into OpenKeychain.</string>
<string name="key_import_text_autocrypt_setup_msg">To import your existing setup from another device, you can also open an Autocrypt Setup Message in %s.</string>
<string name="button_goto_openkeychain">Go to OpenKeychain</string>
<string name="backup_code_checkbox">I have written down this backup code. Without it, I will be unable to restore the backup.</string>
</resources>