
* updates themes and color definitions * removed many color attributes from views and special configurations * reworked settings completely to get rid of "settings toolsbar" * use DayNight theme instead of custom light/dark Note that there are quite a few issues left.
435 lines
17 KiB
C#
435 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
|
|
using Android.App;
|
|
using Android.Content;
|
|
using Android.Content.PM;
|
|
using Android.Graphics;
|
|
using Android.Graphics.Drawables;
|
|
using Android.OS;
|
|
using Android.Provider;
|
|
using Android.Runtime;
|
|
using Android.Text;
|
|
using Android.Util;
|
|
using Android.Views;
|
|
using Android.Widget;
|
|
using keepass2android.database.edit;
|
|
using KeePass.Util.Spr;
|
|
using keepass2android_appSdkStyle;
|
|
using KeePassLib;
|
|
using KeePassLib.Keys;
|
|
using KeePassLib.Security;
|
|
using KeePassLib.Serialization;
|
|
using AlertDialog = Android.App.AlertDialog;
|
|
using Object = Java.Lang.Object;
|
|
|
|
namespace keepass2android
|
|
{
|
|
[Activity(Label = "@string/child_dbs_title", MainLauncher = false, Theme = "@style/Kp2aTheme_BlueNoActionBar", LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden, Exported = true)]
|
|
[IntentFilter(new[] { "kp2a.action.ConfigureChildDatabasesActivity" }, Categories = new[] { Intent.CategoryDefault })]
|
|
public class ConfigureChildDatabasesActivity : LockCloseActivity
|
|
{
|
|
private ChildDatabasesAdapter _adapter;
|
|
|
|
public class ChildDatabasesAdapter : BaseAdapter
|
|
{
|
|
|
|
private readonly ConfigureChildDatabasesActivity _context;
|
|
internal List<AutoExecItem> _displayedChildDatabases;
|
|
|
|
public ChildDatabasesAdapter(ConfigureChildDatabasesActivity context)
|
|
{
|
|
_context = context;
|
|
Update();
|
|
|
|
}
|
|
|
|
public override Object GetItem(int position)
|
|
{
|
|
return position;
|
|
}
|
|
|
|
public override long GetItemId(int position)
|
|
{
|
|
return position;
|
|
}
|
|
|
|
|
|
|
|
private LayoutInflater cursorInflater;
|
|
|
|
public override View GetView(int position, View convertView, ViewGroup parent)
|
|
{
|
|
if (cursorInflater == null)
|
|
cursorInflater = (LayoutInflater)_context.GetSystemService(Context.LayoutInflaterService);
|
|
|
|
View view;
|
|
|
|
if (convertView == null)
|
|
{
|
|
// if it's not recycled, initialize some attributes
|
|
|
|
view = cursorInflater.Inflate(Resource.Layout.child_db_config_row, parent, false);
|
|
|
|
|
|
view.FindViewById<Button>(Resource.Id.child_db_enable_on_this_device).Click += (sender, args) =>
|
|
{
|
|
View sending_view = (View) sender;
|
|
_context.OnEnable(_displayedChildDatabases[GetClickedPos(sending_view)]);
|
|
};
|
|
|
|
view.FindViewById<Button>(Resource.Id.child_db_disable_on_this_device).Click += (sender, args) =>
|
|
{
|
|
View sending_view = (View)sender;
|
|
_context.OnDisable(_displayedChildDatabases[GetClickedPos(sending_view)]);
|
|
};
|
|
|
|
|
|
view.FindViewById<Button>(Resource.Id.child_db_edit).Click += (sender, args) =>
|
|
{
|
|
View sending_view = (View)sender;
|
|
_context.OnEdit(_displayedChildDatabases[GetClickedPos(sending_view)]);
|
|
};
|
|
|
|
|
|
view.FindViewById<Button>(Resource.Id.child_db_open).Click += (sender, args) =>
|
|
{
|
|
View sending_view = (View)sender;
|
|
_context.OnOpen(_displayedChildDatabases[GetClickedPos(sending_view)]);
|
|
};
|
|
|
|
|
|
view.FindViewById<Button>(Resource.Id.child_db_enable_a_copy_for_this_device).Click += (sender, args) =>
|
|
{
|
|
View sending_view = (View)sender;
|
|
_context.OnEnableCopy(_displayedChildDatabases[GetClickedPos(sending_view)]);
|
|
};
|
|
}
|
|
else
|
|
{
|
|
view = convertView;
|
|
}
|
|
|
|
|
|
var iv = view.FindViewById<ImageView>(Resource.Id.child_db_icon);
|
|
var autoExecItem = _displayedChildDatabases[position];
|
|
var pw = autoExecItem.Entry;
|
|
|
|
SprContext ctx = new SprContext(pw, App.Kp2a.FindDatabaseForElement(pw).KpDatabase, SprCompileFlags.All);
|
|
|
|
string deviceId = KeeAutoExecExt.ThisDeviceId;
|
|
|
|
view.FindViewById<TextView>(Resource.Id.child_db_title).Text =
|
|
SprEngine.Compile(pw.Strings.GetSafe(PwDefs.TitleField).ReadString(), ctx);
|
|
|
|
view.FindViewById<TextView>(Resource.Id.child_db_url).Text =
|
|
_context.GetString(Resource.String.entry_url) + ": " + SprEngine.Compile(pw.Strings.GetSafe(PwDefs.UrlField).ReadString(),ctx);
|
|
|
|
bool deviceEnabledExplict;
|
|
bool deviceEnabled = KeeAutoExecExt.IsDeviceEnabled(autoExecItem, deviceId, out deviceEnabledExplict);
|
|
deviceEnabled &= deviceEnabledExplict;
|
|
|
|
|
|
if (!autoExecItem.Enabled)
|
|
{
|
|
view.FindViewById<TextView>(Resource.Id.child_db_enabled_here).Text =
|
|
_context.GetString(Resource.String.plugin_disabled);
|
|
}
|
|
else
|
|
{
|
|
view.FindViewById<TextView>(Resource.Id.child_db_enabled_here).Text =
|
|
_context.GetString(Resource.String.child_db_enabled_on_this_device) + ": " +
|
|
(!deviceEnabledExplict ?
|
|
_context.GetString(Resource.String.unspecified)
|
|
:
|
|
((autoExecItem.Enabled && deviceEnabled)
|
|
? _context.GetString(Resource.String.yes)
|
|
: _context.GetString(Resource.String.no)));
|
|
}
|
|
|
|
view.FindViewById(Resource.Id.child_db_enable_on_this_device).Visibility = !deviceEnabled && autoExecItem.Enabled ? ViewStates.Visible : ViewStates.Gone;
|
|
view.FindViewById(Resource.Id.child_db_disable_on_this_device).Visibility = (deviceEnabled || !deviceEnabledExplict) && autoExecItem.Enabled ? ViewStates.Visible : ViewStates.Gone;
|
|
view.FindViewById(Resource.Id.child_db_enable_a_copy_for_this_device_container).Visibility = !deviceEnabled && autoExecItem.Enabled ? ViewStates.Visible : ViewStates.Gone;
|
|
view.FindViewById(Resource.Id.child_db_edit).Visibility = deviceEnabledExplict || !autoExecItem.Enabled ? ViewStates.Visible : ViewStates.Gone;
|
|
IOConnectionInfo ioc;
|
|
if ((KeeAutoExecExt.TryGetDatabaseIoc(autoExecItem, out ioc)) && App.Kp2a.TryGetDatabase(ioc) == null)
|
|
{
|
|
view.FindViewById(Resource.Id.child_db_open).Visibility = ViewStates.Visible;
|
|
}
|
|
else view.FindViewById(Resource.Id.child_db_open).Visibility = ViewStates.Gone;
|
|
|
|
|
|
Database db = App.Kp2a.FindDatabaseForElement(pw);
|
|
|
|
bool isExpired = pw.Expires && pw.ExpiryTime < DateTime.Now;
|
|
if (isExpired)
|
|
{
|
|
db.DrawableFactory.AssignDrawableTo(iv, _context, db.KpDatabase, PwIcon.Expired, PwUuid.Zero, false);
|
|
}
|
|
else
|
|
{
|
|
db.DrawableFactory.AssignDrawableTo(iv, _context, db.KpDatabase, pw.IconId, pw.CustomIconUuid, false);
|
|
}
|
|
|
|
|
|
|
|
view.Tag = position.ToString();
|
|
|
|
return view;
|
|
}
|
|
|
|
private static int GetClickedPos(View sending_view)
|
|
{
|
|
View viewWithTag = sending_view;
|
|
while (viewWithTag.Tag == null)
|
|
viewWithTag = (View) viewWithTag.Parent;
|
|
int clicked_pos = int.Parse((string) viewWithTag.Tag);
|
|
return clicked_pos;
|
|
}
|
|
|
|
public override int Count
|
|
{
|
|
get { return _displayedChildDatabases.Count; }
|
|
}
|
|
|
|
public void Update()
|
|
{
|
|
|
|
_displayedChildDatabases = KeeAutoExecExt.GetAutoExecItems(App.Kp2a.CurrentDb.KpDatabase)
|
|
.Where(e => App.Kp2a.TryFindDatabaseForElement(e.Entry) != null) //Update() can be called while we're adding entries to the database. They may be part of the group but without saving complete
|
|
.OrderBy(e => SprEngine.Compile(e.Entry.Strings.ReadSafe(PwDefs.TitleField),new SprContext(e.Entry, App.Kp2a.FindDatabaseForElement(e.Entry).KpDatabase, SprCompileFlags.All)))
|
|
.ThenByDescending(e => e.Entry.LastModificationTime)
|
|
.ToList();
|
|
}
|
|
}
|
|
|
|
private void OnOpen(AutoExecItem item)
|
|
{
|
|
KeeAutoExecExt.AutoOpenEntry(this, item, true, new ActivityLaunchModeSimple());
|
|
}
|
|
|
|
private void OnEnableCopy(AutoExecItem item)
|
|
{
|
|
//disable this device for the cloned entry
|
|
KeeAutoExecExt.SetDeviceEnabled(item, KeeAutoExecExt.ThisDeviceId, false);
|
|
//remember the original device settings
|
|
ProtectedString ifDeviceOrig = item.Entry.Strings.GetSafe(KeeAutoExecExt._ifDevice);
|
|
//reset device settings so only the current device is enabled
|
|
item.Entry.Strings.Set(KeeAutoExecExt._ifDevice,new ProtectedString(false,""));
|
|
KeeAutoExecExt.SetDeviceEnabled(item, KeeAutoExecExt.ThisDeviceId, true);
|
|
//now clone
|
|
var newEntry = item.Entry.CloneDeep();
|
|
//reset device settings
|
|
item.Entry.Strings.Set(KeeAutoExecExt._ifDevice, ifDeviceOrig);
|
|
newEntry.SetUuid(new PwUuid(true), true); // Create new UUID
|
|
string strTitle = newEntry.Strings.ReadSafe(PwDefs.TitleField);
|
|
newEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, strTitle + " (" + Android.OS.Build.Model + ")"));
|
|
var addTask = new AddEntry(this, App.Kp2a.CurrentDb, App.Kp2a, newEntry,item.Entry.ParentGroup,new ActionOnFinish(this, (success, message, activity) => ((ConfigureChildDatabasesActivity)activity).Update()));
|
|
|
|
ProgressTask pt = new ProgressTask(App.Kp2a, this, addTask);
|
|
pt.Run();
|
|
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
_adapter.Update();
|
|
_adapter.NotifyDataSetChanged();
|
|
}
|
|
|
|
private void OnEdit(AutoExecItem item)
|
|
{
|
|
EntryEditActivity.Launch(this,item.Entry,new NullTask());
|
|
}
|
|
|
|
private void OnDisable(AutoExecItem item)
|
|
{
|
|
KeeAutoExecExt.SetDeviceEnabled(item,KeeAutoExecExt.ThisDeviceId, false);
|
|
Save(item);
|
|
}
|
|
|
|
private void OnEnable(AutoExecItem item)
|
|
{
|
|
KeeAutoExecExt.SetDeviceEnabled(item, KeeAutoExecExt.ThisDeviceId, true);
|
|
Save(item);
|
|
}
|
|
|
|
private void Save(AutoExecItem item)
|
|
{
|
|
var addTask = new SaveDb(this, App.Kp2a, App.Kp2a.FindDatabaseForElement(item.Entry), new ActionOnFinish(this, (success, message, activity) => ((ConfigureChildDatabasesActivity)activity).Update()));
|
|
|
|
ProgressTask pt = new ProgressTask(App.Kp2a, this, addTask);
|
|
pt.Run();
|
|
}
|
|
|
|
protected override void OnResume()
|
|
{
|
|
base.OnResume();
|
|
_adapter.Update();
|
|
_adapter.NotifyDataSetChanged();
|
|
}
|
|
|
|
|
|
protected override void OnCreate(Bundle savedInstanceState)
|
|
{
|
|
|
|
base.OnCreate(savedInstanceState);
|
|
|
|
SetContentView(Resource.Layout.config_child_db);
|
|
|
|
_adapter = new ChildDatabasesAdapter(this);
|
|
var listView = FindViewById<ListView>(Android.Resource.Id.List);
|
|
listView.Adapter = _adapter;
|
|
|
|
SetSupportActionBar(FindViewById<AndroidX.AppCompat.Widget.Toolbar>(Resource.Id.mytoolbar));
|
|
|
|
FindViewById<Button>(Resource.Id.add_child_db_button).Click += (sender, args) =>
|
|
{
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
builder.SetTitle(Resource.String.add_child_db);
|
|
|
|
List<string> items = new List<string>();
|
|
Dictionary<int, Database> indexToDb = new Dictionary<int, Database>();
|
|
|
|
foreach (var db in App.Kp2a.OpenDatabases)
|
|
{
|
|
if (db != App.Kp2a.CurrentDb)
|
|
{
|
|
indexToDb[items.Count] = db;
|
|
items.Add(App.Kp2a.GetFileStorage(db.Ioc).GetDisplayName(db.Ioc));
|
|
}
|
|
}
|
|
indexToDb[items.Count] = null;
|
|
items.Add(GetString(Resource.String.open_other_db));
|
|
|
|
builder.SetItems(items.ToArray(), (o, eventArgs) =>
|
|
{
|
|
Database db;
|
|
if (!indexToDb.TryGetValue(eventArgs.Which, out db) || (db == null))
|
|
{
|
|
StartFileSelect();
|
|
}
|
|
else
|
|
{
|
|
AddAutoOpenEntryForDatabase(db);
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
AlertDialog dialog = builder.Create();
|
|
dialog.Show();
|
|
};
|
|
}
|
|
|
|
private void AddAutoOpenEntryForDatabase(Database db)
|
|
{
|
|
PwGroup autoOpenGroup = null;
|
|
var rootGroup = App.Kp2a.CurrentDb.KpDatabase.RootGroup;
|
|
foreach (PwGroup pgSub in rootGroup.Groups)
|
|
{
|
|
if (pgSub.Name == "AutoOpen")
|
|
{
|
|
autoOpenGroup = pgSub;
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
if (autoOpenGroup == null)
|
|
{
|
|
AddGroup addGroupTask = AddGroup.GetInstance(this, App.Kp2a, "AutoOpen", 1, null, rootGroup, null, true);
|
|
addGroupTask.Run();
|
|
autoOpenGroup = addGroupTask.Group;
|
|
}
|
|
|
|
PwEntry newEntry = new PwEntry(true, true);
|
|
newEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, App.Kp2a.GetFileStorage(db.Ioc).GetDisplayName(db.Ioc)));
|
|
newEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, TryMakeRelativePath(App.Kp2a.CurrentDb, db.Ioc)));
|
|
var password = db.KpDatabase.MasterKey.GetUserKey<KcpPassword>();
|
|
newEntry.Strings.Set(PwDefs.PasswordField, password == null ? new ProtectedString(true, "") : password.Password);
|
|
|
|
var keyfile = db.KpDatabase.MasterKey.GetUserKey<KcpKeyFile>();
|
|
if ((keyfile != null) && (keyfile.Ioc != null))
|
|
{
|
|
newEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, TryMakeRelativePath(App.Kp2a.CurrentDb, keyfile.Ioc)));
|
|
}
|
|
|
|
newEntry.Strings.Set(KeeAutoExecExt._ifDevice,
|
|
new ProtectedString(false,
|
|
KeeAutoExecExt.BuildIfDevice(new Dictionary<string, bool>()
|
|
{
|
|
{KeeAutoExecExt.ThisDeviceId, true}
|
|
})));
|
|
|
|
var addTask = new AddEntry(this, db, App.Kp2a, newEntry, autoOpenGroup, new ActionOnFinish(this, (success, message, activity) => (activity as ConfigureChildDatabasesActivity)?.Update()));
|
|
|
|
ProgressTask pt = new ProgressTask(App.Kp2a, this, addTask);
|
|
pt.Run();
|
|
}
|
|
|
|
private string TryMakeRelativePath(Database db, IOConnectionInfo ioc)
|
|
{
|
|
try
|
|
{
|
|
var fsDb = App.Kp2a.GetFileStorage(db.Ioc);
|
|
var dbParent = fsDb.GetParentPath(db.Ioc).Path + "/";
|
|
if (ioc.Path.StartsWith(dbParent))
|
|
{
|
|
return "{DB_DIR}{ENV_DIRSEP}" + ioc.Path.Substring(dbParent.Length);
|
|
}
|
|
|
|
}
|
|
catch (NoFileStorageFoundException)
|
|
{
|
|
}
|
|
return ioc.Path;
|
|
|
|
}
|
|
|
|
private void StartFileSelect(bool noForwardToPassword = false)
|
|
{
|
|
Intent intent = new Intent(this, typeof(FileSelectActivity));
|
|
intent.PutExtra(FileSelectActivity.NoForwardToPasswordActivity, noForwardToPassword);
|
|
intent.PutExtra("MakeCurrent", false);
|
|
StartActivityForResult(intent, ReqCodeOpenNewDb);
|
|
}
|
|
|
|
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
|
{
|
|
base.OnActivityResult(requestCode, resultCode, data);
|
|
|
|
if (requestCode == ReqCodeOpenNewDb)
|
|
{
|
|
switch ((int)resultCode)
|
|
{
|
|
case (int)Result.Ok:
|
|
|
|
string iocString = data?.GetStringExtra("ioc");
|
|
IOConnectionInfo ioc = IOConnectionInfo.UnserializeFromString(iocString);
|
|
var db = App.Kp2a.TryGetDatabase(ioc);
|
|
if (db != null)
|
|
AddAutoOpenEntryForDatabase(db);
|
|
|
|
break;
|
|
case PasswordActivity.ResultSelectOtherFile:
|
|
StartFileSelect(true);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
public const int ReqCodeOpenNewDb = 1;
|
|
|
|
|
|
|
|
}
|
|
} |