diff --git a/src/KeePassLib2Android/IDatabaseFormat.cs b/src/KeePassLib2Android/IDatabaseFormat.cs index 9cbd53b5..f9712a57 100644 --- a/src/KeePassLib2Android/IDatabaseFormat.cs +++ b/src/KeePassLib2Android/IDatabaseFormat.cs @@ -23,5 +23,6 @@ namespace KeePassLib bool SupportsTags { get; } bool SupportsOverrideUrl { get; } bool CanRecycle { get; } + bool SupportsTemplates { get; } } } \ No newline at end of file diff --git a/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj b/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj index 9a82467e..5c3bc088 100644 --- a/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj +++ b/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj @@ -56,6 +56,7 @@ + diff --git a/src/Kp2aBusinessLogic/UiStringKey.cs b/src/Kp2aBusinessLogic/UiStringKey.cs index a47a2a8d..58e6f989 100644 --- a/src/Kp2aBusinessLogic/UiStringKey.cs +++ b/src/Kp2aBusinessLogic/UiStringKey.cs @@ -63,6 +63,24 @@ namespace keepass2android AskDeletePermanentlyItems, AskDeletePermanentlyItemsNoRecycle, InOfflineMode, - DuplicateTitle + DuplicateTitle, + TemplateTitle_IdCard, + TemplateField_IdCard_Name, + TemplateField_IdCard_PlaceOfIssue, + TemplateField_IdCard_IssueDate, + TemplateTitle_EMail, + TemplateField_EMail_EMail, + TemplateTitle_WLan, + TemplateTitle_Notes, + TemplateField_WLan_SSID, + TemplateField_Number, + TemplateField_CreditCard_CVV, + TemplateField_CreditCard_PIN, + TemplateField_CreditCard_Owner, + TemplateTitle_CreditCard, + TemplateTitle_Membership, + TemplateGroupName, + AskAddTemplatesTitle, + AskAddTemplatesMessage } } diff --git a/src/Kp2aBusinessLogic/database/KdbDatabaseFormat.cs b/src/Kp2aBusinessLogic/database/KdbDatabaseFormat.cs index 668a9cc9..0f3b3b52 100644 --- a/src/Kp2aBusinessLogic/database/KdbDatabaseFormat.cs +++ b/src/Kp2aBusinessLogic/database/KdbDatabaseFormat.cs @@ -339,6 +339,11 @@ namespace keepass2android get { return false; } } + public bool SupportsTemplates + { + get { return false; } + } + private void AssignParent(PwGroup kpParent, PwDatabaseV3 dbV3, Dictionary groupV3s) { PwGroupV3 parentV3; diff --git a/src/Kp2aBusinessLogic/database/KdbxDatabaseFormat.cs b/src/Kp2aBusinessLogic/database/KdbxDatabaseFormat.cs index 21535d54..f8204bc4 100644 --- a/src/Kp2aBusinessLogic/database/KdbxDatabaseFormat.cs +++ b/src/Kp2aBusinessLogic/database/KdbxDatabaseFormat.cs @@ -78,5 +78,10 @@ namespace keepass2android { get { return true; } } + + public bool SupportsTemplates + { + get { return true; } + } } } \ No newline at end of file diff --git a/src/Kp2aBusinessLogic/database/edit/AddTemplateEntries.cs b/src/Kp2aBusinessLogic/database/edit/AddTemplateEntries.cs new file mode 100644 index 00000000..46effba3 --- /dev/null +++ b/src/Kp2aBusinessLogic/database/edit/AddTemplateEntries.cs @@ -0,0 +1,373 @@ +/* +This file is part of Keepass2Android, Copyright 2016 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 2 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 . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using Android.Content; +using KeePassLib; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace keepass2android +{ + public class AddTemplateEntries : RunnableOnFinish { + + class TemplateEntry + { + public UiStringKey Title { get; set; } + public PwIcon Icon { get; set; } + + public PwUuid Uuid { get; set; } + + public List Fields + { + get; + set; + } + + + public interface ITemplateField + { + void AddToEntry(IKp2aApp app, PwEntry entry, int position); + } + + internal enum FieldType + { + Inline, ProtectedInline + } + internal enum SpecialFieldKey + { + ExpDate, OverrideUrl, Tags + } + + public class CustomField : ITemplateField + { + public UiStringKey FieldName { get; set; } + + public FieldType Type { get; set; } + + + public void AddToEntry(IKp2aApp app, PwEntry entry, int position) + { + Dictionary fn = new Dictionary() + { + { FieldType.ProtectedInline, "Protected Inline"}, + { FieldType.Inline, "Inline"} + }; + + string fieldKey = app.GetResourceString(FieldName); + entry.Strings.Set("_etm_position_"+fieldKey, new ProtectedString(false, position.ToString())); + entry.Strings.Set("_etm_title_"+fieldKey, new ProtectedString(false, fieldKey)); + entry.Strings.Set("_etm_type_"+fieldKey, new ProtectedString(false, fn[Type])); + } + } + + public class StandardField : ITemplateField + { + public string FieldName { get; set; } + public void AddToEntry(IKp2aApp app, PwEntry entry, int position) + { + string fieldKey = FieldName; + entry.Strings.Set("_etm_position_"+fieldKey, new ProtectedString(false, position.ToString())); + entry.Strings.Set("_etm_title_"+fieldKey, new ProtectedString(false, fieldKey)); + entry.Strings.Set("_etm_type_"+fieldKey, new ProtectedString(false, FieldName == PwDefs.PasswordField ? "Protected Inline" : "Inline")); + } + } + + public class SpecialField : ITemplateField + { + public SpecialFieldKey FieldName { get; set; } + + + public const string TagsKey = "@tags"; + public const string OverrideUrlKey = "@override"; + public const string ExpDateKey = "@exp_date"; + + public void AddToEntry(IKp2aApp app, PwEntry entry, int position) + { + string fieldKey = ""; + string type = "Inline"; + switch (FieldName) + { + case SpecialFieldKey.ExpDate: + fieldKey = ExpDateKey; + type = "Date Time"; + break; + case SpecialFieldKey.OverrideUrl: + fieldKey = OverrideUrlKey; + break; + case SpecialFieldKey.Tags: + fieldKey = TagsKey; + break; + } + entry.Strings.Set("_etm_position_" + fieldKey, new ProtectedString(false, position.ToString())); + entry.Strings.Set("_etm_title_" + fieldKey, new ProtectedString(false, fieldKey)); + entry.Strings.Set("_etm_type_" + fieldKey, new ProtectedString(false, type)); + } + } + } + + protected Database Db + { + get { return _app.GetDb(); } + } + + private readonly IKp2aApp _app; + private readonly Context _ctx; + + public AddTemplateEntries(Context ctx, IKp2aApp app, OnFinish finish) + : base(finish) + { + _ctx = ctx; + _app = app; + + //_onFinishToRun = new AfterAdd(this, OnFinishToRun); + } + + static readonly List TemplateEntries = new List() + { + new TemplateEntry() + { + Title = UiStringKey.TemplateTitle_IdCard, + Icon = PwIcon.Identity, + Uuid = new PwUuid(MemUtil.HexStringToByteArray("A7B525BD0CECC84EB9F0CEDC0B49B5B8")), + Fields = new List() + { + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_Number, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_IdCard_Name, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_IdCard_PlaceOfIssue, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_IdCard_IssueDate, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.SpecialField() + { + FieldName = TemplateEntry.SpecialFieldKey.ExpDate + } + } + }, + new TemplateEntry() + { + Title = UiStringKey.TemplateTitle_EMail, + Icon = PwIcon.EMail, + Uuid = new PwUuid(MemUtil.HexStringToByteArray("0B84EC3029E330478CD99B670942295B")), + Fields = new List() + { + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_EMail_EMail, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.StandardField() + { + FieldName = PwDefs.UrlField + }, + new TemplateEntry.StandardField() + { + FieldName = PwDefs.PasswordField + } + + } + }, + new TemplateEntry() + { + Title = UiStringKey.TemplateTitle_WLan, + Icon = PwIcon.IRCommunication, + Uuid = new PwUuid(MemUtil.HexStringToByteArray("46B56A7E90407545B646E8DC488A5FA2")), + Fields = new List() + { + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_WLan_SSID, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.StandardField() + { + FieldName = PwDefs.PasswordField + } + } + }, + + new TemplateEntry() + { + Title = UiStringKey.TemplateTitle_Notes, + Icon = PwIcon.Notepad, + Uuid = new PwUuid(MemUtil.HexStringToByteArray("10F8C25C26AE9B49A47FDA7CDACACEE2")), + Fields = new List() + { + new TemplateEntry.StandardField() + { + FieldName = PwDefs.NotesField + } + } + }, + + new TemplateEntry() + { + Title = UiStringKey.TemplateTitle_CreditCard, + Icon = PwIcon.Homebanking, + Uuid = new PwUuid(MemUtil.HexStringToByteArray("49DD48DBFF149445B3392CE90EA75309")), + Fields = new List() + { + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_Number, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_CreditCard_CVV, + Type = TemplateEntry.FieldType.ProtectedInline + }, + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_CreditCard_PIN, + Type = TemplateEntry.FieldType.ProtectedInline + }, + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_CreditCard_Owner, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.SpecialField() { FieldName = TemplateEntry.SpecialFieldKey.ExpDate} + } + }, + new TemplateEntry() + { + Title = UiStringKey.TemplateTitle_Membership, + Icon = PwIcon.UserKey, + Uuid = new PwUuid(MemUtil.HexStringToByteArray("DD5C627BC66C28498FDEC70740D29168")), + Fields = new List() + { + new TemplateEntry.CustomField() + { + FieldName = UiStringKey.TemplateField_Number, + Type = TemplateEntry.FieldType.Inline + }, + new TemplateEntry.StandardField() + { + FieldName = PwDefs.UrlField + }, + new TemplateEntry.SpecialField() + { + FieldName = TemplateEntry.SpecialFieldKey.ExpDate + } +}} + + }; + + public static bool ContainsAllTemplates(IKp2aApp app) + { + return TemplateEntries.All(t => app.GetDb().Entries.ContainsKey(t.Uuid)); + } + + public override void Run() { + StatusLogger.UpdateMessage(UiStringKey.AddingEntry); + + if (TemplateEntries.GroupBy(e => e.Uuid).Any(g => g.Count() > 1)) + { + throw new Exception("invalid UUIDs in template list!"); + } + + PwGroup templateGroup; + if (!_app.GetDb().Groups.TryGetValue(_app.GetDb().KpDatabase.EntryTemplatesGroup, out templateGroup)) + { + //create template group + templateGroup = new PwGroup(true, true, _app.GetResourceString(UiStringKey.TemplateGroupName), PwIcon.Folder); + _app.GetDb().KpDatabase.RootGroup.AddGroup(templateGroup, true); + _app.GetDb().KpDatabase.EntryTemplatesGroup = templateGroup.Uuid; + _app.GetDb().KpDatabase.EntryTemplatesGroupChanged = DateTime.Now; + _app.GetDb().Dirty.Add(_app.GetDb().KpDatabase.RootGroup); + _app.GetDb().Groups[templateGroup.Uuid] = templateGroup; + } + List addedEntries = new List(); + + foreach (var template in TemplateEntries) + { + if (_app.GetDb().Entries.ContainsKey(template.Uuid)) + continue; + PwEntry entry = CreateEntry(template); + templateGroup.AddEntry(entry, true); + addedEntries.Add(entry); + _app.GetDb().Entries[entry.Uuid] = entry; + } + + if (addedEntries.Any()) + { + _app.GetDb().Dirty.Add(templateGroup); + + // Commit to disk + SaveDb save = new SaveDb(_ctx, _app, OnFinishToRun); + save.SetStatusLogger(StatusLogger); + save.Run(); + } + } + + private PwEntry CreateEntry(TemplateEntry template) + { + PwEntry entry = new PwEntry(false, true); + entry.Uuid = template.Uuid; + entry.IconId = template.Icon; + entry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, _app.GetResourceString(template.Title))); + entry.Strings.Set("_etm_template", new ProtectedString(false, "1")); + int position = 0; + foreach (var field in template.Fields) + { + field.AddToEntry(_app, entry, position); + position++; + } + return entry; + } + + private class AfterAdd : OnFinish { + private readonly Database _db; + private readonly List _entries; + + public AfterAdd(Database db, List entries, OnFinish finish):base(finish) { + _db = db; + _entries = entries; + + } + + + + public override void Run() { + + + base.Run(); + } + } + + + } + +} + diff --git a/src/Kp2aBusinessLogic/database/edit/CopyEntry.cs b/src/Kp2aBusinessLogic/database/edit/CopyEntry.cs new file mode 100644 index 00000000..14ffd7bb --- /dev/null +++ b/src/Kp2aBusinessLogic/database/edit/CopyEntry.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; +using KeePassLib; +using KeePassLib.Security; + +namespace keepass2android.database.edit +{ + public class CopyEntry: AddEntry + { + public CopyEntry(Context ctx, IKp2aApp app, PwEntry entry, OnFinish finish) + : base(ctx, app, CreateCopy(entry, app), entry.ParentGroup, finish) + { + } + + private static PwEntry CreateCopy(PwEntry entry, IKp2aApp app) + { + var newEntry = entry.CloneDeep(); + 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 + " - " + app.GetResourceString(UiStringKey.DuplicateTitle))); + + return newEntry; + } + } +} \ No newline at end of file diff --git a/src/Kp2aBusinessLogic/database/edit/DeleteRunnable.cs b/src/Kp2aBusinessLogic/database/edit/DeleteRunnable.cs index 48c53cc8..5fa72baa 100644 --- a/src/Kp2aBusinessLogic/database/edit/DeleteRunnable.cs +++ b/src/Kp2aBusinessLogic/database/edit/DeleteRunnable.cs @@ -249,6 +249,12 @@ namespace keepass2android PwDatabase pd = Db.KpDatabase; PwGroup pgRecycleBin = pd.RootGroup.FindGroup(pd.RecycleBinUuid, true); + if (pg.Uuid.Equals(pd.EntryTemplatesGroup)) + { + pd.EntryTemplatesGroup = PwUuid.Zero; + pd.EntryTemplatesGroupChanged = DateTime.Now; + } + pgParent.Groups.Remove(pg); touchedGroups.Add(pgParent); if ((DeletePermanently) || (!CanRecycle)) diff --git a/src/keepass2android/GroupActivity.cs b/src/keepass2android/GroupActivity.cs index f2a29368..84ec9c29 100644 --- a/src/keepass2android/GroupActivity.cs +++ b/src/keepass2android/GroupActivity.cs @@ -60,7 +60,8 @@ namespace keepass2android private const String Tag = "Group Activity:"; - + private const string Askaddtemplates = "AskAddTemplates"; + public static void Launch(Activity act, AppTask appTask) { Launch(act, null, appTask); } @@ -220,26 +221,34 @@ namespace keepass2android View addEntry = FindViewById (Resource.Id.fabAddNewEntry); addEntry.Click += (sender, e) => { - PwEntry defaultTemplate = new PwEntry(false, false); - defaultTemplate.IconId = PwIcon.Key; - defaultTemplate.Strings.Set(PwDefs.TitleField, new ProtectedString(false, GetString(Resource.String.DefaultTemplate))); - List templates = new List() { defaultTemplate }; - if (!PwUuid.Zero.Equals(App.Kp2a.GetDb().KpDatabase.EntryTemplatesGroup)) + if (App.Kp2a.GetDb().DatabaseFormat.SupportsTemplates && + !AddTemplateEntries.ContainsAllTemplates(App.Kp2a) && + PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean(Askaddtemplates, true)) { - templates.AddRange(App.Kp2a.GetDb().Groups[App.Kp2a.GetDb().KpDatabase.EntryTemplatesGroup].Entries.OrderBy(entr => entr.Strings.ReadSafe(PwDefs.TitleField))); - } - - new AlertDialog.Builder(this) - .SetAdapter(new TemplateListAdapter(this, Android.Resource.Layout.SelectDialogItem, - Android.Resource.Id.Text1, templates), (o, args) => + App.Kp2a.AskYesNoCancel(UiStringKey.AskAddTemplatesTitle, UiStringKey.AskAddTemplatesMessage,UiStringKey.yes, UiStringKey.no, + (o, args) => { - - EntryEditActivity.Launch(this, Group, templates[args.Which].Uuid, AppTask); - }) - .Show(); - - - + //yes + ProgressTask pt = new ProgressTask(App.Kp2a, this, + new AddTemplateEntries(this, App.Kp2a, new ActionOnFinish( + delegate + { + StartAddEntry(); + }))); + pt.Run(); + }, + (o, args) => + { + var edit = PreferenceManager.GetDefaultSharedPreferences(this).Edit(); + edit.PutBoolean(Askaddtemplates, false); + edit.Commit(); + //no + StartAddEntry(); + },null, this); + + } + else + StartAddEntry(); }; } @@ -252,6 +261,33 @@ namespace keepass2android } + private void StartAddEntry() + { + PwEntry defaultTemplate = new PwEntry(false, false); + defaultTemplate.IconId = PwIcon.Key; + defaultTemplate.Strings.Set(PwDefs.TitleField, new ProtectedString(false, GetString(Resource.String.DefaultTemplate))); + List templates = new List() {defaultTemplate}; + if ((!PwUuid.Zero.Equals(App.Kp2a.GetDb().KpDatabase.EntryTemplatesGroup)) + && (App.Kp2a.GetDb().KpDatabase.RootGroup.FindGroup(App.Kp2a.GetDb().KpDatabase.EntryTemplatesGroup, true) != null)) + { + templates.AddRange( + App.Kp2a.GetDb().Groups[App.Kp2a.GetDb().KpDatabase.EntryTemplatesGroup].Entries.OrderBy( + entr => entr.Strings.ReadSafe(PwDefs.TitleField))); + } + if (templates.Count > 1) + { + new AlertDialog.Builder(this) + .SetAdapter(new TemplateListAdapter(this, Android.Resource.Layout.SelectDialogItem, + Android.Resource.Id.Text1, templates), + (o, args) => { EntryEditActivity.Launch(this, Group, templates[args.Which].Uuid, AppTask); }) + .Show(); + } + else + { + EntryEditActivity.Launch(this, Group, PwUuid.Zero, AppTask); + } + } + public override void OnCreateContextMenu(IContextMenu menu, View v, IContextMenuContextMenuInfo menuInfo) { diff --git a/src/keepass2android/Resources/values/strings.xml b/src/keepass2android/Resources/values/strings.xml index da18efe1..06ced315 100644 --- a/src/keepass2android/Resources/values/strings.xml +++ b/src/keepass2android/Resources/values/strings.xml @@ -579,10 +579,33 @@ Copy - Standard-Eintrag - + Standard entry + Templates + + + ID card + + Name + Place of issue + Date of issue + E-Mail + E-Mail address + Wireless LAN + Secure note + SSID + Number + CVV + PIN + Card holder + Credit card + Membership Change log + Add templates? + Keepass2Android contains entry templates for E-Mail accounts, Wireless-LAN passwords, secure notes and more. Would you like to add these to your database? If you choose No, you can add them later in the database settings. + + Add templates to database + Please note! This is a preview release and might come with some flaws! If you experience *anything* unexpected, please let me know (on Codeplex or by email). diff --git a/src/keepass2android/Resources/xml/preferences.xml b/src/keepass2android/Resources/xml/preferences.xml index 08a8dd5a..1d93ba66 100644 --- a/src/keepass2android/Resources/xml/preferences.xml +++ b/src/keepass2android/Resources/xml/preferences.xml @@ -58,8 +58,13 @@ > + + - diff --git a/src/keepass2android/settings/DatabaseSettingsActivity.cs b/src/keepass2android/settings/DatabaseSettingsActivity.cs index 2704008a..e37af717 100644 --- a/src/keepass2android/settings/DatabaseSettingsActivity.cs +++ b/src/keepass2android/settings/DatabaseSettingsActivity.cs @@ -351,6 +351,7 @@ namespace keepass2android PrepareDefaultUsername(db); PrepareDatabaseName(db); PrepareMasterPassword(); + PrepareTemplates(db); Preference algorithm = FindPreference(GetString(Resource.String.algorithm_key)); SetAlgorithm(db, algorithm); @@ -434,7 +435,30 @@ namespace keepass2android } - private void PrepareMasterPassword() + private void PrepareTemplates(Database db) + { + Preference pref = FindPreference("AddTemplates_pref_key"); + if ((!db.DatabaseFormat.SupportsTemplates) || (AddTemplateEntries.ContainsAllTemplates(App.Kp2a))) + { + pref.Enabled = false; + } + else + { + pref.PreferenceClick += (sender, args) => + { + ProgressTask pt = new ProgressTask(App.Kp2a, Activity, + new AddTemplateEntries(Activity, App.Kp2a, new ActionOnFinish( + delegate + { + pref.Enabled = false; + }))); + pt.Run(); + }; + } + + } + + private void PrepareMasterPassword() { Preference changeMaster = FindPreference(GetString(Resource.String.master_pwd_key)); if (App.Kp2a.GetDb().CanWrite)