Files
keepass2android/src/keepass2android-app/CreateDatabaseActivity.cs
2025-04-08 10:37:40 +02:00

468 lines
13 KiB
C#

using System;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Preferences;
using Android.Text;
using Android.Views;
using Android.Widget;
using Java.IO;
using KeePassLib.Keys;
using KeePassLib.Serialization;
using keepass2android.Io;
using keepass2android;
using Environment = Android.OS.Environment;
using IOException = Java.IO.IOException;
namespace keepass2android
{
[Activity(Label = "@string/app_name",
ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.KeyboardHidden,
Theme = "@style/Kp2aTheme_BlueActionBar")]
public class CreateDatabaseActivity : LifecycleAwareActivity
{
private IOConnectionInfo _ioc;
private string _keyfileFilename;
private bool _restoringInstanceState;
private bool _showPassword;
private readonly ActivityDesign _design;
private AppTask _appTask;
private AppTask AppTask
{
get { return _appTask; }
set
{
_appTask = value;
Kp2aLog.LogTask(value, MyDebugName);
}
}
public CreateDatabaseActivity()
{
_design = new ActivityDesign(this);
}
private const int RequestCodeKeyFile = 0;
private const int RequestCodeDbFilename = 1;
private const string KeyfilefilenameBundleKey = "KeyfileFilename";
protected override void OnSaveInstanceState(Bundle outState)
{
base.OnSaveInstanceState(outState);
outState.PutString(Util.KeyFilename, _ioc.Path);
outState.PutString(Util.KeyServerusername, _ioc.UserName);
outState.PutString(Util.KeyServerpassword, _ioc.Password);
outState.PutInt(Util.KeyServercredmode, (int)_ioc.CredSaveMode);
if (_keyfileFilename != null)
outState.PutString(KeyfilefilenameBundleKey, _keyfileFilename);
}
protected override void OnCreate(Bundle bundle)
{
_design.ApplyTheme();
base.OnCreate(bundle);
SupportActionBar.SetDisplayHomeAsUpEnabled(true);
SupportActionBar.SetHomeButtonEnabled(true);
SetContentView(Resource.Layout.create_database);
AppTask = AppTask.GetTaskInOnCreate(bundle, Intent);
SetDefaultIoc();
FindViewById(Resource.Id.keyfile_filename).Visibility = ViewStates.Gone;
var keyfileCheckbox = FindViewById<CheckBox>(Resource.Id.use_keyfile);
if (bundle != null)
{
_keyfileFilename = bundle.GetString(KeyfilefilenameBundleKey, null);
if (_keyfileFilename != null)
{
FindViewById<TextView>(Resource.Id.keyfile_filename).Text = FileSelectHelper.ConvertFilenameToIocPath(_keyfileFilename);
FindViewById(Resource.Id.keyfile_filename).Visibility = ViewStates.Visible;
keyfileCheckbox.Checked = true;
}
if (bundle.GetString(Util.KeyFilename, null) != null)
{
_ioc = new IOConnectionInfo
{
Path = bundle.GetString(Util.KeyFilename),
UserName = bundle.GetString(Util.KeyServerusername),
Password = bundle.GetString(Util.KeyServerpassword),
CredSaveMode = (IOCredSaveMode) bundle.GetInt(Util.KeyServercredmode),
};
}
}
UpdateIocView();
keyfileCheckbox.CheckedChange += (sender, args) =>
{
if (keyfileCheckbox.Checked)
{
if (_restoringInstanceState)
return;
Util.ShowBrowseDialog(this, RequestCodeKeyFile, false, true);
}
else
{
FindViewById(Resource.Id.keyfile_filename).Visibility = ViewStates.Gone;
_keyfileFilename = null;
}
};
FindViewById(Resource.Id.btn_change_location).Click += (sender, args) =>
{
Intent intent = new Intent(this, typeof(FileStorageSelectionActivity));
StartActivityForResult(intent, RequestCodeDbFilename);
};
Button generatePassword = (Button)FindViewById(Resource.Id.generate_button);
generatePassword.Click += (sender, e) =>
{
GeneratePasswordActivity.LaunchWithoutLockCheck(this);
};
FindViewById(Resource.Id.btn_create).Click += (sender, evt) =>
{
CreateDatabase(Intent.GetBooleanExtra("MakeCurrent",true));
};
Button btnTogglePassword = (Button)FindViewById(Resource.Id.toggle_password);
btnTogglePassword.Click += (sender, e) =>
{
_showPassword = !_showPassword;
MakePasswordMaskedOrVisible();
};
Util.SetNoPersonalizedLearning(FindViewById(Resource.Id.root));
}
readonly PasswordFont _passwordFont = new PasswordFont();
private void MakePasswordMaskedOrVisible()
{
EditText password = (EditText)FindViewById(Resource.Id.entry_password);
TextView confpassword = (TextView)FindViewById(Resource.Id.entry_confpassword);
int selStart = password.SelectionStart, selEnd = password.SelectionEnd;
if (_showPassword)
{
password.InputType = InputTypes.ClassText | InputTypes.TextVariationVisiblePassword;
_passwordFont.ApplyTo(password);
confpassword.Visibility = ViewStates.Gone;
}
else
{
password.InputType = InputTypes.ClassText | InputTypes.TextVariationPassword;
confpassword.Visibility = ViewStates.Visible;
}
password.SetSelection(selStart, selEnd);
}
private void CreateDatabase(bool makeCurrent)
{
var keyfileCheckbox = FindViewById<CheckBox>(Resource.Id.use_keyfile);
string password;
if (!TryGetPassword(out password)) return;
// Verify that a password or keyfile is set
if (password.Length == 0 && !keyfileCheckbox.Checked)
{
App.Kp2a.ShowMessage(this, Resource.String.error_nopass, MessageSeverity.Error);
return;
}
//create the key
CompositeKey newKey = new CompositeKey();
if (String.IsNullOrEmpty(password) == false)
{
newKey.AddUserKey(new KcpPassword(password));
}
if (String.IsNullOrEmpty(_keyfileFilename) == false)
{
try
{
var ioc = IOConnectionInfo.FromPath(_keyfileFilename);
using (var stream = App.Kp2a.GetFileStorage(ioc).OpenFileForRead(ioc))
{
byte[] keyfileData = Util.StreamToMemoryStream(stream).ToArray();
newKey.AddUserKey(new KcpKeyFile(keyfileData, ioc, true));
}
}
catch (Exception)
{
App.Kp2a.ShowMessage(this, Resource.String.error_adding_keyfile, MessageSeverity.Error);
return;
}
}
// Create the new database
CreateDb create = new CreateDb(App.Kp2a, this, _ioc, new LaunchGroupActivity(_ioc, this), false, newKey, makeCurrent);
ProgressTask createTask = new ProgressTask(
App.Kp2a,
this, create);
createTask.Run();
}
private bool TryGetPassword(out string pass)
{
TextView passView = (TextView)FindViewById(Resource.Id.entry_password);
pass = passView.Text;
if (_showPassword)
return true;
TextView passConfView = (TextView) FindViewById(Resource.Id.entry_confpassword);
String confpass = passConfView.Text;
// Verify that passwords match
if (! pass.Equals(confpass))
{
// Passwords do not match
App.Kp2a.ShowMessage(this, Resource.String.error_pass_match, MessageSeverity.Error);
return false;
}
return true;
}
protected override void OnResume()
{
base.OnResume();
_design.ReapplyTheme();
}
protected override void OnRestoreInstanceState(Bundle savedInstanceState)
{
_restoringInstanceState = true;
base.OnRestoreInstanceState(savedInstanceState);
_restoringInstanceState = false;
}
private void UpdateIocView()
{
string displayPath = App.Kp2a.GetFileStorage(_ioc).GetDisplayName(_ioc);
int protocolSeparatorPos = displayPath.IndexOf("://", StringComparison.Ordinal);
string protocolId = protocolSeparatorPos < 0 ?
"file" : displayPath.Substring(0, protocolSeparatorPos);
Drawable drawable = App.Kp2a.GetStorageIcon(protocolId);
FindViewById<ImageView>(Resource.Id.filestorage_logo).SetImageDrawable(drawable);
String title = App.Kp2a.GetStorageDisplayName(protocolId);
FindViewById<TextView>(Resource.Id.filestorage_label).Text = title;
FindViewById<TextView>(Resource.Id.label_filename).Text = protocolSeparatorPos < 0 ?
displayPath :
displayPath.Substring(protocolSeparatorPos + 3);
}
private void SetDefaultIoc()
{
Java.IO.File directory = GetExternalFilesDir(null);
if (directory == null)
directory = FilesDir;
string strDir = directory.CanonicalPath;
if (!strDir.EndsWith(Java.IO.File.Separator))
strDir += Java.IO.File.Separator;
string filename = strDir + "keepass.kdbx";
filename = FileSelectHelper.ConvertFilenameToIocPath(filename);
int count = 2;
while (new Java.IO.File(filename).Exists())
{
filename = FileSelectHelper.ConvertFilenameToIocPath(strDir + "keepass" + count + ".kdbx");
count++;
}
_ioc = new IOConnectionInfo
{
Path = filename
};
}
private static string SdDir
{
get
{
string sdDir = Environment.ExternalStorageDirectory.AbsolutePath;
if (!sdDir.EndsWith("/"))
sdDir += "/";
if (!sdDir.StartsWith("file://"))
sdDir = "file://" + sdDir;
return sdDir;
}
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (resultCode == KeePass.ResultOkPasswordGenerator)
{
String generatedPassword = data.GetStringExtra("keepass2android.password.generated_password");
FindViewById<TextView>(Resource.Id.entry_password).Text = generatedPassword;
FindViewById<TextView>(Resource.Id.entry_confpassword).Text = generatedPassword;
}
FileSelectHelper fileSelectHelper = new FileSelectHelper(this, true, true, RequestCodeDbFilename)
{
DefaultExtension = "kdbx"
};
fileSelectHelper.OnOpen += (sender, info) =>
{
_ioc = info;
(sender as CreateDatabaseActivity ?? this).UpdateIocView();
};
if (fileSelectHelper.HandleActivityResult(this, requestCode, resultCode, data))
return;
if (resultCode == Result.Ok)
{
if (requestCode == RequestCodeKeyFile)
{
if (data.Data.Scheme == "content")
{
if ((int)Build.VERSION.SdkInt >= 19)
{
//try to take persistable permissions
try
{
Kp2aLog.Log("TakePersistableUriPermission");
var takeFlags = data.Flags
& (ActivityFlags.GrantReadUriPermission
| ActivityFlags.GrantWriteUriPermission);
this.ContentResolver.TakePersistableUriPermission(data.Data, takeFlags);
}
catch (Exception e)
{
Kp2aLog.Log(e.ToString());
}
}
}
string filename = Util.IntentToFilename(data, this);
if (filename == null)
filename = data.DataString;
_keyfileFilename = FileSelectHelper.ConvertFilenameToIocPath(filename);
FindViewById<TextView>(Resource.Id.keyfile_filename).Text = _keyfileFilename;
FindViewById(Resource.Id.keyfile_filename).Visibility = ViewStates.Visible;
}
}
if (resultCode == (Result)FileStorageResults.FileUsagePrepared)
{
_ioc = new IOConnectionInfo();
Util.SetIoConnectionFromIntent(_ioc, data);
UpdateIocView();
}
if (resultCode == (Result)FileStorageResults.FileChooserPrepared)
{
IOConnectionInfo ioc = new IOConnectionInfo();
Util.SetIoConnectionFromIntent(ioc, data);
new FileSelectHelper(this, true, true, RequestCodeDbFilename) { DefaultExtension = "kdbx" }
.StartFileChooser(ioc.Path);
}
}
private void AfterQueryCredentials(IOConnectionInfo ioc)
{
_ioc = ioc;
UpdateIocView();
}
private class LaunchGroupActivity : FileOnFinish
{
readonly CreateDatabaseActivity _activity;
private readonly IOConnectionInfo _ioc;
public LaunchGroupActivity(IOConnectionInfo ioc, CreateDatabaseActivity activity)
: base(activity, null)
{
_activity = activity;
_ioc = ioc;
}
public override void Run()
{
if (Success)
{
// Update the ongoing notification
App.Kp2a.UpdateOngoingNotification();
if (PreferenceManager.GetDefaultSharedPreferences(_activity).GetBoolean(_activity.GetString(Resource.String.RememberRecentFiles_key), _activity.Resources.GetBoolean(Resource.Boolean.RememberRecentFiles_default)))
{
// Add to recent files
FileDbHelper dbHelper = App.Kp2a.FileDbHelper;
dbHelper.CreateFile(_ioc, Filename, true);
}
Intent data = new Intent();
data.PutExtra("ioc", IOConnectionInfo.SerializeToString(_ioc));
_activity.SetResult(Result.Ok, data);
_activity.Finish();
}
else
{
DisplayMessage(_activity);
try
{
App.Kp2a.GetFileStorage(_ioc).Delete(_ioc);
}
catch (Exception e)
{
//not nice, but not a catastrophic failure if we can't delete the file:
Kp2aLog.Log("couldn't delete file after failure! " + e);
}
}
}
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
switch (item.ItemId)
{
case Android.Resource.Id.Home:
OnBackPressed();
return true;
}
return false;
}
}
}