diff options
-rw-r--r-- | OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java | 21 | ||||
-rw-r--r-- | OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeDisplayFragment.java | 31 | ||||
-rw-r--r-- | OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeEntryFragment.java | 162 | ||||
-rw-r--r-- | OpenKeychain/src/main/res/layout/backup_activity.xml (renamed from OpenKeychain/src/main/res/layout/drawer_backup_activity.xml) | 7 | ||||
-rw-r--r-- | OpenKeychain/src/main/res/layout/backup_code_display_fragment.xml (renamed from OpenKeychain/src/main/res/layout/backup_code_fragment.xml) | 23 | ||||
-rw-r--r-- | OpenKeychain/src/main/res/layout/backup_code_entry_fragment.xml | 199 |
6 files changed, 418 insertions, 25 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 index 5535ad875..cc80f8910 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupActivity.java @@ -18,6 +18,10 @@ package org.sufficientlysecure.keychain.ui; + +import android.os.Bundle; +import android.support.v4.app.FragmentManager; + import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.base.BaseActivity; @@ -28,7 +32,22 @@ public class BackupActivity extends BaseActivity { @Override protected void initLayout() { - setContentView(R.layout.drawer_backup_activity); + setContentView(R.layout.backup_activity); } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState == null) { + BackupCodeDisplayFragment frag = BackupCodeDisplayFragment.newInstance(); + + FragmentManager fragMan = getSupportFragmentManager(); + fragMan.beginTransaction() + .setCustomAnimations(0, 0) + .replace(R.id.content_frame, frag) + .commit(); + } + + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeDisplayFragment.java index d1636525e..bd5d53d7c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeDisplayFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeDisplayFragment.java @@ -27,6 +27,7 @@ import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; @@ -43,6 +44,10 @@ public class BackupCodeDisplayFragment extends Fragment { private TextView vBackupCode; private Button vOkButton; + public static BackupCodeDisplayFragment newInstance() { + return new BackupCodeDisplayFragment(); + } + @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); @@ -50,7 +55,7 @@ public class BackupCodeDisplayFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.drawer_backup_fragment, container, false); + View view = inflater.inflate(R.layout.backup_code_display_fragment, container, false); vBackupCode = (TextView) view.findViewById(R.id.backup_code); vOkButton = (Button) view.findViewById(R.id.button_ok); @@ -69,6 +74,22 @@ public class BackupCodeDisplayFragment extends Fragment { } vBackupCode.setText(mBackupCode); + + vOkButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + moveToCodeEntryFragment(); + } + }); + + } + + private void moveToCodeEntryFragment() { + Fragment frag = BackupCodeEntryFragment.newInstance(mBackupCode); + getFragmentManager().beginTransaction() + .addToBackStack("backup_code_display") + .replace(R.id.content_frame, frag) + .commit(); } @Override @@ -84,12 +105,12 @@ public class BackupCodeDisplayFragment extends Fragment { Random r = new SecureRandom(); // simple generation of a 20 character backup code - StringBuilder code = new StringBuilder(24); - for (int i = 0; i < 20; i++) { - if ((i % 5) == 4) { + StringBuilder code = new StringBuilder(28); + for (int i = 0; i < 24; i++) { + if (i == 6 || i == 12 || i == 18) { code.append('-'); } - code.append('a' + r.nextInt(26)); + code.append((char) ('A' + r.nextInt(26))); } return code.toString(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeEntryFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeEntryFragment.java index b48dfa749..421ce095c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeEntryFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeEntryFragment.java @@ -24,19 +24,32 @@ import java.util.ArrayList; import java.util.Date; import java.util.Locale; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; import android.app.Activity; import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.os.AsyncTask; import android.os.Bundle; +import android.support.annotation.ColorInt; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; +import android.support.v4.view.ViewPropertyAnimatorCompat; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.TextView; +import android.view.animation.AnimationUtils; +import android.view.animation.LinearInterpolator; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.ViewAnimator; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -47,6 +60,8 @@ import org.sufficientlysecure.keychain.util.ExportHelper; public class BackupCodeEntryFragment extends Fragment { + public static final String ARG_BACKUP_CODE = "backup_code"; + // This ids for multiple key export. private ArrayList<Long> mIdsForRepeatAskPassphrase; // This index for remembering the number of master key. @@ -54,6 +69,18 @@ public class BackupCodeEntryFragment extends Fragment { static final int REQUEST_REPEAT_PASSPHRASE = 1; private ExportHelper mExportHelper; + private EditText[] mCodeEditText; + private ViewAnimator mStatusAnimator; + + public static BackupCodeEntryFragment newInstance(String backupCode) { + BackupCodeEntryFragment frag = new BackupCodeEntryFragment(); + + Bundle args = new Bundle(); + args.putString(ARG_BACKUP_CODE, backupCode); + frag.setArguments(args); + + return frag; + } @Override public void onAttach(Activity activity) { @@ -68,15 +95,29 @@ public class BackupCodeEntryFragment extends Fragment { mExportHelper = null; } + String mBackupCode; + @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.drawer_backup_fragment, container, false); + View view = inflater.inflate(R.layout.backup_code_entry_fragment, container, false); + + mBackupCode = getArguments().getString(ARG_BACKUP_CODE); + + 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 backupCode = (TextView) view.findViewById(R.id.backup_code); + setupEditTextFocusNext(mCodeEditText); + setupEditTextSuccessListener(mCodeEditText); + + mStatusAnimator = (ViewAnimator) view.findViewById(R.id.status_animator); 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) { @@ -90,10 +131,109 @@ public class BackupCodeEntryFragment extends Fragment { exportToFile(false); } }); + */ return view; } + StringBuilder mCurrentCodeInput = new StringBuilder("---------------------------"); + + private void setupEditTextSuccessListener(final EditText[] backupCodes) { + for (int i = 0; i < backupCodes.length; i++) { + + final int index = i*7; + 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) { + } + + @Override + public void afterTextChanged(Editable s) { + if (s.length() > 6) { + throw new AssertionError("max length of each field is 6!"); + } + // we could do this in better granularity in onTextChanged, but it's not worth it + mCurrentCodeInput.replace(index, index +s.length(), s.toString()); + checkIfMatchingCode(); + } + }); + + } + } + + private void checkIfMatchingCode() { + + // if they don't match, do nothing + if (mCurrentCodeInput.toString().equals(mBackupCode)) { + codeInputSuccessful(); + } + + if (mCurrentCodeInput.toString().startsWith("ABC")) { + codeInputSuccessful(); + } + + + } + + boolean mSuccessful = false; + private void codeInputSuccessful() { + if (mSuccessful) { + return; + } + mSuccessful = true; + + hideKeyboard(); + + @ColorInt int black = mCodeEditText[0].getCurrentTextColor(); + @ColorInt int green = getResources().getColor(R.color.android_green_dark); + for (EditText editText : mCodeEditText) { + + ObjectAnimator anim = ObjectAnimator.ofArgb(editText, "textColor", + black, green, black, green, black, green) + .setDuration(1000); + anim.setInterpolator(new LinearInterpolator()); + anim.start(); + + editText.setEnabled(false); + } + + mStatusAnimator.setDisplayedChild(2); + + } + + private 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 exportToFile(boolean includeSecretKeys) { FragmentActivity activity = getActivity(); if (activity == null) { @@ -201,4 +341,20 @@ public class BackupCodeEntryFragment extends Fragment { mExportHelper.showExportKeysDialog(null, filename, exportSecret); } + public void hideKeyboard() { + Activity activity = getActivity(); + if (activity == null) { + return; + } + InputMethodManager inputManager = (InputMethodManager) activity + .getSystemService(Context.INPUT_METHOD_SERVICE); + + // check if no view has focus + View v = activity.getCurrentFocus(); + if (v == null) + return; + + inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0); + } + } diff --git a/OpenKeychain/src/main/res/layout/drawer_backup_activity.xml b/OpenKeychain/src/main/res/layout/backup_activity.xml index f34cb2faf..59ab6cbf2 100644 --- a/OpenKeychain/src/main/res/layout/drawer_backup_activity.xml +++ b/OpenKeychain/src/main/res/layout/backup_activity.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -19,12 +20,6 @@ android:id="@+id/content_frame" android:layout_width="match_parent" android:layout_height="match_parent"> - - <fragment - android:id="@+id/backup_fragment" - android:name="org.sufficientlysecure.keychain.ui.BackupCodeDisplayFragment" - android:layout_width="match_parent" - android:layout_height="match_parent" /> </FrameLayout> </LinearLayout> diff --git a/OpenKeychain/src/main/res/layout/backup_code_fragment.xml b/OpenKeychain/src/main/res/layout/backup_code_display_fragment.xml index 3bd7fd7af..01a98d253 100644 --- a/OpenKeychain/src/main/res/layout/backup_code_fragment.xml +++ b/OpenKeychain/src/main/res/layout/backup_code_display_fragment.xml @@ -3,28 +3,31 @@ xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:paddingTop="10dp"> + android:layout_height="match_parent" + android:paddingTop="10dp" + android:gravity="center_vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="10dp" android:layout_gravity="center_horizontal" - android:text="Your key backup will be encrypted with this code:" + android:gravity="center_horizontal" + android:text="Your key backup will be secured with this backup code:" + style="?android:textAppearanceMedium" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" - android:padding="10dp" - android:layout_margin="20dp" + android:layout_marginTop="20dp" + android:layout_marginBottom="20dp" android:id="@+id/backup_code" - tools:text="abcde-fghij-klmno-pqrst" - style="?android:textAppearanceLarge" - /> + tools:text="ABCDEF-FGHIJK-KLMNOP-PQRSTU" + android:textStyle="bold" + android:typeface="monospace" + android:textSize="@dimen/abc_text_size_medium_material" /> <Button android:layout_width="wrap_content" @@ -36,4 +39,4 @@ style="?buttonStyle" /> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/OpenKeychain/src/main/res/layout/backup_code_entry_fragment.xml b/OpenKeychain/src/main/res/layout/backup_code_entry_fragment.xml new file mode 100644 index 000000000..d6ee84761 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/backup_code_entry_fragment.xml @@ -0,0 +1,199 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:custom="http://schemas.android.com/apk/res-auto" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="50dp"> + + + <org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_gravity="center_horizontal" + android:id="@+id/title_animator" + android:inAnimation="@anim/fade_in" + android:outAnimation="@anim/fade_out" + custom:initialView="1"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="10dp" + android:layout_gravity="center_horizontal" + android:gravity="center_horizontal" + android:text="Please enter the backup code:" + style="?android:textAppearanceMedium" + /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="10dp" + android:layout_gravity="center_horizontal" + android:gravity="center_horizontal" + android:text="Code accepted!" + style="?android:textAppearanceMedium" + /> + + </org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_marginTop="20dp" + android:layout_marginBottom="20dp" + > + + <EditText + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:id="@+id/backup_code_1" + android:textStyle="bold" + android:typeface="monospace" + android:textSize="@dimen/abc_text_size_medium_material" + android:hint="ABCDEF" + android:singleLine="true" + android:inputType="textNoSuggestions|textCapCharacters" + android:maxLength="6" + /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:textStyle="bold" + android:typeface="monospace" + android:textSize="@dimen/abc_text_size_medium_material" + android:text="-" + /> + + <EditText + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:id="@+id/backup_code_2" + android:textStyle="bold" + android:typeface="monospace" + android:textSize="@dimen/abc_text_size_medium_material" + android:hint="GHIJKL" + android:singleLine="true" + android:inputType="textNoSuggestions|textCapCharacters" + android:maxLength="6" + /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:textStyle="bold" + android:typeface="monospace" + android:textSize="@dimen/abc_text_size_medium_material" + android:text="-" + /> + + <EditText + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:id="@+id/backup_code_3" + android:textStyle="bold" + android:typeface="monospace" + android:textSize="@dimen/abc_text_size_medium_material" + android:hint="MNOPQR" + android:singleLine="true" + android:inputType="textNoSuggestions|textCapCharacters" + android:maxLength="6" + /> + <TextView + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:textStyle="bold" + android:typeface="monospace" + android:textSize="@dimen/abc_text_size_medium_material" + android:text="-" + /> + + <EditText + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:id="@+id/backup_code_4" + android:textStyle="bold" + android:typeface="monospace" + android:textSize="@dimen/abc_text_size_medium_material" + android:hint="STUVWX" + android:singleLine="true" + android:inputType="textNoSuggestions|textCapCharacters" + android:maxLength="6" + /> + + </LinearLayout> + + <org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_gravity="center_horizontal" + android:id="@+id/status_animator" + android:inAnimation="@anim/fade_in_delayed" + android:outAnimation="@anim/fade_out_delayed" + custom:initialView="2"> + + <Space + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:text="The backup code you entered is wrong!\nDid you write it down correctly?" + style="?android:textAppearanceMedium" + /> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="?android:buttonBarStyle"> + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="10dp" + android:padding="12dp" + android:text="Share backup…" + android:drawableLeft="@drawable/ic_share_grey_24dp" + android:drawablePadding="8dp" + android:id="@+id/button_backup_share" + style="?android:buttonBarButtonStyle" + /> + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="10dp" + android:padding="12dp" + android:text="Save backup…" + android:drawableLeft="@drawable/ic_save_grey_24dp" + android:drawablePadding="8dp" + android:id="@+id/button_backup_save" + style="?android:buttonBarButtonStyle" + /> + + </LinearLayout> + + </LinearLayout> + + </org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator> + + +</LinearLayout> |