948 lines
25 KiB
C#
948 lines
25 KiB
C#
using System;
|
|
using System.Globalization;
|
|
using Android.App;
|
|
using Android.Content;
|
|
using Android.OS;
|
|
using Android.Widget;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using KeePassLib;
|
|
using KeePassLib.Security;
|
|
using KeePassLib.Utility;
|
|
|
|
namespace keepass2android
|
|
{
|
|
/// <summary>
|
|
/// Interface for data stored in an intent or bundle as extra string
|
|
/// </summary>
|
|
public interface IExtra
|
|
{
|
|
/// <summary>
|
|
/// put data to a bundle by calling one of the PutXX methods
|
|
/// </summary>
|
|
void ToBundle(Bundle b);
|
|
|
|
/// <summary>
|
|
/// Put data to an intent by calling PutExtra
|
|
/// </summary>
|
|
void ToIntent(Intent i);
|
|
}
|
|
|
|
/// <summary>
|
|
/// represents data stored in an intent or bundle as extra string
|
|
/// </summary>
|
|
public class StringExtra: IExtra
|
|
{
|
|
public string Key { get; set; }
|
|
public string Value{ get; set; }
|
|
|
|
#region IExtra implementation
|
|
|
|
public void ToBundle(Bundle b)
|
|
{
|
|
b.PutString(Key, Value);
|
|
}
|
|
|
|
public void ToIntent(Intent i)
|
|
{
|
|
i.PutExtra(Key, Value);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// represents data stored in an intent or bundle as extra string array list
|
|
/// </summary>
|
|
public class StringArrayListExtra : IExtra
|
|
{
|
|
public string Key { get; set; }
|
|
public IList<string> Value { get; set; }
|
|
|
|
#region IExtra implementation
|
|
|
|
public void ToBundle(Bundle b)
|
|
{
|
|
b.PutStringArrayList(Key, Value);
|
|
}
|
|
|
|
public void ToIntent(Intent i)
|
|
{
|
|
i.PutStringArrayListExtra(Key, Value);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// represents data stored in an intent or bundle as extra int
|
|
/// </summary>
|
|
public class IntExtra: IExtra
|
|
{
|
|
public string Key { get; set; }
|
|
public int Value{ get; set; }
|
|
|
|
#region IExtra implementation
|
|
|
|
public void ToBundle(Bundle b)
|
|
{
|
|
b.PutInt(Key, Value);
|
|
}
|
|
|
|
public void ToIntent(Intent i)
|
|
{
|
|
i.PutExtra(Key, Value);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// represents data stored in an intent or bundle as extra bool
|
|
/// </summary>
|
|
public class BoolExtra: IExtra
|
|
{
|
|
public string Key { get; set; }
|
|
public bool Value{ get; set; }
|
|
|
|
#region IExtra implementation
|
|
|
|
public void ToBundle(Bundle b)
|
|
{
|
|
b.PutBoolean(Key, Value);
|
|
}
|
|
|
|
public void ToIntent(Intent i)
|
|
{
|
|
i.PutExtra(Key, Value);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// represents data stored in an intent or bundle as extra string array
|
|
/// </summary>
|
|
public class StringArrayExtra : IExtra
|
|
{
|
|
public string Key { get; set; }
|
|
public string[] Value { get; set; }
|
|
|
|
#region IExtra implementation
|
|
|
|
public void ToBundle(Bundle b)
|
|
{
|
|
b.PutStringArray(Key, Value);
|
|
}
|
|
|
|
public void ToIntent(Intent i)
|
|
{
|
|
i.PutExtra(Key, Value);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// base class for "tasks": these are things the user wants to do and which require several activities
|
|
/// </summary>
|
|
/// Therefore AppTasks need to be serializable to bundles and intents to "survive" saving to instance state and changing activities.
|
|
/// An AppTask has a type and may have several parameters ("extras").
|
|
/// Activities call the task at special points so tasks can change the behaviour at these points.
|
|
public abstract class AppTask
|
|
{
|
|
/// <summary>
|
|
/// Loads the parameters of the task from the given bundle
|
|
/// </summary>
|
|
public virtual void Setup(Bundle b)
|
|
{}
|
|
|
|
/// <summary>
|
|
/// Returns the parameters of the task for storage in a bundle or intent
|
|
/// </summary>
|
|
/// <value>The extras.</value>
|
|
public virtual IEnumerable<IExtra> Extras {
|
|
get
|
|
{
|
|
yield break;
|
|
}
|
|
}
|
|
|
|
public virtual void AfterUnlockDatabase(PasswordActivity act)
|
|
{
|
|
GroupActivity.Launch(act, this);
|
|
}
|
|
|
|
public virtual void AfterAddNewEntry(EntryEditActivity entryEditActivity, PwEntry newEntry)
|
|
{
|
|
}
|
|
|
|
|
|
public virtual void PrepareNewEntry(PwEntry newEntry)
|
|
{
|
|
|
|
}
|
|
|
|
public const String AppTaskKey = "KP2A_APPTASK";
|
|
|
|
/// <summary>
|
|
/// Should be used in OnCreate to (re)create a task
|
|
/// if savedInstanceState is not null, the task is recreated from there. Otherwise it's taken from the intent.
|
|
/// </summary>
|
|
public static AppTask GetTaskInOnCreate(Bundle savedInstanceState, Intent intent)
|
|
{
|
|
AppTask task;
|
|
if (savedInstanceState != null)
|
|
{
|
|
task = CreateFromBundle(savedInstanceState);
|
|
}
|
|
else
|
|
{
|
|
task = CreateFromIntent(intent);
|
|
}
|
|
Kp2aLog.Log("Loaded task " + task);
|
|
return task;
|
|
}
|
|
|
|
public static AppTask CreateFromIntent(Intent i)
|
|
{
|
|
return CreateFromBundle(i.Extras);
|
|
}
|
|
|
|
public static AppTask CreateFromBundle(Bundle b)
|
|
{
|
|
return CreateFromBundle(b, new NullTask());
|
|
}
|
|
|
|
public static AppTask CreateFromBundle(Bundle b, AppTask failureReturn)
|
|
{
|
|
if (b == null)
|
|
return failureReturn;
|
|
|
|
string taskType = b.GetString(AppTaskKey);
|
|
|
|
if (string.IsNullOrEmpty(taskType))
|
|
return failureReturn;
|
|
|
|
try
|
|
{
|
|
Type type = Type.GetType("keepass2android." + taskType);
|
|
if (type == null)
|
|
return failureReturn;
|
|
AppTask task = (AppTask)Activator.CreateInstance(type);
|
|
task.Setup(b);
|
|
return task;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Kp2aLog.Log("Cannot convert " + taskType + " in task: " + e);
|
|
return failureReturn;
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the extras of the task to the intent
|
|
/// </summary>
|
|
public void ToIntent(Intent intent)
|
|
{
|
|
GetTypeExtra(GetType()).ToIntent(intent);
|
|
|
|
foreach (IExtra extra in Extras)
|
|
{
|
|
extra.ToIntent(intent);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the extras of the task to the bundle
|
|
/// </summary>
|
|
public void ToBundle(Bundle bundle)
|
|
{
|
|
GetTypeExtra(GetType()).ToBundle(bundle);
|
|
|
|
foreach (IExtra extra in Extras)
|
|
{
|
|
extra.ToBundle(bundle);
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an IExtra which must be part of the Extras of a task to describe the type
|
|
/// </summary>
|
|
static IExtra GetTypeExtra(Type type)
|
|
{
|
|
return new StringExtra { Key=AppTaskKey, Value=type.Name};
|
|
}
|
|
|
|
public virtual void StartInGroupActivity(GroupBaseActivity groupBaseActivity)
|
|
{
|
|
}
|
|
|
|
public virtual void SetupGroupBaseActivityButtons(GroupBaseActivity groupBaseActivity)
|
|
{
|
|
groupBaseActivity.SetupNormalButtons();
|
|
}
|
|
|
|
public void SetActivityResult(Activity activity, Result result)
|
|
{
|
|
Intent data = new Intent();
|
|
ToIntent(data);
|
|
activity.SetResult(result, data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to extract the task from the data given as an Intent object in OnActivityResult. If successful, the task is assigned,
|
|
/// otherwise, false is returned.
|
|
/// </summary>
|
|
public static bool TryGetFromActivityResult(Intent data, ref AppTask task)
|
|
{
|
|
if (data == null)
|
|
{
|
|
Kp2aLog.Log("TryGetFromActivityResult: no data");
|
|
return false;
|
|
}
|
|
AppTask tempTask = CreateFromBundle(data.Extras, null);
|
|
if (tempTask == null)
|
|
{
|
|
Kp2aLog.Log("No AppTask in OnActivityResult");
|
|
return false;
|
|
}
|
|
|
|
task = tempTask;
|
|
Kp2aLog.Log("AppTask " +task+" in OnActivityResult");
|
|
return true;
|
|
}
|
|
|
|
protected void RemoveTaskFromIntent(Activity act)
|
|
{
|
|
if (act.Intent != null)
|
|
act.Intent.RemoveExtra(AppTaskKey);
|
|
|
|
}
|
|
|
|
public virtual void CompleteOnCreateEntryActivity(EntryActivity activity)
|
|
{
|
|
activity.StartNotificationsService(false);
|
|
}
|
|
|
|
virtual public void PopulatePasswordAccessServiceIntent(Intent intent)
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implementation of AppTask for "no task currently active" (Null pattern)
|
|
/// </summary>
|
|
public class NullTask: AppTask
|
|
{
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// User is about to search an entry for a given URL
|
|
/// </summary>
|
|
/// Derive from SelectEntryTask. This means that as soon as an Entry is opened, we're returning with
|
|
/// ExitAfterTaskComplete. This also allows te specify the flag if we need to display the user notifications.
|
|
public class SearchUrlTask: SelectEntryTask
|
|
{
|
|
public const String UrlToSearchKey = "UrlToSearch";
|
|
|
|
public string UrlToSearchFor
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public override void Setup(Bundle b)
|
|
{
|
|
base.Setup(b);
|
|
UrlToSearchFor = b.GetString(UrlToSearchKey);
|
|
}
|
|
public override IEnumerable<IExtra> Extras
|
|
{
|
|
get
|
|
{
|
|
foreach (IExtra e in base.Extras)
|
|
yield return e;
|
|
yield return new StringExtra { Key=UrlToSearchKey, Value = UrlToSearchFor };
|
|
}
|
|
}
|
|
public override void AfterUnlockDatabase(PasswordActivity act)
|
|
{
|
|
if (String.IsNullOrEmpty(UrlToSearchFor))
|
|
{
|
|
GroupActivity.Launch(act, new SelectEntryTask() { ShowUserNotifications = ShowUserNotifications});
|
|
}
|
|
else
|
|
{
|
|
ShareUrlResults.Launch(act, this);
|
|
}
|
|
|
|
|
|
//removed. this causes an issue in the following workflow:
|
|
//When the user wants to find an entry for a URL but has the wrong database open he needs
|
|
//to switch to another database. But the Task is removed already the first time when going through PasswordActivity
|
|
// (with the wrong db).
|
|
//Then after switching to the right database, the task is gone.
|
|
|
|
//A reason this code existed was the following workflow:
|
|
//Using Chrome browser (with NEW_TASK flag for ActionSend): Share URL -> KP2A.
|
|
//Now the AppTask was in PasswordActivity and didn't get out of it.
|
|
//This is now solved by returning new tasks in ActivityResult.
|
|
|
|
//RemoveTaskFromIntent(act);
|
|
//act.AppTask = new NullTask();
|
|
}
|
|
|
|
public override void PopulatePasswordAccessServiceIntent(Intent intent)
|
|
{
|
|
base.PopulatePasswordAccessServiceIntent(intent);
|
|
intent.PutExtra(UrlToSearchKey, UrlToSearchFor);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// User is about to select an entry for use in another app
|
|
/// </summary>
|
|
public class SelectEntryTask: AppTask
|
|
{
|
|
public SelectEntryTask()
|
|
{
|
|
ShowUserNotifications = true;
|
|
CloseAfterCreate = true;
|
|
}
|
|
|
|
public const String ShowUserNotificationsKey = "ShowUserNotifications";
|
|
|
|
public bool ShowUserNotifications { get; set; }
|
|
|
|
public const String CloseAfterCreateKey = "CloseAfterCreate";
|
|
|
|
public bool CloseAfterCreate { get; set; }
|
|
|
|
|
|
public override void Setup(Bundle b)
|
|
{
|
|
ShowUserNotifications = GetBoolFromBundle(b, ShowUserNotificationsKey, true);
|
|
CloseAfterCreate = GetBoolFromBundle(b, CloseAfterCreateKey, true);
|
|
}
|
|
|
|
private static bool GetBoolFromBundle(Bundle b, string key, bool defaultValue)
|
|
{
|
|
bool boolValue;
|
|
if (!Boolean.TryParse(b.GetString(key), out boolValue))
|
|
{
|
|
boolValue = defaultValue;
|
|
}
|
|
return boolValue;
|
|
}
|
|
|
|
public override IEnumerable<IExtra> Extras
|
|
{
|
|
get
|
|
{
|
|
yield return new StringExtra { Key = ShowUserNotificationsKey, Value = ShowUserNotifications.ToString() };
|
|
yield return new StringExtra { Key = CloseAfterCreateKey, Value = CloseAfterCreate.ToString() };
|
|
}
|
|
}
|
|
|
|
public override void CompleteOnCreateEntryActivity(EntryActivity activity)
|
|
{
|
|
if (ShowUserNotifications)
|
|
{
|
|
//show the notifications
|
|
activity.StartNotificationsService(CloseAfterCreate);
|
|
}
|
|
else
|
|
{
|
|
//to avoid getting into inconsistent state (LastOpenedEntry and Notifications): clear notifications:
|
|
CopyToClipboardService.CancelNotifications(activity);
|
|
}
|
|
if (CloseAfterCreate)
|
|
{
|
|
//close
|
|
activity.CloseAfterTaskComplete();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// User is about to select an entry. When selected, ask whether the url he was searching for earlier should be stored
|
|
/// in the selected entry for later use.
|
|
/// </summary>
|
|
public class SelectEntryForUrlTask: SelectEntryTask
|
|
{
|
|
/// <summary>
|
|
/// default constructor for creating from Bundle
|
|
/// </summary>
|
|
public SelectEntryForUrlTask()
|
|
{
|
|
|
|
}
|
|
|
|
public SelectEntryForUrlTask(string url)
|
|
{
|
|
UrlToSearchFor = url;
|
|
ShowUserNotifications = true;
|
|
}
|
|
|
|
public const String UrlToSearchKey = "UrlToSearch";
|
|
|
|
public string UrlToSearchFor
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public override void Setup(Bundle b)
|
|
{
|
|
base.Setup(b);
|
|
UrlToSearchFor = b.GetString(UrlToSearchKey);
|
|
}
|
|
public override IEnumerable<IExtra> Extras
|
|
{
|
|
get
|
|
{
|
|
foreach (IExtra e in base.Extras)
|
|
yield return e;
|
|
yield return new StringExtra { Key = UrlToSearchKey, Value = UrlToSearchFor };
|
|
}
|
|
}
|
|
|
|
public override void CompleteOnCreateEntryActivity(EntryActivity activity)
|
|
{
|
|
//if the database is readonly (or no URL exists), don't offer to modify the URL
|
|
if ((App.Kp2a.GetDb().CanWrite == false) || (String.IsNullOrEmpty(UrlToSearchFor)))
|
|
{
|
|
base.CompleteOnCreateEntryActivity(activity);
|
|
return;
|
|
}
|
|
|
|
|
|
AskAddUrlThenCompleteCreate(activity, UrlToSearchFor);
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// brings up a dialog asking the user whether he wants to add the given URL to the entry for automatic finding
|
|
/// </summary>
|
|
public void AskAddUrlThenCompleteCreate(EntryActivity activity, string url)
|
|
{
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
|
builder.SetTitle(activity.GetString(Resource.String.AddUrlToEntryDialog_title));
|
|
|
|
builder.SetMessage(activity.GetString(Resource.String.AddUrlToEntryDialog_text, new Java.Lang.Object[] { url }));
|
|
|
|
builder.SetPositiveButton(activity.GetString(Resource.String.yes), (dlgSender, dlgEvt) =>
|
|
{
|
|
activity.AddUrlToEntry(url, () => base.CompleteOnCreateEntryActivity(activity));
|
|
});
|
|
|
|
builder.SetNegativeButton(activity.GetString(Resource.String.no), (dlgSender, dlgEvt) =>
|
|
{
|
|
base.CompleteOnCreateEntryActivity(activity);
|
|
});
|
|
|
|
Dialog dialog = builder.Create();
|
|
dialog.Show();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// User is about to move entries and/or groups to another group
|
|
/// </summary>
|
|
public class MoveElementsTask: AppTask
|
|
{
|
|
public const String UuidsKey = "MoveElement_Uuids";
|
|
|
|
public IEnumerable<PwUuid> Uuids
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public override void Setup(Bundle b)
|
|
{
|
|
Uuids = b.GetString(UuidsKey).Split(';')
|
|
.Where(s => !String.IsNullOrEmpty(s))
|
|
.Select(stringPart => new PwUuid(MemUtil.HexStringToByteArray(stringPart)))
|
|
.ToList(); //property might be accessed several times, avoid parsing each time
|
|
}
|
|
|
|
public override IEnumerable<IExtra> Extras
|
|
{
|
|
get
|
|
{
|
|
yield return new StringExtra
|
|
{
|
|
Key = UuidsKey,
|
|
Value = Uuids.Select(uuid => MemUtil.ByteArrayToHexString(uuid.UuidBytes))
|
|
.Aggregate((a, b) => a + ";" + b)
|
|
};
|
|
}
|
|
}
|
|
|
|
public override void StartInGroupActivity(GroupBaseActivity groupBaseActivity)
|
|
{
|
|
base.StartInGroupActivity(groupBaseActivity);
|
|
groupBaseActivity.StartMovingElements();
|
|
}
|
|
public override void SetupGroupBaseActivityButtons(GroupBaseActivity groupBaseActivity)
|
|
{
|
|
groupBaseActivity.ShowInsertElementsButtons();
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// User is about to create a new entry. The task might already "know" some information about the contents.
|
|
/// </summary>
|
|
public class CreateEntryThenCloseTask: AppTask
|
|
{
|
|
public CreateEntryThenCloseTask()
|
|
{
|
|
ShowUserNotifications = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// extra key if only a URL is passed. optional.
|
|
/// </summary>
|
|
public const String UrlKey = "CreateEntry_Url";
|
|
|
|
/// <summary>
|
|
/// extra key if a json serialized key/value mapping is passed. optional.
|
|
/// </summary>
|
|
/// Uses the PluginSDKs keys because this is mainly used for communicating with plugins.
|
|
/// Of course the data might also contain "non-output-data" (e.g. placeholders), but usually won't.
|
|
public const String AllFieldsKey = Keepass2android.Pluginsdk.Strings.ExtraEntryOutputData;
|
|
|
|
/// <summary>
|
|
/// extra key to specify a list of protected field keys in AllFieldsKey. Passed as StringArrayExtra. optional.
|
|
/// </summary>
|
|
public const String ProtectedFieldsListKey = Keepass2android.Pluginsdk.Strings.ExtraProtectedFieldsList;
|
|
|
|
|
|
/// <summary>
|
|
/// Extra key to specify whether user notifications (e.g. for copy password or keyboard) should be displayed when the entry
|
|
/// is selected after creating.
|
|
/// </summary>
|
|
public const String ShowUserNotificationsKey = "ShowUserNotifications";
|
|
|
|
|
|
public string Url { get; set; }
|
|
|
|
public string AllFields { get; set; }
|
|
|
|
public IList<string> ProtectedFieldsList { get; set; }
|
|
|
|
public bool ShowUserNotifications { get; set; }
|
|
|
|
|
|
public override void Setup(Bundle b)
|
|
{
|
|
bool showUserNotification;
|
|
if (!Boolean.TryParse(b.GetString(ShowUserNotificationsKey), out showUserNotification))
|
|
{
|
|
showUserNotification = true; //default to true
|
|
}
|
|
ShowUserNotifications = showUserNotification;
|
|
|
|
Url = b.GetString(UrlKey);
|
|
AllFields = b.GetString(AllFieldsKey);
|
|
ProtectedFieldsList = b.GetStringArrayList(ProtectedFieldsListKey);
|
|
}
|
|
public override IEnumerable<IExtra> Extras
|
|
{
|
|
get
|
|
{
|
|
if (Url != null)
|
|
yield return new StringExtra { Key = UrlKey, Value = Url };
|
|
if (AllFields != null)
|
|
yield return new StringExtra { Key = AllFieldsKey, Value = AllFields };
|
|
if (ProtectedFieldsList != null)
|
|
yield return new StringArrayListExtra { Key = ProtectedFieldsListKey, Value = ProtectedFieldsList };
|
|
|
|
yield return new StringExtra { Key = ShowUserNotificationsKey, Value = ShowUserNotifications.ToString() };
|
|
}
|
|
}
|
|
|
|
|
|
public override void PrepareNewEntry(PwEntry newEntry)
|
|
{
|
|
if (Url != null)
|
|
{
|
|
newEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, Url));
|
|
}
|
|
if (AllFields != null)
|
|
{
|
|
|
|
var allFields = new Org.Json.JSONObject(AllFields);
|
|
for (var iter = allFields.Keys(); iter.HasNext; )
|
|
{
|
|
string key = iter.Next().ToString();
|
|
string value = allFields.Get(key).ToString();
|
|
bool isProtected = ((ProtectedFieldsList != null) && (ProtectedFieldsList.Contains(key)))
|
|
|| (key == PwDefs.PasswordField);
|
|
newEntry.Strings.Set(key, new ProtectedString(isProtected, value));
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
public override void AfterAddNewEntry(EntryEditActivity entryEditActivity, PwEntry newEntry)
|
|
{
|
|
EntryActivity.Launch(entryEditActivity, newEntry, -1,
|
|
new SelectEntryTask { ShowUserNotifications = this.ShowUserNotifications},
|
|
ActivityFlags.ForwardResult);
|
|
//no need to call Finish here, that's done in EntryEditActivity ("closeOrShowError")
|
|
}
|
|
|
|
public override void CompleteOnCreateEntryActivity(EntryActivity activity)
|
|
{
|
|
//if the user selects an entry before creating the new one, we're not closing the app
|
|
base.CompleteOnCreateEntryActivity(activity);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Navigate to a folder and open a Task (appear in SearchResult)
|
|
/// </summary>
|
|
public abstract class NavigateAndLaunchTask: AppTask
|
|
{
|
|
// All group Uuid are stored in guuidKey + indice
|
|
// The last one is the destination group
|
|
public const String NumberOfGroupsKey = "NumberOfGroups";
|
|
public const String GUuidKey = "gUuidKey";
|
|
public const String FullGroupNameKey = "fullGroupNameKey";
|
|
public const String ToastEnableKey = "toastEnableKey";
|
|
|
|
#if INCLUDE_DEBUG_MOVE_GROUPNAME
|
|
public const String gNameKey = "gNameKey";
|
|
private LinkedList<string> groupNameList;
|
|
#endif
|
|
|
|
private LinkedList<string> _groupUuid;
|
|
protected AppTask TaskToBeLaunchedAfterNavigation;
|
|
|
|
protected String FullGroupName {
|
|
get ;
|
|
set ;
|
|
}
|
|
|
|
protected bool ToastEnable {
|
|
get;
|
|
set;
|
|
}
|
|
|
|
protected NavigateAndLaunchTask() {
|
|
TaskToBeLaunchedAfterNavigation = new NullTask();
|
|
FullGroupName = "";
|
|
ToastEnable = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="keepass2android.NavigateAndLaunchTask"/> class.
|
|
/// </summary>
|
|
/// <param name="groups">Groups.</param>
|
|
/// <param name="taskToBeLaunchedAfterNavigation">Task to be launched after navigation.</param>
|
|
/// <param name="toastEnable">If set to <c>true</c>, toast will be displayed after navigation.</param>
|
|
protected NavigateAndLaunchTask(PwGroup groups, AppTask taskToBeLaunchedAfterNavigation, bool toastEnable = false) {
|
|
TaskToBeLaunchedAfterNavigation = taskToBeLaunchedAfterNavigation;
|
|
PopulateGroups (groups);
|
|
ToastEnable = toastEnable;
|
|
}
|
|
|
|
public void PopulateGroups(PwGroup groups) {
|
|
|
|
_groupUuid = new LinkedList<String>();
|
|
|
|
#if INCLUDE_DEBUG_MOVE_GROUPNAME
|
|
groupNameList = new LinkedList<String>{};
|
|
#endif
|
|
|
|
FullGroupName = "";
|
|
PwGroup readGroup = groups;
|
|
while (readGroup != null) {
|
|
|
|
if ( (readGroup.ParentGroup != null) ||
|
|
(readGroup.ParentGroup == null) && (readGroup == groups) ) {
|
|
FullGroupName = readGroup.Name + "." + FullGroupName;
|
|
}
|
|
|
|
_groupUuid.AddFirst (MemUtil.ByteArrayToHexString (readGroup.Uuid.UuidBytes));
|
|
|
|
#if INCLUDE_DEBUG_MOVE_GROUPNAME
|
|
groupNameList.AddFirst (readGroup.Name);
|
|
#endif
|
|
|
|
readGroup = readGroup.ParentGroup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Loads the parameters of the task from the given bundle. Embeded task is not setup from this bundle
|
|
/// </summary>
|
|
/// <param name="b">The bundle component.</param>
|
|
public override void Setup(Bundle b)
|
|
{
|
|
int numberOfGroups = b.GetInt(NumberOfGroupsKey);
|
|
|
|
_groupUuid = new LinkedList<String>();
|
|
#if INCLUDE_DEBUG_MOVE_GROUPNAME
|
|
groupNameList = new LinkedList<String>{};
|
|
#endif
|
|
|
|
int i = 0;
|
|
|
|
while (i < numberOfGroups) {
|
|
|
|
_groupUuid.AddLast ( b.GetString (GUuidKey + i) ) ;
|
|
|
|
#if INCLUDE_DEBUG_MOVE_GROUPNAME
|
|
groupNameList.AddLast ( b.GetString (gNameKey + i);
|
|
#endif
|
|
i++;
|
|
}
|
|
|
|
FullGroupName = b.GetString (FullGroupNameKey);
|
|
ToastEnable = b.GetBoolean (ToastEnableKey);
|
|
|
|
}
|
|
|
|
public override IEnumerable<IExtra> Extras
|
|
{
|
|
get
|
|
{
|
|
// Return Navigate group Extras
|
|
IEnumerator<String> eGroupKeys = _groupUuid.GetEnumerator ();
|
|
|
|
#if INCLUDE_DEBUG_MOVE_GROUPNAME
|
|
IEnumerator<String> eGroupName = groupNameList.GetEnumerator ();
|
|
#endif
|
|
|
|
int i = 0;
|
|
while (eGroupKeys.MoveNext()) {
|
|
yield return new StringExtra { Key = GUuidKey + i.ToString (CultureInfo.InvariantCulture), Value = eGroupKeys.Current };
|
|
|
|
#if INCLUDE_DEBUG_MOVE_GROUPNAME
|
|
eGroupName.MoveNext();
|
|
yield return new StringExtra { Key = gNameKey + i.ToString (), Value = eGroupName.Current };
|
|
#endif
|
|
|
|
i++;
|
|
}
|
|
|
|
yield return new IntExtra{ Key = NumberOfGroupsKey, Value = i };
|
|
yield return new StringExtra{ Key = FullGroupNameKey, Value = FullGroupName };
|
|
yield return new BoolExtra{ Key = ToastEnableKey, Value = ToastEnable };
|
|
|
|
// Return afterTaskExtras
|
|
foreach (var extra in TaskToBeLaunchedAfterNavigation.Extras)
|
|
{
|
|
yield return extra;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
public override void StartInGroupActivity(GroupBaseActivity groupBaseActivity)
|
|
{
|
|
base.StartInGroupActivity(groupBaseActivity);
|
|
|
|
if (GroupIsFound(groupBaseActivity) ){ // Group has been found: display toaster and stop here
|
|
|
|
if (ToastEnable) {
|
|
String toastMessage = groupBaseActivity.GetString (Resource.String.NavigationToGroupCompleted_message, new Java.Lang.Object[] { FullGroupName});
|
|
|
|
Toast.MakeText (groupBaseActivity, toastMessage, ToastLength.Long).Show ();
|
|
}
|
|
|
|
groupBaseActivity.StartTask (TaskToBeLaunchedAfterNavigation);
|
|
return;
|
|
|
|
} else if (_groupUuid.Contains(groupBaseActivity.UuidGroup)) { // Need to go up in groups tree
|
|
|
|
// Get next Group Uuid
|
|
var linkedListNode = _groupUuid.Find(groupBaseActivity.UuidGroup);
|
|
if (linkedListNode != null)
|
|
{
|
|
//Note: Resharper says there is a possible NullRefException.
|
|
//This is not the case because it was checked above if we're already there or not.
|
|
String nextGroupUuid = linkedListNode.Next.Value;
|
|
PwUuid nextGroupPwUuid = new PwUuid (MemUtil.HexStringToByteArray (nextGroupUuid));
|
|
|
|
// Create Group Activity
|
|
PwGroup nextGroup = App.Kp2a.GetDb ().Groups [nextGroupPwUuid];
|
|
GroupActivity.Launch (groupBaseActivity, nextGroup, this);
|
|
}
|
|
return;
|
|
|
|
} else { // Need to go down in groups tree
|
|
SetActivityResult(groupBaseActivity, KeePass.ExitNormal);
|
|
groupBaseActivity.Finish();
|
|
|
|
}
|
|
|
|
}
|
|
public override void SetupGroupBaseActivityButtons(GroupBaseActivity groupBaseActivity)
|
|
{
|
|
|
|
}
|
|
|
|
public bool GroupIsFound(GroupBaseActivity groupBaseActivity)
|
|
{
|
|
return _groupUuid.Last.Value.Equals (groupBaseActivity.UuidGroup);
|
|
}
|
|
}
|
|
|
|
public class NavigateToFolder: NavigateAndLaunchTask {
|
|
|
|
public NavigateToFolder()
|
|
{
|
|
}
|
|
|
|
public NavigateToFolder(PwGroup groups, bool toastEnable = false)
|
|
: base(groups, new NullTask(), toastEnable)
|
|
{
|
|
}
|
|
|
|
}
|
|
|
|
public class NavigateToFolderAndLaunchMoveElementTask: NavigateAndLaunchTask {
|
|
|
|
public NavigateToFolderAndLaunchMoveElementTask()
|
|
{
|
|
|
|
}
|
|
|
|
|
|
public NavigateToFolderAndLaunchMoveElementTask(PwGroup groups, List<PwUuid> uuids, bool toastEnable = false)
|
|
:base(groups, new MoveElementsTask() { Uuids = uuids }, toastEnable) {
|
|
}
|
|
|
|
public override void Setup(Bundle b) {
|
|
base.Setup(b);
|
|
|
|
TaskToBeLaunchedAfterNavigation = new MoveElementsTask ();
|
|
TaskToBeLaunchedAfterNavigation.Setup (b);
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|