From cc47c71059c3a2ad522d21260c7f006152531e07 Mon Sep 17 00:00:00 2001 From: Philipp Crocoll Date: Tue, 15 Sep 2015 21:45:04 +0200 Subject: [PATCH] Improved password + QuickUnlock screens by introducing AdjustResize (now working with design lib 22.2.1.0) --- src/keepass2android/FixedDrawerLayout.cs | 88 ++ .../MeasuringRelativeLayout.cs | 64 ++ src/keepass2android/PasswordActivity.cs | 146 +++- .../Properties/AndroidManifest_debug.xml | 2 +- .../Properties/AndroidManifest_net.xml | 2 +- src/keepass2android/QuickUnlock.cs | 22 +- .../Resources/layout/QuickUnlock.xml | 19 +- .../Resources/layout/password.xml | 752 +++++++++--------- src/keepass2android/keepass2android.csproj | 9 +- 9 files changed, 650 insertions(+), 454 deletions(-) create mode 100644 src/keepass2android/FixedDrawerLayout.cs create mode 100644 src/keepass2android/MeasuringRelativeLayout.cs diff --git a/src/keepass2android/FixedDrawerLayout.cs b/src/keepass2android/FixedDrawerLayout.cs new file mode 100644 index 00000000..3473ae07 --- /dev/null +++ b/src/keepass2android/FixedDrawerLayout.cs @@ -0,0 +1,88 @@ +using System; +using Android.Content; +using Android.Graphics; +using Android.OS; +using Android.Runtime; +using Android.Util; + +namespace keepass2android +{ + public class FixedDrawerLayout : Android.Support.V4.Widget.DrawerLayout + { + private bool _fitsSystemWindows; + + protected FixedDrawerLayout(IntPtr javaReference, JniHandleOwnership transfer) + : base(javaReference, transfer) + { + } + + public FixedDrawerLayout(Context context, IAttributeSet attrs, int defStyle) + : base(context, attrs, defStyle) + { + } + + public FixedDrawerLayout(Context context, IAttributeSet attrs) + : base(context, attrs) + { + } + + public FixedDrawerLayout(Context context) + : base(context) + { + } + + private int[] mInsets = new int[4]; + + protected override bool FitSystemWindows(Rect insets) + { + if (Build.VERSION.SdkInt >= Build.VERSION_CODES.Kitkat) + { + // Intentionally do not modify the bottom inset. For some reason, + // if the bottom inset is modified, window resizing stops working. + // TODO: Figure out why. + + mInsets[0] = insets.Left; + mInsets[1] = insets.Top; + mInsets[2] = insets.Right; + + insets.Left = 0; + insets.Top = 0; + insets.Right = 0; + } + + return base.FitSystemWindows(insets); + + } + public int[] GetInsets() + { + return mInsets; + } + + public struct MeasureArgs + { + public int ActualHeight; + public int ProposedHeight; + + } + + public event EventHandler MeasureEvent; + + protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + MeasureArgs args; + + args.ProposedHeight = MeasureSpec.GetSize(heightMeasureSpec); + args.ActualHeight = Height; + + + OnMeasureEvent(args); + base.OnMeasure(widthMeasureSpec, heightMeasureSpec); + } + + protected virtual void OnMeasureEvent(MeasureArgs args) + { + var handler = MeasureEvent; + if (handler != null) handler(this, args); + } + } +} \ No newline at end of file diff --git a/src/keepass2android/MeasuringRelativeLayout.cs b/src/keepass2android/MeasuringRelativeLayout.cs new file mode 100644 index 00000000..38a4a6f2 --- /dev/null +++ b/src/keepass2android/MeasuringRelativeLayout.cs @@ -0,0 +1,64 @@ +using System; +using Android.Content; +using Android.Runtime; +using Android.Util; +using Android.Widget; + +namespace keepass2android +{ + public class MeasuringRelativeLayout : RelativeLayout + { + protected MeasuringRelativeLayout(IntPtr javaReference, JniHandleOwnership transfer) + : base(javaReference, transfer) + { + } + + public MeasuringRelativeLayout(Context context) + : base(context) + { + } + + public MeasuringRelativeLayout(Context context, IAttributeSet attrs) + : base(context, attrs) + { + } + + public MeasuringRelativeLayout(Context context, IAttributeSet attrs, int defStyleAttr) + : base(context, attrs, defStyleAttr) + { + } + + public MeasuringRelativeLayout(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) + : base(context, attrs, defStyleAttr, defStyleRes) + { + } + + + public class MeasureArgs + { + public int ActualHeight; + public int ProposedHeight; + + } + + public event EventHandler MeasureEvent; + + protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + MeasureArgs args = new MeasureArgs(); + + args.ProposedHeight = MeasureSpec.GetSize(heightMeasureSpec); + args.ActualHeight = Height; + + + OnMeasureEvent(args); + base.OnMeasure(widthMeasureSpec, heightMeasureSpec); + } + + protected virtual void OnMeasureEvent(MeasureArgs args) + { + var handler = MeasureEvent; + if (handler != null) handler(this, args); + } + } +} \ No newline at end of file diff --git a/src/keepass2android/PasswordActivity.cs b/src/keepass2android/PasswordActivity.cs index 6754a304..5d2f8b3f 100644 --- a/src/keepass2android/PasswordActivity.cs +++ b/src/keepass2android/PasswordActivity.cs @@ -38,7 +38,6 @@ using Android.Graphics; using Android.Support.Design.Widget; using Android.Support.V4.Widget; using Android.Support.V7.App; -using Android.Util; using keepass2android; using KeePassLib.Keys; using KeePassLib.Serialization; @@ -56,13 +55,15 @@ using Process = Android.OS.Process; using KeeChallenge; using AlertDialog = Android.App.AlertDialog; +using Toolbar = Android.Support.V7.Widget.Toolbar; namespace keepass2android { [Activity(Label = "@string/app_name", - ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.KeyboardHidden, + ConfigurationChanges = ConfigChanges.Orientation, LaunchMode = LaunchMode.SingleInstance, - Theme = "@style/MyTheme_Blue")] /*caution: also contained in AndroidManifest.xml*/ + WindowSoftInputMode = SoftInput.AdjustResize, + Theme = "@style/MyTheme_Blue")] /*caution: also contained in AndroidManifest.xml*/ //TODO: rotating device crashes the app public class PasswordActivity : LockingActivity { @@ -149,7 +150,6 @@ namespace keepass2android private const string KeyFileOrProviderKey = "KeyFileOrProviderKey"; - private ActivityDesign _design; private bool _performingLoad; private bool _keepPasswordInOnResume; private Typeface _passwordFont; @@ -161,12 +161,12 @@ namespace keepass2android public PasswordActivity (IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) { - _design = new ActivityDesign(this); + } public PasswordActivity() { - _design = new ActivityDesign(this); + } @@ -680,11 +680,68 @@ namespace keepass2android } } + int count = 1; + + private DrawerLayout mDrawerLayout; + //private RecyclerView mDrawerList; + + private string mDrawerTitle; + private MeasuringRelativeLayout.MeasureArgs _measureArgs; + internal class MyActionBarDrawerToggle : ActionBarDrawerToggle + { + PasswordActivity owner; + + public MyActionBarDrawerToggle(PasswordActivity activity, DrawerLayout layout, int imgRes, int openRes, int closeRes) + : base(activity, layout, openRes, closeRes) + { + owner = activity; + } + + public override void OnDrawerClosed(View drawerView) + { + owner.SupportActionBar.Title = owner.Title; + owner.InvalidateOptionsMenu(); + } + + public override void OnDrawerOpened(View drawerView) + { + owner.SupportActionBar.Title = owner.mDrawerTitle; + owner.InvalidateOptionsMenu(); + } + } + private void UncollapseToolbar() + { + AppBarLayout appbarLayout = FindViewById(Resource.Id.appbar); + var tmp = appbarLayout.LayoutParameters; + CoordinatorLayout.LayoutParams p = tmp.JavaCast(); + var tmp2 = p.Behavior; + var behavior = tmp2.JavaCast(); + if (behavior == null) + { + p.Behavior = behavior = new AppBarLayout.Behavior(); + } + behavior.OnNestedFling(FindViewById(Resource.Id.main_content), appbarLayout, null, 0, -10000, false); + } + + private void CollapseToolbar() + { + AppBarLayout appbarLayout = FindViewById(Resource.Id.appbar); + ViewGroup.LayoutParams tmp = appbarLayout.LayoutParameters; + CoordinatorLayout.LayoutParams p = tmp.JavaCast(); + var tmp2 = p.Behavior; + var behavior = tmp2.JavaCast(); + if (behavior == null) + { + p.Behavior = behavior = new AppBarLayout.Behavior(); + } + behavior.OnNestedFling(FindViewById(Resource.Id.main_content), appbarLayout, null, 0, 200, true); + } + protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); - _design.ApplyTheme(); + //use FlagSecure to make sure the last (revealed) character of the master password is not visible in recent apps if (PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean( GetString(Resource.String.ViewDatabaseSecure_key), true)) @@ -692,7 +749,6 @@ namespace keepass2android Window.SetFlags(WindowManagerFlags.Secure, WindowManagerFlags.Secure); } - Intent i = Intent; //only load the AppTask if this is the "first" OnCreate (not because of kill/resume, i.e. savedInstanceState==null) @@ -705,12 +761,12 @@ namespace keepass2android } else { - AppTask = AppTask.GetTaskInOnCreate(savedInstanceState, Intent); + AppTask = AppTask.GetTaskInOnCreate(savedInstanceState, Intent); } - + String action = i.Action; - + _prefs = PreferenceManager.GetDefaultSharedPreferences(this); _rememberKeyfile = _prefs.GetBoolean(GetString(Resource.String.keyfile_key), Resources.GetBoolean(Resource.Boolean.keyfile_default)); @@ -720,7 +776,7 @@ namespace keepass2android if (action != null && action.Equals(ViewIntent)) { if (!GetIocFromViewIntent(i)) return; - } + } else if ((action != null) && (action.Equals(Intents.StartWithOtp))) { if (!GetIocFromOtpIntent(savedInstanceState, i)) return; @@ -758,56 +814,49 @@ namespace keepass2android App.Kp2a.LockDatabase(false); } - - SetContentView(Resource.Layout.password); - InitializeToolbar(); + InitializeToolbar(); - - InitializeFilenameView(); + InitializeFilenameView(); if (KeyProviderType == KeyProviders.KeyFile) { UpdateKeyfileIocView(); } - - var passwordEdit = FindViewById(Resource.Id.password_edit); - passwordEdit.TextChanged += + var passwordEdit = FindViewById(Resource.Id.password_edit); + passwordEdit.TextChanged += (sender, args) => { _password = passwordEdit.Text; UpdateOkButtonState(); }; passwordEdit.EditorAction += (sender, args) => - { - if ((args.ActionId == ImeAction.Done) || ((args.ActionId == ImeAction.ImeNull) && (args.Event.Action == KeyEventActions.Down))) - OnOk(); - }; - + { + if ((args.ActionId == ImeAction.Done) || ((args.ActionId == ImeAction.ImeNull) && (args.Event.Action == KeyEventActions.Down))) + OnOk(); + }; + FindViewById(Resource.Id.pass_otpsecret).TextChanged += (sender, args) => UpdateOkButtonState(); passwordEdit.Text = _password; - passwordEdit.RequestFocus(); - Window.SetSoftInputMode(SoftInput.StateVisible); - var passwordFont = Typeface.CreateFromAsset(Assets, "SourceCodePro-Regular.ttf"); passwordEdit.Typeface = passwordFont; - + InitializeBottomBarButtons(); InitializePasswordModeSpinner(); InitializeOtpSecretSpinner(); - InitializeNavDrawerButtons(); + InitializeNavDrawerButtons(); UpdateOkButtonState(); - + InitializeTogglePasswordButton(); InitializeKeyfileBrowseButton(); @@ -821,6 +870,30 @@ namespace keepass2android .PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection, RequestCodePrepareDbFile, false); } + + + mDrawerTitle = this.Title; + mDrawerLayout = FindViewById(Resource.Id.drawer_layout); + var rootview = FindViewById(Resource.Id.relative_layout); + rootview.ViewTreeObserver.GlobalLayout += (sender, args2) => + { + Android.Util.Log.Debug("KP2A", "GlobalLayout"); + var args = _measureArgs; + if (args == null) + return; + Android.Util.Log.Debug("KP2A", "ActualHeight=" + args.ActualHeight); + Android.Util.Log.Debug("KP2A", "ProposedHeight=" + args.ProposedHeight); + if (args.ActualHeight < args.ProposedHeight) + UncollapseToolbar(); + if (args.ActualHeight > args.ProposedHeight) + CollapseToolbar(); + }; + rootview.MeasureEvent += (sender, args) => + { + //Snackbar.Make(rootview, "height="+args.ActualHeight, Snackbar.LengthLong).Show(); + this._measureArgs = args; + }; + } private void InitializeNavDrawerButtons() @@ -1436,7 +1509,7 @@ namespace keepass2android outState.PutString(OtpInfoKey, sw.ToString()); } - + //more OTP TODO: // * Caching of aux file // * -> implement IFileStorage in JavaFileStorage based on ListFiles @@ -1507,7 +1580,12 @@ namespace keepass2android { base.OnResume(); - _design.ReapplyTheme(); + EditText pwd = FindViewById(Resource.Id.password_edit); + pwd.PostDelayed(() => + { + InputMethodManager keyboard = (InputMethodManager)GetSystemService(Context.InputMethodService); + keyboard.ShowSoftInput(pwd, 0); + }, 50); View killButton = FindViewById(Resource.Id.kill_app); if (PreferenceManager.GetDefaultSharedPreferences(this) @@ -1526,7 +1604,7 @@ namespace keepass2android { killButton.Visibility = ViewStates.Gone; } - + if (!_keepPasswordInOnResume) { if ( diff --git a/src/keepass2android/Properties/AndroidManifest_debug.xml b/src/keepass2android/Properties/AndroidManifest_debug.xml index 5447f533..940ab716 100644 --- a/src/keepass2android/Properties/AndroidManifest_debug.xml +++ b/src/keepass2android/Properties/AndroidManifest_debug.xml @@ -54,7 +54,7 @@ - + diff --git a/src/keepass2android/Properties/AndroidManifest_net.xml b/src/keepass2android/Properties/AndroidManifest_net.xml index 43e70011..14511244 100644 --- a/src/keepass2android/Properties/AndroidManifest_net.xml +++ b/src/keepass2android/Properties/AndroidManifest_net.xml @@ -57,7 +57,7 @@ - + diff --git a/src/keepass2android/QuickUnlock.cs b/src/keepass2android/QuickUnlock.cs index cfc44d41..8705e478 100644 --- a/src/keepass2android/QuickUnlock.cs +++ b/src/keepass2android/QuickUnlock.cs @@ -24,30 +24,31 @@ using Android.Widget; using Android.Content.PM; using KeePassLib.Keys; using Android.Preferences; +using Android.Runtime; +using Android.Support.Design.Widget; using Android.Views.InputMethods; using KeePassLib.Serialization; namespace keepass2android { - [Activity(Label = "@string/app_name", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.KeyboardHidden, + [Activity(Label = "@string/app_name", + ConfigurationChanges = ConfigChanges.Orientation, + WindowSoftInputMode = SoftInput.AdjustResize, + MainLauncher = false, Theme = "@style/MyTheme_Blue")] public class QuickUnlock : LifecycleDebugActivity { private IOConnectionInfo _ioc; private QuickUnlockBroadcastReceiver _intentReceiver; - private ActivityDesign _design; - public QuickUnlock() { - _design = new ActivityDesign(this); } protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); - _design.ApplyTheme(); - + //use FlagSecure to make sure the last (revealed) character of the password is not visible in recent apps if (PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean( GetString(Resource.String.ViewDatabaseSecure_key), true)) @@ -63,9 +64,8 @@ namespace keepass2android return; } - SetContentView(Resource.Layout.QuickUnlock); - + if (App.Kp2a.GetDb().KpDatabase.Name != "") { FindViewById(Resource.Id.filename_label).Visibility = ViewStates.Visible; @@ -122,8 +122,10 @@ namespace keepass2android IntentFilter filter = new IntentFilter(); filter.AddAction(Intents.DatabaseLocked); RegisterReceiver(_intentReceiver, filter); + } + private void OnUnlock(int quickUnlockLength, EditText pwd) { KcpPassword kcpPassword = (KcpPassword) App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey(typeof (KcpPassword)); @@ -150,9 +152,7 @@ namespace keepass2android protected override void OnResume() { base.OnResume(); - - _design.ReapplyTheme(); - + CheckIfUnloaded(); EditText pwd = (EditText) FindViewById(Resource.Id.QuickUnlock_password); diff --git a/src/keepass2android/Resources/layout/QuickUnlock.xml b/src/keepass2android/Resources/layout/QuickUnlock.xml index ddf281d1..a94480e4 100644 --- a/src/keepass2android/Resources/layout/QuickUnlock.xml +++ b/src/keepass2android/Resources/layout/QuickUnlock.xml @@ -1,9 +1,15 @@ - + + - \ No newline at end of file + + \ No newline at end of file diff --git a/src/keepass2android/Resources/layout/password.xml b/src/keepass2android/Resources/layout/password.xml index 733bea5b..8aefaa8f 100644 --- a/src/keepass2android/Resources/layout/password.xml +++ b/src/keepass2android/Resources/layout/password.xml @@ -1,414 +1,378 @@ - - - - - + + + +