diff --git a/src/keepass2android/GeneratePasswordActivity.cs b/src/keepass2android/GeneratePasswordActivity.cs index 325a9464..07f1cba2 100644 --- a/src/keepass2android/GeneratePasswordActivity.cs +++ b/src/keepass2android/GeneratePasswordActivity.cs @@ -16,15 +16,22 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file */ using System; +using System.Collections.Generic; using System.Globalization; +using System.Linq; using Android.App; +using Android.App.Admin; using Android.Content; using Android.Content.PM; +using Android.Graphics; using Android.OS; using Android.Preferences; using Android.Views; using Android.Widget; +using Java.Util; +using KeePassLib.Cryptography; using Newtonsoft.Json; +using OtpKeyProv; namespace keepass2android { @@ -84,6 +91,48 @@ namespace keepass2android act.StartActivityForResult(i, 0); } + private PasswordProfiles _profiles; + + private bool _updateDisabled = false; + + class PasswordProfiles + { + public List> Profiles { get; set; } + + public PasswordGenerator.CombinedKeyOptions LastUsedSettings { get; set; } + + public int? TryFindLastUsedProfileIndex() + { + for (int i=0;i(key, options); + return; + } + } + + Profiles.Add(new KeyValuePair(key, options)); + } + + public void Remove(in int profileIndex) + { + Profiles.RemoveAt(profileIndex); + } + } protected override void OnCreate(Bundle savedInstanceState) { _design.ApplyTheme(); @@ -95,13 +144,13 @@ namespace keepass2android var prefs = GetPreferences(FileCreationMode.Private); - PasswordGenerator.PasswordGenerationOptions options = null; - string jsonOptions = prefs.GetString("password_generator_options", null); + + string jsonOptions = prefs.GetString("password_generator_profiles", null); if (jsonOptions != null) { try { - options = JsonConvert.DeserializeObject(jsonOptions); + _profiles = JsonConvert.DeserializeObject(jsonOptions); } catch (Exception e) { @@ -110,37 +159,57 @@ namespace keepass2android } else { - options = new PasswordGenerator.PasswordGenerationOptions() + PasswordGenerator.CombinedKeyOptions options = new PasswordGenerator.CombinedKeyOptions() { - Length = prefs.GetInt("length", 12), - UpperCase = prefs.GetBoolean("cb_uppercase", true), - LowerCase = prefs.GetBoolean("cb_lowercase", true), - Digits = prefs.GetBoolean("cb_digits", true), - Minus = prefs.GetBoolean("cb_minus", false), - Underline = prefs.GetBoolean("cb_underline", false), - Space = prefs.GetBoolean("cb_space", false), - Specials = prefs.GetBoolean("cb_specials", false), - SpecialsExtended = false, - Brackets = prefs.GetBoolean("cb_brackets", false) + PasswordGenerationOptions = new PasswordGenerator.PasswordGenerationOptions() + { + Length = prefs.GetInt("length", 12), + UpperCase = prefs.GetBoolean("cb_uppercase", true), + LowerCase = prefs.GetBoolean("cb_lowercase", true), + Digits = prefs.GetBoolean("cb_digits", true), + Minus = prefs.GetBoolean("cb_minus", false), + Underline = prefs.GetBoolean("cb_underline", false), + Space = prefs.GetBoolean("cb_space", false), + Specials = prefs.GetBoolean("cb_specials", false), + SpecialsExtended = false, + Brackets = prefs.GetBoolean("cb_brackets", false) + } + }; + _profiles = new PasswordProfiles() + { + LastUsedSettings = options, + Profiles = GetDefaultProfiles() }; - } + } - ((CheckBox)FindViewById(Resource.Id.cb_uppercase)).Checked = options.UpperCase; - ((CheckBox)FindViewById(Resource.Id.cb_lowercase)).Checked = options.LowerCase; - ((CheckBox)FindViewById(Resource.Id.cb_digits)).Checked = options.Digits; - ((CheckBox)FindViewById(Resource.Id.cb_minus)).Checked = options.Minus; - ((CheckBox)FindViewById(Resource.Id.cb_underline)).Checked = options.Underline; - ((CheckBox)FindViewById(Resource.Id.cb_space)).Checked = options.Space; - ((CheckBox)FindViewById(Resource.Id.cb_specials)).Checked = options.Specials; - ((CheckBox)FindViewById(Resource.Id.cb_specials_extended)).Checked = options.SpecialsExtended; - ((CheckBox)FindViewById(Resource.Id.cb_brackets)).Checked = options.Brackets; - ((CheckBox)FindViewById(Resource.Id.cb_exclude_lookalike)).Checked = options.ExcludeLookAlike; - ((CheckBox)FindViewById(Resource.Id.cb_at_least_one_from_each_group)).Checked = options.AtLeastOneFromEachGroup; + _profiles ??= new PasswordProfiles(); + _profiles.LastUsedSettings ??= new PasswordGenerator.CombinedKeyOptions() + { + PasswordGenerationOptions = new PasswordGenerator.PasswordGenerationOptions() + {Length = 7, UpperCase = true, LowerCase = true, Digits = true} + }; + _profiles.Profiles ??= new List>(); - ((EditText)FindViewById(Resource.Id.length)).Text = options.Length.ToString(CultureInfo.InvariantCulture); - - foreach (int id in _buttonLengthButtonIds) { + _updateDisabled = true; + PopulateFieldsFromOptions(_profiles.LastUsedSettings); + _updateDisabled = false; + + var profileSpinner = UpdateProfileSpinner(); + + profileSpinner.ItemSelected += (sender, args) => + { + if (profileSpinner.SelectedItemPosition > 0) + { + _profiles.LastUsedSettings = _profiles.Profiles[profileSpinner.SelectedItemPosition - 1].Value; + _updateDisabled = true; + PopulateFieldsFromOptions(_profiles.LastUsedSettings); + _updateDisabled = false; + UpdatePassword(); + } + }; + + foreach (int id in _buttonLengthButtonIds) { Button button = (Button) FindViewById(id); button.Click += (sender, e) => { @@ -148,15 +217,33 @@ namespace keepass2android EditText editText = (EditText) FindViewById(Resource.Id.length); editText.Text = b.Text; - UpdatePassword(); - }; } + FindViewById(Resource.Id.length).TextChanged += (sender, args) => UpdatePassword(); + FindViewById(Resource.Id.wordcount).TextChanged += (sender, args) => UpdatePassword(); + FindViewById(Resource.Id.wordseparator).TextChanged += (sender, args) => UpdatePassword(); + foreach (int id in _checkboxIds) { FindViewById(id).CheckedChange += (sender, args) => UpdatePassword(); } + + var mode_spinner = FindViewById(Resource.Id.spinner_password_generator_mode); + mode_spinner.ItemSelected += (sender, args) => + { + FindViewById(Resource.Id.passphraseOptions).Visibility = + mode_spinner.SelectedItemPosition == 0 ? ViewStates.Gone : ViewStates.Visible; + FindViewById(Resource.Id.passwordOptions).Visibility = + mode_spinner.SelectedItemPosition == 1 ? ViewStates.Gone : ViewStates.Visible; + + UpdatePassword(); + }; + + FindViewById(Resource.Id.spinner_password_generator_case_mode).ItemSelected += (sender, args) => + { + UpdatePassword(); + }; Button genPassButton = (Button) FindViewById(Resource.Id.generate_password_button); genPassButton.Click += (sender, e) => { UpdatePassword(); }; @@ -184,75 +271,320 @@ namespace keepass2android Finish(); }; - - EditText txtPasswordToSet = (EditText) FindViewById(Resource.Id.password_edit); - txtPasswordToSet.Text = GeneratePassword(); + FindViewById(Resource.Id.btn_password_generator_profile_save) + .Click += (sender, args) => + { + var editText = new EditText(this); + new AlertDialog.Builder(this) + .SetMessage(Resource.String.save_password_generation_profile_text) + .SetView(editText) + .SetPositiveButton(Android.Resource.String.Ok, (o, eventArgs) => + { + _profiles.Add(editText.Text, GetOptions()); + UpdateProfileSpinner(); + }) + .Show(); + }; + + FindViewById(Resource.Id.btn_password_generator_profile_delete) + .Click += (sender, args) => + { + if (profileSpinner.SelectedItemPosition > 0) + { + _profiles.Remove(profileSpinner.SelectedItemPosition-1); + UpdateProfileSpinner(); + } + }; + + + EditText txtPasswordToSet = (EditText) FindViewById(Resource.Id.password_edit); _passwordFont.ApplyTo(txtPasswordToSet); + UpdatePassword(); + SupportActionBar.SetDisplayHomeAsUpEnabled(true); SupportActionBar.SetHomeButtonEnabled(true); } + private Spinner UpdateProfileSpinner() + { + string[] profileNames = new List {GetString(Resource.String.custom_settings)} + .Concat(_profiles.Profiles.Select(p => p.Key)) + .ToArray(); + ArrayAdapter profileArrayAdapter = new ArrayAdapter(this, + Android.Resource.Layout.SimpleSpinnerDropDownItem, + profileNames); + var profileSpinner = FindViewById(Resource.Id.spinner_password_generator_profile); + profileSpinner.Adapter = profileArrayAdapter; + + UpdateProfileSpinnerSelection(); + return profileSpinner; + } + + private static List> GetDefaultProfiles() + { + return new List>() + { + new KeyValuePair( + "Simple12", new PasswordGenerator.CombinedKeyOptions() + { + PasswordGenerationOptions + = new PasswordGenerator.PasswordGenerationOptions() + { + Length = 12, AtLeastOneFromEachGroup = true, ExcludeLookAlike = true, + Digits = true, LowerCase = true, UpperCase = true + } + + } + ), + new KeyValuePair( + "Special12", new PasswordGenerator.CombinedKeyOptions() + { + PasswordGenerationOptions + = new PasswordGenerator.PasswordGenerationOptions() + { + Length = 12, AtLeastOneFromEachGroup = true, ExcludeLookAlike = true, + Digits = true, LowerCase = true, UpperCase = true,Specials = true,Brackets = true + } + + } + ), + new KeyValuePair( + "Password64", new PasswordGenerator.CombinedKeyOptions() + { + PasswordGenerationOptions + = new PasswordGenerator.PasswordGenerationOptions() + { + Length = 64, AtLeastOneFromEachGroup = true, + Digits = true, LowerCase = true, UpperCase = true, ExcludeLookAlike = false,Specials = true,Brackets = true, Minus = true, Space = true, SpecialsExtended = true,Underline = true + } + + } + ), + new KeyValuePair( + + "Passphrase7", new PasswordGenerator.CombinedKeyOptions() + { + PassphraseGenerationOptions + = new PasswordGenerator.PassphraseGenerationOptions() + { + WordCount = 7, + CaseMode = PasswordGenerator.PassphraseGenerationOptions.PassphraseCaseMode.Lowercase, + Separator = " " + } + } + ), + new KeyValuePair( + "Passphrase5Plus", new PasswordGenerator.CombinedKeyOptions() + { + PassphraseGenerationOptions + = new PasswordGenerator.PassphraseGenerationOptions() + { + WordCount = 5, + CaseMode = PasswordGenerator.PassphraseGenerationOptions.PassphraseCaseMode.PascalCase, + Separator = " " + }, + PasswordGenerationOptions = new PasswordGenerator.PasswordGenerationOptions() + { + Length = 2, + AtLeastOneFromEachGroup = true, + ExcludeLookAlike = true, + Digits = true, + Specials = true + } + } + ) + + }; + } + + private void PopulateFieldsFromOptions(PasswordGenerator.CombinedKeyOptions combinedOptions) + { + PasswordGenerator.PasswordGenerationOptions passwordOptions = combinedOptions.PasswordGenerationOptions; + if (passwordOptions != null) + { + ((CheckBox)FindViewById(Resource.Id.cb_uppercase)).Checked = passwordOptions.UpperCase; + ((CheckBox)FindViewById(Resource.Id.cb_lowercase)).Checked = passwordOptions.LowerCase; + ((CheckBox)FindViewById(Resource.Id.cb_digits)).Checked = passwordOptions.Digits; + ((CheckBox)FindViewById(Resource.Id.cb_minus)).Checked = passwordOptions.Minus; + ((CheckBox)FindViewById(Resource.Id.cb_underline)).Checked = passwordOptions.Underline; + ((CheckBox)FindViewById(Resource.Id.cb_space)).Checked = passwordOptions.Space; + ((CheckBox)FindViewById(Resource.Id.cb_specials)).Checked = passwordOptions.Specials; + ((CheckBox)FindViewById(Resource.Id.cb_specials_extended)).Checked = passwordOptions.SpecialsExtended; + ((CheckBox)FindViewById(Resource.Id.cb_brackets)).Checked = passwordOptions.Brackets; + ((CheckBox)FindViewById(Resource.Id.cb_exclude_lookalike)).Checked = passwordOptions.ExcludeLookAlike; + ((CheckBox)FindViewById(Resource.Id.cb_at_least_one_from_each_group)).Checked = passwordOptions.AtLeastOneFromEachGroup; + + ((EditText)FindViewById(Resource.Id.length)).Text = passwordOptions.Length.ToString(CultureInfo.InvariantCulture); + + FindViewById(Resource.Id.passwordOptions).Visibility = ViewStates.Visible; + } + else + { + FindViewById(Resource.Id.passwordOptions).Visibility = ViewStates.Gone; + } + + var passphraseOptions = combinedOptions.PassphraseGenerationOptions; + + if (passphraseOptions != null) + { + FindViewById(Resource.Id.wordcount).Text = passphraseOptions.WordCount.ToString(CultureInfo.InvariantCulture); + FindViewById(Resource.Id.wordseparator).Text = passphraseOptions.Separator; + FindViewById(Resource.Id.spinner_password_generator_case_mode) + .SetSelection((int)passphraseOptions.CaseMode); + + FindViewById(Resource.Id.passphraseOptions).Visibility = ViewStates.Visible; + } + else + { + FindViewById(Resource.Id.passphraseOptions).Visibility = ViewStates.Gone; + } + + + int mode; + if (combinedOptions.PasswordGenerationOptions != null && + combinedOptions.PassphraseGenerationOptions != null) + mode = 2; + else if (combinedOptions.PasswordGenerationOptions == null && + combinedOptions.PassphraseGenerationOptions != null) + mode = 1; + else mode = 0; + + FindViewById(Resource.Id.spinner_password_generator_mode) + .SetSelection(mode); + + } + private void UpdatePassword() { + if (_updateDisabled) + return; String password = GeneratePassword(); EditText txtPassword = (EditText) FindViewById(Resource.Id.password_edit); txtPassword.Text = password; + + var progressBar = FindViewById(Resource.Id.pb_password_strength); + var passwordBits = QualityEstimation.EstimatePasswordBits(password.ToCharArray()); + progressBar.Progress = (int)passwordBits; + progressBar.Max = 128; + + Color color = new Color(196, 63, 49); + if (passwordBits > 40) + { + color = new Color(219, 152, 55); + } + if (passwordBits > 64) + { + color = new Color(96, 138, 38); + } + if (passwordBits > 100) + { + color = new Color(31, 128, 31); + } + progressBar.ProgressDrawable.SetColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SrcIn)); + + FindViewById(Resource.Id.tv_password_strength).Text = " " + passwordBits + " bits"; + + + + UpdateProfileSpinnerSelection(); + } + + private void UpdateProfileSpinnerSelection() + { + int? lastUsedIndex = _profiles.TryFindLastUsedProfileIndex(); + FindViewById(Resource.Id.spinner_password_generator_profile) + .SetSelection(lastUsedIndex != null ? lastUsedIndex.Value + 1 : 0); } public String GeneratePassword() { String password = ""; - try { + try + { + PasswordGenerator generator = new PasswordGenerator(this); - int length; - if (!int.TryParse(((EditText) FindViewById(Resource.Id.length)).Text, out length)) - { - Toast.MakeText(this, Resource.String.error_wrong_length, ToastLength.Long).Show(); - return password; - } + var options = GetOptions(); + try + { + password = generator.GeneratePassword(options); + } + catch (Exception e) + { + throw new ArgumentException(GetString(Resource.String.error_pass_gen_type)); + } - PasswordGenerator generator = new PasswordGenerator(this); + _profiles.LastUsedSettings = options; - var options = GetOptions(length); - - password = generator.GeneratePassword(options); - - var prefs = GetPreferences(FileCreationMode.Private); - prefs.Edit() - .PutString("password_generator_options", JsonConvert.SerializeObject(options)) - .Commit(); - - - - } catch (ArgumentException e) { + SaveProfiles(); + } + catch (Exception e) + { Toast.MakeText(this, e.Message, ToastLength.Long).Show(); } return password; } - private PasswordGenerator.PasswordGenerationOptions GetOptions(int length) + private void SaveProfiles() { - PasswordGenerator.PasswordGenerationOptions options = new PasswordGenerator.PasswordGenerationOptions() + var prefs = GetPreferences(FileCreationMode.Private); + prefs.Edit() + .PutString("password_generator_profiles", JsonConvert.SerializeObject(_profiles)) + .Commit(); + } + + private PasswordGenerator.CombinedKeyOptions GetOptions() + { + PasswordGenerator.CombinedKeyOptions options = new PasswordGenerator.CombinedKeyOptions(); + if (FindViewById(Resource.Id.passphraseOptions).Visibility == ViewStates.Visible) { - Length = length, - UpperCase = ((CheckBox) FindViewById(Resource.Id.cb_uppercase)).Checked, - LowerCase = ((CheckBox) FindViewById(Resource.Id.cb_lowercase)).Checked, - Digits = ((CheckBox) FindViewById(Resource.Id.cb_digits)).Checked, - Minus = ((CheckBox) FindViewById(Resource.Id.cb_minus)).Checked, - Underline = ((CheckBox) FindViewById(Resource.Id.cb_underline)).Checked, - Space = ((CheckBox) FindViewById(Resource.Id.cb_space)).Checked, - Specials = ((CheckBox) FindViewById(Resource.Id.cb_specials)).Checked, - SpecialsExtended = ((CheckBox) FindViewById(Resource.Id.cb_specials_extended)).Checked, - Brackets = ((CheckBox) FindViewById(Resource.Id.cb_brackets)).Checked, - ExcludeLookAlike = ((CheckBox) FindViewById(Resource.Id.cb_exclude_lookalike)).Checked, - AtLeastOneFromEachGroup = ((CheckBox) FindViewById(Resource.Id.cb_at_least_one_from_each_group)).Checked - }; + int wordCount; + if (!int.TryParse(((EditText)FindViewById(Resource.Id.wordcount)).Text, out wordCount)) + { + throw new Exception(GetString(Resource.String.error_wrong_length)); + } + + options.PassphraseGenerationOptions = + new PasswordGenerator.PassphraseGenerationOptions() + { + WordCount = wordCount, + Separator = FindViewById(Resource.Id.wordseparator).Text, + CaseMode = (PasswordGenerator.PassphraseGenerationOptions.PassphraseCaseMode)FindViewById(Resource.Id.spinner_password_generator_case_mode).SelectedItemPosition + }; + } + + if (FindViewById(Resource.Id.passwordOptions).Visibility == ViewStates.Visible) + { + int length; + if (!int.TryParse(((EditText) FindViewById(Resource.Id.length)).Text, out length)) + { + throw new Exception(GetString(Resource.String.error_wrong_length)); + } + + options.PasswordGenerationOptions = + new PasswordGenerator.PasswordGenerationOptions() + { + Length = length, + UpperCase = ((CheckBox) FindViewById(Resource.Id.cb_uppercase)).Checked, + LowerCase = ((CheckBox) FindViewById(Resource.Id.cb_lowercase)).Checked, + Digits = ((CheckBox) FindViewById(Resource.Id.cb_digits)).Checked, + Minus = ((CheckBox) FindViewById(Resource.Id.cb_minus)).Checked, + Underline = ((CheckBox) FindViewById(Resource.Id.cb_underline)).Checked, + Space = ((CheckBox) FindViewById(Resource.Id.cb_space)).Checked, + Specials = ((CheckBox) FindViewById(Resource.Id.cb_specials)).Checked, + SpecialsExtended = ((CheckBox) FindViewById(Resource.Id.cb_specials_extended)).Checked, + Brackets = ((CheckBox) FindViewById(Resource.Id.cb_brackets)).Checked, + ExcludeLookAlike = ((CheckBox) FindViewById(Resource.Id.cb_exclude_lookalike)).Checked, + AtLeastOneFromEachGroup = ((CheckBox) FindViewById(Resource.Id.cb_at_least_one_from_each_group)) + .Checked + }; + } + return options; } diff --git a/src/keepass2android/Resources/layout/generate_password.xml b/src/keepass2android/Resources/layout/generate_password.xml index 47b6563d..06c1cd6e 100644 --- a/src/keepass2android/Resources/layout/generate_password.xml +++ b/src/keepass2android/Resources/layout/generate_password.xml @@ -1,8 +1,8 @@ + android:layout_width="fill_parent" + android:background="?activityBackgroundColor" + android:layout_height="fill_parent"> - + +