Files
keepass2android/src/keepass2android/settings/DatabaseSettingsActivity.cs
Rick Brown 141d2f3ddb Persist FTP/SFTP debug log preference
-Commit/apply FtpDebug_key state to persistent preferences
-Configure SFTP/JSch logging based on FtpDebug_key/LogFilename
 state on app startup
2023-11-06 16:55:36 -05:00

1015 lines
42 KiB
C#

/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
Keepass2Android 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.
Keepass2Android 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 Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Widget;
using Android.Preferences;
using Android.Provider;
using Android.Views.Autofill;
using Java.IO;
using KeePass.DataExchange;
using KeePassLib.Cryptography.Cipher;
using KeePassLib.Keys;
using KeePassLib.Serialization;
using KeePassLib.Utility;
using keepass2android.Io;
using keepass2android.Utils;
using KeePassLib;
using KeePassLib.Cryptography.KeyDerivation;
using KeePassLib.Interfaces;
using System.Collections.Generic;
namespace keepass2android
{
//http://stackoverflow.com/a/27422401/292233
#pragma warning disable CS0618 // Type or member is obsolete
public class SettingsFragment : PreferenceFragment
{
public class KeyboardSwitchPrefManager
{
private readonly Activity _act;
private CheckBoxPreference _switchPref;
private CheckBoxPreference _openKp2aAutoPref;
private CheckBoxPreference _openOnlyOnSearchPref;
private CheckBoxPreference _switchBackPref;
private PreferenceScreen _screen;
private PreferenceFragment _fragment;
public KeyboardSwitchPrefManager(PreferenceFragment fragment)
{
var act = fragment.Activity;
this._act = act;
this._fragment = fragment;
this._screen = (PreferenceScreen)_fragment.FindPreference(act.GetString(Resource.String.keyboardswitch_prefs_key));
var keyboardSwapPref = _fragment.FindPreference("get_keyboardswap");
var pm = act.PackageManager;
var intnt = Keepass2android.Kbbridge.ImeSwitcher.GetLaunchIntentForKeyboardSwap(act);
if ((intnt != null) && pm.QueryIntentActivities(intnt, 0).Any())
{
_screen.RemovePreference(keyboardSwapPref);
}
else
{
keyboardSwapPref.PreferenceClick += (sender, args) =>
{
Util.GotoUrl(act, act.GetString(Resource.String.MarketURL) + "keepass2android.plugin.keyboardswap2");
};
}
_switchPref = (CheckBoxPreference)_fragment.FindPreference("kp2a_switch_rooted");
_openKp2aAutoPref =
(CheckBoxPreference)_fragment.FindPreference(act.GetString(Resource.String.OpenKp2aKeyboardAutomatically_key));
_openOnlyOnSearchPref =
(CheckBoxPreference)
_fragment.FindPreference(act.GetString(Resource.String.OpenKp2aKeyboardAutomaticallyOnlyAfterSearch_key));
_switchBackPref =
(CheckBoxPreference)_fragment.FindPreference(act.GetString(Resource.String.AutoSwitchBackKeyboard_key));
EnableSwitchPreferences(_switchPref.Checked);
_switchPref.PreferenceChange += (sender, args) =>
{
bool switchOnRooted = (bool)args.NewValue;
EnableSwitchPreferences(switchOnRooted);
};
}
private void EnableSwitchPreferences(bool switchOnRooted)
{
if (!switchOnRooted)
{
if (_fragment.FindPreference(_act.GetString(Resource.String.OpenKp2aKeyboardAutomatically_key)) == null)
{
_screen.AddPreference(_openKp2aAutoPref);
}
if (_fragment.FindPreference(_act.GetString(Resource.String.OpenKp2aKeyboardAutomaticallyOnlyAfterSearch_key)) != null)
{
_screen.RemovePreference(_openOnlyOnSearchPref);
}
}
else
{
{
_screen.RemovePreference(_openKp2aAutoPref);
}
if (_fragment.FindPreference(_act.GetString(Resource.String.OpenKp2aKeyboardAutomaticallyOnlyAfterSearch_key)) == null)
{
_screen.AddPreference(_openOnlyOnSearchPref);
}
}
/*_openKp2aAutoPref.Enabled = !switchOnRooted;
_openOnlyOnSearchPref.Enabled = switchOnRooted;
_switchBackPref.Enabled = switchOnRooted;*/
}
}
private KeyboardSwitchPrefManager _switchPrefManager;
private Preference aesRounds, argon2parallelism, argon2rounds, argon2memory;
void OnRememberKeyFileHistoryChanged(object sender, Preference.PreferenceChangeEventArgs eventArgs)
{
if (!(bool)eventArgs.NewValue)
{
App.Kp2a.FileDbHelper.DeleteAllKeys();
}
}
void OnShowUnlockedNotificationChanged(object sender, Preference.PreferenceChangeEventArgs eventArgs)
{
App.Kp2a.UpdateOngoingNotification();
}
public override void OnResume()
{
base.OnResume();
UpdateAutofillPref();
}
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
AddPreferencesFromResource(Resource.Xml.preferences);
// Re-use the change handlers for the application settings
FindPreference(GetString(Resource.String.keyfile_key)).PreferenceChange += OnRememberKeyFileHistoryChanged;
var unlockedNotificationPref = FindPreference(GetString(Resource.String.ShowUnlockedNotification_key));
unlockedNotificationPref.PreferenceChange += OnShowUnlockedNotificationChanged;
if ((int)Build.VERSION.SdkInt >= 26)
{
//use system notification channels to control notification visibility
unlockedNotificationPref.Parent.RemovePreference(unlockedNotificationPref);
}
FindPreference(GetString(Resource.String.DebugLog_key)).PreferenceChange += OnDebugLogChanged;
FindPreference(GetString(Resource.String.DebugLog_send_key)).PreferenceClick += OnSendDebug;
#if !EXCLUDE_JAVAFILESTORAGE && !NoNet
FindPreference(GetString(Resource.String.FtpDebug_key)).PreferenceChange += OnJSchDebugChanged;
#else
FindPreference(GetString(Resource.String.FtpDebug_key)).Enabled = false;
#endif
HashSet<string> supportedLocales = new HashSet<string>() { "en", "af", "ar", "az", "be", "bg", "ca", "cs", "da", "de", "el", "es", "eu", "fa", "fi", "fr", "gl", "he", "hr", "hu", "id", "in", "it", "iw", "ja", "ko", "ml", "nb", "nl", "nn", "no", "pl", "pt", "ro", "ru", "si", "sk", "sl", "sr", "sv", "tr", "uk", "vi", "zh" };
var languagePref = (ListPreference)FindPreference(GetString(Resource.String.app_language_pref_key));
new AppLanguageManager(this, languagePref, supportedLocales);
UpdateAutofillPref();
var autofillPref = FindPreference(GetString(Resource.String.AutoFill_prefs_key));
if (autofillPref != null)
{
autofillPref.PreferenceClick += (sender, args) =>
{
var intent = new Intent(Settings.ActionRequestSetAutofillService);
if (((AutofillManager)Activity.GetSystemService(Java.Lang.Class.FromType(typeof(AutofillManager))))
.HasEnabledAutofillServices)
{
intent.SetData(Android.Net.Uri.Parse("package:" + Context.PackageName + "notexisting")); //if we use our package name, the activity won't launch
}
else
{
intent.SetData(Android.Net.Uri.Parse("package:" + Context.PackageName));
}
try
{
Context.StartActivity(intent);
}
catch (ActivityNotFoundException e)
{
//this exception was reported by many Huawei users
Kp2aLog.LogUnexpectedError(e);
new AlertDialog.Builder(Context)
.SetTitle(Resource.String.autofill_enable)
.SetMessage(Resource.String.autofill_enable_failed)
.SetPositiveButton(Android.Resource.String.Ok, (o, eventArgs) => { })
.Show();
}
catch (Exception e)
{
Kp2aLog.LogUnexpectedError(e);
}
};
}
PrepareNoDonatePreference(Activity, FindPreference(GetString(Resource.String.NoDonateOption_key)));
PrepareNoDonationReminderPreference(Activity, ((PreferenceScreen)FindPreference(GetString(Resource.String.display_prefs_key))), FindPreference(GetString(Resource.String.NoDonationReminder_key)));
FindPreference(GetString(Resource.String.design_key)).PreferenceChange += (sender, args) => Activity.Recreate();
Database db = App.Kp2a.CurrentDb;
if (db != null)
{
ListPreference kdfPref = (ListPreference)FindPreference(GetString(Resource.String.kdf_key));
kdfPref.SetEntries(KdfPool.Engines.Select(eng => eng.Name).ToArray());
string[] kdfValues = KdfPool.Engines.Select(eng => eng.Uuid.ToHexString()).ToArray();
kdfPref.SetEntryValues(kdfValues);
kdfPref.SetValueIndex(kdfValues.Select((v, i) => new { kdf = v, index = i }).First(el => el.kdf == db.KpDatabase.KdfParameters.KdfUuid.ToHexString()).index);
kdfPref.PreferenceChange += OnKdfChange;
aesRounds = FindPreference(GetString(Resource.String.rounds_key));
argon2rounds = FindPreference("argon2rounds");
argon2memory = FindPreference("argon2memory");
argon2parallelism = FindPreference("argon2parallelism");
aesRounds.PreferenceChange += (sender, e) => UpdateKdfSummary(e.Preference);
argon2rounds.PreferenceChange += (sender, e) => UpdateKdfSummary(e.Preference);
argon2memory.PreferenceChange += (sender, e) => UpdateKdfSummary(e.Preference);
argon2parallelism.PreferenceChange += (sender, e) => UpdateKdfSummary(e.Preference);
UpdateKdfScreen();
PrepareDefaultUsername(db);
PrepareDatabaseName(db);
PrepareMasterPassword();
PrepareTemplates(db);
ListPreference algorithmPref = (ListPreference)FindPreference(GetString(Resource.String.algorithm_key));
algorithmPref.SetEntries(CipherPool.GlobalPool.Engines.Select(eng => eng.DisplayName).ToArray());
string[] algoValues = CipherPool.GlobalPool.Engines.Select(eng => eng.CipherUuid.ToHexString()).ToArray();
algorithmPref.SetEntryValues(algoValues);
algorithmPref.SetValueIndex(algoValues.Select((v, i) => new { kdf = v, index = i }).First(el => el.kdf == db.KpDatabase.DataCipherUuid.ToHexString()).index);
algorithmPref.PreferenceChange += AlgorithmPrefChange;
algorithmPref.Summary =
CipherPool.GlobalPool.GetCipher(App.Kp2a.CurrentDb.KpDatabase.DataCipherUuid).DisplayName;
UpdateImportDbPref();
UpdateImportKeyfilePref();
}
try
{
//depending on Android version, we offer to use a transparent icon for QuickUnlock or use the notification priority (since API level 16)
Preference hideQuickUnlockTranspIconPref = FindPreference(GetString(Resource.String.QuickUnlockIconHidden_key));
Preference hideQuickUnlockIconPref = FindPreference(GetString(Resource.String.QuickUnlockIconHidden16_key));
var quickUnlockScreen = ((PreferenceScreen)FindPreference(GetString(Resource.String.QuickUnlock_prefs_key)));
if ((int)Android.OS.Build.VERSION.SdkInt >= 26)
{
//use notification channels
quickUnlockScreen.RemovePreference(hideQuickUnlockTranspIconPref);
quickUnlockScreen.RemovePreference(hideQuickUnlockIconPref);
}
else if ((int)Android.OS.Build.VERSION.SdkInt >= 16)
{
quickUnlockScreen.RemovePreference(hideQuickUnlockTranspIconPref);
unlockedNotificationPref.PreferenceChange += (sender, args) => App.Kp2a.UpdateOngoingNotification();
hideQuickUnlockIconPref.PreferenceChange += delegate { App.Kp2a.UpdateOngoingNotification(); };
}
else
{
//old version: only show transparent quickUnlock and no option to hide unlocked icon:
quickUnlockScreen.RemovePreference(hideQuickUnlockIconPref);
FindPreference(GetString(Resource.String.QuickUnlockIconHidden_key)).PreferenceChange +=
delegate { App.Kp2a.UpdateOngoingNotification(); };
((PreferenceScreen)FindPreference(GetString(Resource.String.display_prefs_key))).RemovePreference(
unlockedNotificationPref);
}
}
catch (Exception ex)
{
Kp2aLog.LogUnexpectedError(ex);
}
//AppSettingsActivity.PrepareKeyboardSwitchingPreferences(this);
_switchPrefManager = new KeyboardSwitchPrefManager(this);
PrepareSeparateNotificationsPreference();
FindPreference("IconSetKey").PreferenceChange += (sender, args) =>
{
if (App.Kp2a.CurrentDb != null)
App.Kp2a.CurrentDb.DrawableFactory.Clear();
};
Preference cachingPreference = FindPreference(GetString(Resource.String.UseOfflineCache_key));
cachingPreference.PreferenceChange += OnUseOfflineCacheChanged;
}
private void UpdateAutofillPref()
{
var autofillScreen = FindPreference(GetString(Resource.String.AutoFill_prefs_screen_key));
var autofillPref = FindPreference(GetString(Resource.String.AutoFill_prefs_key));
var autofillDisabledPref = FindPreference(GetString(Resource.String.AutofillDisabledQueriesPreference_key));
var autofillSavePref = FindPreference(GetString(Resource.String.OfferSaveCredentials_key));
var autofillInlineSuggestions = FindPreference(GetString(Resource.String.InlineSuggestions_key));
var noAutofillDisablingPref = FindPreference(GetString(Resource.String.NoAutofillDisabling_key));
var autofillNoDalVerification = FindPreference(GetString(Resource.String.NoDalVerification_key));
if (autofillPref == null)
return;
if ((Android.OS.Build.VERSION.SdkInt < Android.OS.BuildVersionCodes.O) ||
!((AutofillManager) Activity.GetSystemService(Java.Lang.Class.FromType(typeof(AutofillManager))))
.IsAutofillSupported)
{
var passwordAccessScreen =
(PreferenceScreen) FindPreference(Activity.GetString(Resource.String.password_access_prefs_key));
passwordAccessScreen.RemovePreference(autofillScreen);
}
else
{
if (((AutofillManager) Activity.GetSystemService(Java.Lang.Class.FromType(typeof(AutofillManager))))
.HasEnabledAutofillServices)
{
autofillDisabledPref.Enabled = true;
autofillSavePref.Enabled = true;
autofillNoDalVerification.Enabled = true;
autofillInlineSuggestions.Enabled = true;
noAutofillDisablingPref.Enabled = true;
autofillPref.Summary = Activity.GetString(Resource.String.plugin_enabled);
autofillPref.Intent = new Intent(Intent.ActionView);
autofillPref.Intent.SetData(Android.Net.Uri.Parse("https://philippc.github.io/keepass2android/OreoAutoFill.html"));
}
else
{
autofillNoDalVerification.Enabled = false;
autofillDisabledPref.Enabled = false;
autofillSavePref.Enabled = false;
noAutofillDisablingPref.Enabled = false;
autofillInlineSuggestions.Enabled = false;
autofillPref.Summary = Activity.GetString(Resource.String.not_enabled);
}
if ((int)Android.OS.Build.VERSION.SdkInt < 30)
{
autofillInlineSuggestions.Summary = Activity.GetString(Resource.String.requires_android11);
CheckBoxPreference cbp = autofillInlineSuggestions as CheckBoxPreference;
if (cbp != null)
cbp.Checked = false;
autofillInlineSuggestions.Enabled = false;
}
}
}
private void OnSendDebug(object sender, Preference.PreferenceClickEventArgs e)
{
Kp2aLog.SendLog(this.Activity);
}
private void OnDebugLogChanged(object sender, Preference.PreferenceChangeEventArgs e)
{
if ((bool)e.NewValue)
Kp2aLog.CreateLogFile();
else
Kp2aLog.FinishLogFile();
#if !EXCLUDE_JAVAFILESTORAGE && !NoNet
SetJSchLogging(PreferenceManager.GetDefaultSharedPreferences(Application.Context)
.GetBoolean(Application.Context.GetString(Resource.String.FtpDebug_key), false));
#endif
}
#if !EXCLUDE_JAVAFILESTORAGE && !NoNet
private void OnJSchDebugChanged(object sender, Preference.PreferenceChangeEventArgs e)
{
bool debugEnabled = (bool)e.NewValue;
SetJSchLogging(debugEnabled);
string prefKey = Application.Context.GetString(Resource.String.FtpDebug_key);
PreferenceManager.SharedPreferences.Edit().PutBoolean(prefKey, debugEnabled).Apply();
}
private void SetJSchLogging(bool enabled)
{
var sftpStorage = new Keepass2android.Javafilestorage.SftpStorage(Context);
string? logFilename = null;
if (Kp2aLog.LogToFile)
{
logFilename = Kp2aLog.LogFilename;
}
sftpStorage.SetJschLogging(enabled, logFilename);
}
#endif
private void AlgorithmPrefChange(object sender, Preference.PreferenceChangeEventArgs preferenceChangeEventArgs)
{
var db = App.Kp2a.CurrentDb;
var previousCipher = db.KpDatabase.DataCipherUuid;
db.KpDatabase.DataCipherUuid = new PwUuid(MemUtil.HexStringToByteArray((string)preferenceChangeEventArgs.NewValue));
SaveDb save = new SaveDb(Activity, App.Kp2a, App.Kp2a.CurrentDb, new ActionOnFinish(Activity, (success, message, activity) =>
{
if (!success)
{
db.KpDatabase.DataCipherUuid = previousCipher;
Toast.MakeText(activity, message, ToastLength.Long).Show();
return;
}
preferenceChangeEventArgs.Preference.Summary =
CipherPool.GlobalPool.GetCipher(db.KpDatabase.DataCipherUuid).DisplayName;
}));
ProgressTask pt = new ProgressTask(App.Kp2a, Activity, save);
pt.Run();
}
private void UpdateKdfScreen()
{
var db = App.Kp2a.CurrentDb;
var kdf = KdfPool.Get(db.KpDatabase.KdfParameters.KdfUuid);
var kdfpref = FindPreference(GetString(Resource.String.kdf_key));
kdfpref.Summary = kdf.Name;
var kdfscreen = ((PreferenceScreen)FindPreference(GetString(Resource.String.kdf_screen_key)));
if (kdf is AesKdf)
{
if (kdfscreen.FindPreference(GetString(Resource.String.rounds_key)) == null)
kdfscreen.AddPreference(aesRounds);
kdfscreen.RemovePreference(argon2rounds);
kdfscreen.RemovePreference(argon2memory);
kdfscreen.RemovePreference(argon2parallelism);
aesRounds.Enabled = db.CanWrite;
UpdateKdfSummary(aesRounds);
}
else
{
kdfscreen.RemovePreference(aesRounds);
if (kdfscreen.FindPreference("argon2rounds") == null)
{
kdfscreen.AddPreference(argon2rounds);
kdfscreen.AddPreference(argon2memory);
kdfscreen.AddPreference(argon2parallelism);
}
UpdateKdfSummary(argon2rounds);
UpdateKdfSummary(argon2memory);
UpdateKdfSummary(argon2parallelism);
}
}
private void OnKdfChange(object sender, Preference.PreferenceChangeEventArgs preferenceChangeEventArgs)
{
var db = App.Kp2a.CurrentDb;
var previousKdfParams = db.KpDatabase.KdfParameters;
Kp2aLog.Log("previous kdf: " + KdfPool.Get(db.KpDatabase.KdfParameters.KdfUuid) + " " + db.KpDatabase.KdfParameters.KdfUuid.ToHexString() );
db.KpDatabase.KdfParameters =
KdfPool.Get(
new PwUuid(MemUtil.HexStringToByteArray((string)preferenceChangeEventArgs.NewValue)))
.GetDefaultParameters();
Kp2aLog.Log("--new kdf: " + KdfPool.Get(db.KpDatabase.KdfParameters.KdfUuid) + " " + db.KpDatabase.KdfParameters.KdfUuid.ToHexString());
SaveDb save = new SaveDb(Activity, App.Kp2a, App.Kp2a.CurrentDb, new ActionOnFinish(Activity, (success, message, activity) =>
{
if (!success)
{
db.KpDatabase.KdfParameters = previousKdfParams;
Toast.MakeText(activity, message, ToastLength.Long).Show();
return;
}
UpdateKdfScreen();
}));
ProgressTask pt = new ProgressTask(App.Kp2a, Activity, save);
pt.Run();
}
private void UpdateKdfSummary(Preference preference)
{
preference.Summary = ((keepass2android.settings.KdfNumberParamPreference)preference).ParamValue.ToString();
}
private void PrepareNoDonationReminderPreference(Activity ctx, PreferenceScreen screen, Preference preference)
{
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(ctx);
if (!prefs.GetBoolean("DismissedDonateReminder", false))
{
screen.RemovePreference(preference);
}
}
private void PrepareTemplates(Database db)
{
Preference pref = FindPreference("AddTemplates_pref_key");
if ((!db.DatabaseFormat.SupportsTemplates) || (AddTemplateEntries.ContainsAllTemplates(App.Kp2a.CurrentDb)))
{
pref.Enabled = false;
}
else
{
pref.PreferenceClick += (sender, args) =>
{
ProgressTask pt = new ProgressTask(App.Kp2a, Activity,
new AddTemplateEntries(Activity, App.Kp2a, new ActionOnFinish(Activity,
delegate
{
pref.Enabled = false;
})));
pt.Run();
};
}
}
private void PrepareMasterPassword()
{
Preference changeMaster = FindPreference(GetString(Resource.String.master_pwd_key));
if (App.Kp2a.CurrentDb.CanWrite)
{
changeMaster.Enabled = true;
changeMaster.PreferenceClick += delegate { new SetPasswordDialog(Activity).Show(); };
}
}
private void PrepareDatabaseName(Database db)
{
Preference databaseName = FindPreference(GetString(Resource.String.database_name_key));
if (!db.DatabaseFormat.HasDatabaseName)
{
((PreferenceScreen) FindPreference(GetString(Resource.String.db_key))).RemovePreference(databaseName);
}
else
{
databaseName.Enabled = db.CanWrite;
((EditTextPreference) databaseName).EditText.Text = db.KpDatabase.Name;
((EditTextPreference) databaseName).Text = db.KpDatabase.Name;
databaseName.PreferenceChange += (sender, e) =>
{
DateTime previousNameChanged = db.KpDatabase.NameChanged;
String previousName = db.KpDatabase.Name;
db.KpDatabase.Name = e.NewValue.ToString();
SaveDb save = new SaveDb(Activity, App.Kp2a, App.Kp2a.CurrentDb, new ActionOnFinish(Activity, (success, message, activity) =>
{
if (!success)
{
db.KpDatabase.Name = previousName;
db.KpDatabase.NameChanged = previousNameChanged;
Toast.MakeText(activity, message, ToastLength.Long).Show();
}
else
{
// Name is reflected in notification, so update it
App.Kp2a.UpdateOngoingNotification();
}
}));
ProgressTask pt = new ProgressTask(App.Kp2a, Activity, save);
pt.Run();
};
}
}
private void PrepareDefaultUsername(Database db)
{
Preference defaultUser = FindPreference(GetString(Resource.String.default_username_key));
if (!db.DatabaseFormat.HasDefaultUsername)
{
((PreferenceScreen) FindPreference(GetString(Resource.String.db_key))).RemovePreference(defaultUser);
}
else
{
defaultUser.Enabled = db.CanWrite;
((EditTextPreference) defaultUser).EditText.Text = db.KpDatabase.DefaultUserName;
((EditTextPreference) defaultUser).Text = db.KpDatabase.DefaultUserName;
defaultUser.PreferenceChange += (sender, e) =>
{
DateTime previousUsernameChanged = db.KpDatabase.DefaultUserNameChanged;
String previousUsername = db.KpDatabase.DefaultUserName;
db.KpDatabase.DefaultUserName = e.NewValue.ToString();
SaveDb save = new SaveDb(Activity, App.Kp2a, App.Kp2a.CurrentDb, new ActionOnFinish(Activity, (success, message, activity) =>
{
if (!success)
{
db.KpDatabase.DefaultUserName = previousUsername;
db.KpDatabase.DefaultUserNameChanged = previousUsernameChanged;
Toast.MakeText(activity, message, ToastLength.Long).Show();
}
}));
ProgressTask pt = new ProgressTask(App.Kp2a, Activity, save);
pt.Run();
};
}
}
public void PrepareSeparateNotificationsPreference()
{
try
{
//depending on Android version, we offer to show a combined notification (with action buttons) (since API level 16)
Preference separateNotificationsPref = FindPreference(Activity.GetString(Resource.String.ShowSeparateNotifications_key));
var passwordAccessScreen = ((PreferenceScreen)FindPreference(Activity.GetString(Resource.String.password_access_prefs_key)));
if ((int)Build.VERSION.SdkInt < 16)
{
passwordAccessScreen.RemovePreference(separateNotificationsPref);
}
}
catch (Exception ex)
{
Kp2aLog.LogUnexpectedError(ex);
}
}
private void OnUseOfflineCacheChanged(object sender, Preference.PreferenceChangeEventArgs e)
{
if (!(bool)e.NewValue)
{
AlertDialog.Builder builder = new AlertDialog.Builder(Activity);
builder.SetTitle(GetString(Resource.String.ClearOfflineCache_title));
builder.SetMessage(GetString(Resource.String.ClearOfflineCache_question));
builder.SetPositiveButton(App.Kp2a.GetResourceString(UiStringKey.yes), (o, args) =>
{
try
{
App.Kp2a.ClearOfflineCache();
}
catch (Exception ex)
{
Kp2aLog.LogUnexpectedError(ex);
Toast.MakeText(LocaleManager.LocalizedAppContext, ex.Message, ToastLength.Long).Show();
}
}
);
builder.SetNegativeButton(App.Kp2a.GetResourceString(UiStringKey.no), (o, args) =>
{
((CheckBoxPreference) e.Preference).Checked = true;
}
);
builder.SetCancelable(false);
Dialog dialog = builder.Create();
dialog.Show();
}
}
private void UpdateImportKeyfilePref()
{
var prefs = PreferenceManager.GetDefaultSharedPreferences(Activity);
var rememberKeyfile = prefs.GetBoolean(GetString(Resource.String.keyfile_key), Resources.GetBoolean(Resource.Boolean.keyfile_default));
Preference importKeyfile = FindPreference("import_keyfile_prefs");
Preference exportKeyfile = FindPreference("export_keyfile_prefs");
importKeyfile.Summary = "";
if (!rememberKeyfile)
{
importKeyfile.Summary = GetString(Resource.String.KeyfileMoveRequiresRememberKeyfile);
importKeyfile.Enabled = false;
exportKeyfile.Enabled = false;
return;
}
CompositeKey masterKey = App.Kp2a.CurrentDb.KpDatabase.MasterKey;
if (masterKey.ContainsType(typeof(KcpKeyFile)))
{
IOConnectionInfo iocKeyfile = ((KcpKeyFile)masterKey.GetUserKey(typeof(KcpKeyFile))).Ioc;
if (iocKeyfile.IsLocalFile() && IoUtil.IsInInternalDirectory(iocKeyfile.Path, Activity))
{
importKeyfile.Enabled = false;
exportKeyfile.Enabled = true;
exportKeyfile.PreferenceClick += (sender, args) => { ExportKeyfileFromInternalFolder(); };
importKeyfile.Summary = GetString(Resource.String.FileIsInInternalDirectory);
}
else
{
exportKeyfile.Enabled = false;
importKeyfile.Enabled = true;
importKeyfile.PreferenceClick += (sender, args) => { MoveKeyfileToInternalFolder(); };
}
}
else
{
exportKeyfile.Enabled = false;
importKeyfile.Enabled = false;
}
}
private void ExportKeyfileFromInternalFolder()
{
StartActivity(new Intent(Activity.ApplicationContext, typeof(ExportKeyfileActivity)));
}
private void MoveKeyfileToInternalFolder()
{
Func<Action> copyAndReturnPostExecute = () =>
{
try
{
CompositeKey masterKey = App.Kp2a.CurrentDb.KpDatabase.MasterKey;
var sourceIoc = ((KcpKeyFile)masterKey.GetUserKey(typeof(KcpKeyFile))).Ioc;
var newIoc = IoUtil.ImportFileToInternalDirectory(sourceIoc, Activity, App.Kp2a);
((KcpKeyFile)masterKey.GetUserKey(typeof(KcpKeyFile))).ResetIoc(newIoc);
var keyfileString = IOConnectionInfo.SerializeToString(newIoc);
App.Kp2a.StoreOpenedFileAsRecent(App.Kp2a.CurrentDb.Ioc, keyfileString, false);
return () =>
{
UpdateImportKeyfilePref();
var builder = new AlertDialog.Builder(Activity);
builder
.SetMessage(Resource.String.KeyfileMoved);
builder.SetPositiveButton(Android.Resource.String.Ok, (sender, args) => { });
builder.Show();
};
}
catch (Exception e)
{
return () =>
{
Toast.MakeText(Activity, App.Kp2a.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message, ToastLength.Long).Show();
};
}
};
new SimpleLoadingDialog(Activity, GetString(Resource.String.CopyingFile), false,
copyAndReturnPostExecute
).Execute();
}
private void UpdateImportDbPref()
{
//Import db/key file preferences:
Preference importDb = FindPreference("import_db_prefs");
bool isLocalOrContent =
App.Kp2a.CurrentDb.Ioc.IsLocalFile() || App.Kp2a.CurrentDb.Ioc.Path.StartsWith("content://");
if (!isLocalOrContent)
{
importDb.Summary = GetString(Resource.String.OnlyAvailableForLocalFiles);
importDb.Enabled = false;
}
else
{
if (IoUtil.IsInInternalDirectory(App.Kp2a.CurrentDb.Ioc.Path, Activity))
{
importDb.Summary = GetString(Resource.String.FileIsInInternalDirectory);
importDb.Enabled = false;
}
else
{
importDb.Enabled = true;
importDb.PreferenceClick += delegate { MoveDbToInternalFolder(); };
}
}
}
private void MoveDbToInternalFolder()
{
Func<Action> copyAndReturnPostExecute = () =>
{
try
{
var sourceIoc = App.Kp2a.CurrentDb.Ioc;
var newIoc = IoUtil.ImportFileToInternalDirectory(sourceIoc, Activity, App.Kp2a);
return () =>
{
var builder = new AlertDialog.Builder(Activity);
builder
.SetMessage(Resource.String.DatabaseFileMoved);
builder.SetPositiveButton(Android.Resource.String.Ok, (sender, args) =>
{
var key = App.Kp2a.CurrentDb.KpDatabase.MasterKey;
App.Kp2a.CloseDatabase(App.Kp2a.CurrentDb);
PasswordActivity.Launch(Activity, newIoc, key, new ActivityLaunchModeSimple(), false);
});
builder.Show();
};
}
catch (Exception e)
{
return () =>
{
Toast.MakeText(Activity, App.Kp2a.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message, ToastLength.Long).Show();
};
}
};
new SimpleLoadingDialog(Activity, GetString(Resource.String.CopyingFile), false,
copyAndReturnPostExecute
).Execute();
}
private void SetAlgorithm(Database db, Preference algorithm)
{
algorithm.Summary = CipherPool.GlobalPool.GetCipher(db.KpDatabase.DataCipherUuid).DisplayName;
}
public void PrepareNoDonatePreference(Context ctx, Preference preference)
{
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(ctx);
long usageCount = prefs.GetLong(ctx.GetString(Resource.String.UsageCount_key), 0);
#if DEBUG
preference.Enabled = (usageCount > 1);
#else
preference.Enabled = (usageCount > 50);
#endif
preference.PreferenceChange += delegate(object sender, Preference.PreferenceChangeEventArgs args)
{
if ((bool)args.NewValue)
{
new AlertDialog.Builder(ctx)
.SetTitle(ctx.GetString(AppNames.AppNameResource))
.SetCancelable(false)
.SetPositiveButton(Android.Resource.String.Ok, delegate(object o, DialogClickEventArgs eventArgs)
{
Util.GotoDonateUrl(ctx);
((Dialog)o).Dismiss();
})
.SetMessage(Resource.String.NoDonateOption_question)
.Create().Show();
}
};
}
}
/// <summary>
/// <para>
/// A helper class that manages language preference display and selection.
/// </para>
/// <para>
/// The idea is to provide a ListPreference with a "System language" item at the top, followed by
/// the localized list of supported language names. The items are backed by their corresponding "code".
/// For a langauge that's the 2-char lowercase language code, which is exactly the same code that
/// LocaleManager.Language expects.
/// </para>
/// <para>
/// "System language" is a special case. LocaleManager.Language expects null, but ListPreference
/// does not support null as a valid code. To work around this, LanguageEntry.SYS_LANG_CODE
/// is used as the preference code. LanguageEntry.PrefCodeToLanguage(string) is used to convert the
/// preference codes to language codes as needed.
/// </para>
/// </summary>
internal class AppLanguageManager
{
private readonly PreferenceFragment _fragment;
private readonly ListPreference _langPref;
private readonly Dictionary<string, LanguageEntry> _langEntriesByCodeUnique;
public AppLanguageManager(PreferenceFragment fragment, ListPreference langPref, HashSet<string> supportedLocales)
{
this._fragment = fragment;
this._langPref = langPref;
this._langEntriesByCodeUnique = CreateCodeToEntryMapping(fragment, supportedLocales);
ConfigureLanguageList();
}
private static Dictionary<string, LanguageEntry> CreateCodeToEntryMapping(PreferenceFragment fragment, HashSet<string> supportedLocales)
{
var localesByCode = new Dictionary<string, List<Java.Util.Locale>>();
foreach (var loc in Java.Util.Locale.GetAvailableLocales())
{
if (!supportedLocales.Contains(loc.Language))
continue;
if (!localesByCode.ContainsKey(loc.Language))
{
localesByCode[loc.Language] = new List<Java.Util.Locale>();
}
localesByCode[loc.Language].Add(loc);
}
var langEntriesByCodeUnique = localesByCode
.Select(l => new KeyValuePair<string, LanguageEntry>(l.Key, LanguageEntry.OfLocale(l.Value.First())))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
var sysLangEntry = LanguageEntry.SystemDefault(fragment.GetString(Resource.String.SystemLanguage));
langEntriesByCodeUnique.Add(sysLangEntry.Code, sysLangEntry);
return langEntriesByCodeUnique;
}
private void ConfigureLanguageList()
{
List<KeyValuePair<string, LanguageEntry>> langEntriesList = _langEntriesByCodeUnique
.OrderByDescending(kvp => kvp.Value.IsSystem)
.ThenBy(kvp => kvp.Value.Name)
.ToList();
_langPref.SetEntries(langEntriesList
.Select(kvp => kvp.Value.Name)
.ToArray());
_langPref.SetEntryValues(langEntriesList
.Select(kvp => kvp.Value.Code)
.ToArray());
_langPref.Summary = GetDisplayLanguage(LanguageEntry.PrefCodeToLanguage(_langPref.Value));
_langPref.PreferenceChange += AppLanguagePrefChange;
}
private string GetDisplayLanguage(string languageCode)
{
if (languageCode != null && this._langEntriesByCodeUnique.ContainsKey(languageCode))
return this._langEntriesByCodeUnique[languageCode]?.Name;
else
return _fragment.GetString(Resource.String.SystemLanguage);
}
private void AppLanguagePrefChange(object sender, Preference.PreferenceChangeEventArgs args)
{
string langCode = LanguageEntry.PrefCodeToLanguage((string)args.NewValue);
LocaleManager.Language = langCode;
_langPref.Summary = GetDisplayLanguage(langCode);
}
}
#pragma warning restore CS0618 // Type or member is obsolete
/// <summary>
/// Activity to configure the application and database settings. The database must be unlocked, and this activity will close if it becomes locked.
/// </summary>
[Activity(Label = "@string/app_name", Theme = "@style/MyTheme", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden)]
public class DatabaseSettingsActivity : LockCloseActivity
{
public DatabaseSettingsActivity()
{
}
public static void Launch(Activity ctx)
{
ctx.StartActivity(new Intent(ctx, typeof(DatabaseSettingsActivity)));
}
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.preference);
SetSupportActionBar(FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Resource.Id.mytoolbar));
}
}
}