PluginHost-Test project: modified EntryActivity for receiving actions and fields

This commit is contained in:
Philipp Crocoll 2014-05-05 06:15:19 +02:00
parent 4697dbf41c
commit 07038d7549
34 changed files with 1745 additions and 333 deletions

View File

@ -25,6 +25,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KP2AKdbLibraryBinding", "KP
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArtTestApp", "ArtTestApp\ArtTestApp.csproj", "{1FF6C335-A627-43C9-AAA7-CBAC2E74CD18}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArtTestApp", "ArtTestApp\ArtTestApp.csproj", "{1FF6C335-A627-43C9-AAA7-CBAC2E74CD18}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginHostTest", "PluginHostTest\PluginHostTest.csproj", "{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginSdkBinding", "PluginSdkBinding\PluginSdkBinding.csproj", "{3DA3911E-36DE-465E-8F15-F1991B6437E5}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -284,6 +288,48 @@ Global
{1FF6C335-A627-43C9-AAA7-CBAC2E74CD18}.ReleaseNoNet|Mixed Platforms.Deploy.0 = Release|Any CPU {1FF6C335-A627-43C9-AAA7-CBAC2E74CD18}.ReleaseNoNet|Mixed Platforms.Deploy.0 = Release|Any CPU
{1FF6C335-A627-43C9-AAA7-CBAC2E74CD18}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU {1FF6C335-A627-43C9-AAA7-CBAC2E74CD18}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{1FF6C335-A627-43C9-AAA7-CBAC2E74CD18}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU {1FF6C335-A627-43C9-AAA7-CBAC2E74CD18}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Debug|Mixed Platforms.Deploy.0 = Debug|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Debug|Win32.ActiveCfg = Debug|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Debug|x64.ActiveCfg = Debug|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Release|Any CPU.Build.0 = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Release|Any CPU.Deploy.0 = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Release|Mixed Platforms.Deploy.0 = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Release|Win32.ActiveCfg = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Release|x64.ActiveCfg = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.ReleaseNoNet|Any CPU.Deploy.0 = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.ReleaseNoNet|Mixed Platforms.Deploy.0 = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Debug|Win32.ActiveCfg = Debug|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Debug|x64.ActiveCfg = Debug|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Release|Any CPU.Build.0 = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Release|Win32.ActiveCfg = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Release|x64.ActiveCfg = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

37
src/PluginHostTest/App.cs Normal file
View File

@ -0,0 +1,37 @@
using KeePassLib;
using KeePassLib.Keys;
using KeePassLib.Serialization;
namespace keepass2android
{
public class App
{
public class Kp2A
{
private static Db _mDb;
public class Db
{
public void SetEntry(PwEntry e)
{
KpDatabase = new PwDatabase();
KpDatabase.New(new IOConnectionInfo(), new CompositeKey());
KpDatabase.RootGroup.AddEntry(e, true);
}
public PwDatabase KpDatabase
{
get; set;
}
}
public static Db GetDb()
{
if (_mDb == null)
_mDb = new Db();
return _mDb;
}
}
}
}

View File

@ -0,0 +1,14 @@
using Android.Content;
using Android.Widget;
using KeePassLib.Security;
namespace keepass2android
{
internal class CopyToClipboardService
{
public static void CopyValueToClipboardWithTimeout(Context ctx, string text)
{
Toast.MakeText(ctx, text, ToastLength.Short).Show();
}
}
}

View File

@ -19,10 +19,12 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Linq; using System.Linq;
using System.Threading;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.OS; using Android.OS;
using Android.Runtime;
using Android.Text;
using Android.Views; using Android.Views;
using Android.Widget; using Android.Widget;
using Android.Preferences; using Android.Preferences;
@ -32,79 +34,412 @@ using Android.Content.PM;
using Android.Webkit; using Android.Webkit;
using Android.Graphics; using Android.Graphics;
using Java.IO; using Java.IO;
using KeePassLib;
using KeePassLib.Security;
using Keepass2android.Pluginsdk;
using PluginHostTest; using PluginHostTest;
using Uri = Android.Net.Uri;
namespace keepass2android namespace keepass2android
{ {
[Activity (Label = "@string/app_name", ConfigurationChanges=ConfigChanges.Orientation|ConfigChanges.KeyboardHidden, Theme="@style/NoTitleBar")]
public class EntryActivity : Activity { [Activity(Label = "@string/app_name", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.KeyboardHidden,
Theme = "@style/NoTitleBar")]
public class EntryActivity : Activity
{
public const String KeyEntry = "entry"; public const String KeyEntry = "entry";
public const String KeyRefreshPos = "refresh_pos"; public const String KeyRefreshPos = "refresh_pos";
public const String KeyCloseAfterCreate = "close_after_create"; public const String KeyCloseAfterCreate = "close_after_create";
protected PwEntry Entry = new PwEntry(true, true);
private static Typeface _passwordFont; private static Typeface _passwordFont;
private bool _showPassword; internal bool _showPassword;
private int _pos; private int _pos;
private List<TextView> _protectedTextViews; private List<TextView> _protectedTextViews;
private readonly Dictionary<string, List<IPopupMenuItem>> _popupMenuItems =
new Dictionary<string, List<IPopupMenuItem>>();
protected void SetEntryView() { private readonly Dictionary<string, IStringView> _stringViews = new Dictionary<string, IStringView>();
private readonly List<PluginMenuOption> _pendingMenuOptions = new List<PluginMenuOption>();
private IMenu _menu;
protected void SetEntryView()
{
SetContentView(Resource.Layout.entry_view); SetContentView(Resource.Layout.entry_view);
} }
protected void SetupEditButtons() { protected void SetupEditButtons()
View edit = FindViewById(Resource.Id.entry_edit); {
View edit = FindViewById(Resource.Id.entry_edit);
if (true) if (true)
{ {
edit.Visibility = ViewStates.Visible; edit.Visibility = ViewStates.Visible;
edit.Click += (sender, e) => edit.Click += (sender, e) =>
{ {
}; };
} }
else else
{ {
edit.Visibility = ViewStates.Gone; edit.Visibility = ViewStates.Gone;
} }
} }
private class PluginActionReceiver : BroadcastReceiver
{
private readonly EntryActivity _activity;
public PluginActionReceiver(EntryActivity activity)
{
_activity = activity;
}
public override void OnReceive(Context context, Intent intent)
{
var pluginPackage = intent.GetStringExtra(Strings.ExtraSender);
if (new PluginDatabase(context).IsValidAccessToken(pluginPackage,
intent.GetStringExtra(Strings.ExtraAccessToken),
Strings.ScopeCurrentEntry))
{
if (intent.GetStringExtra(Strings.ExtraEntryId) != _activity.Entry.Uuid.ToHexString())
{
Kp2aLog.Log("received action for wrong entry " + intent.GetStringExtra(Strings.ExtraEntryId));
return;
}
_activity.AddPluginAction(pluginPackage,
intent.GetStringExtra(Strings.ExtraFieldId),
intent.GetStringExtra(Strings.ExtraActionDisplayText),
intent.GetIntExtra(Strings.ExtraActionIconResId, -1),
intent.GetBundleExtra(Strings.ExtraActionData));
}
else
{
Kp2aLog.Log("received invalid request. Plugin not authorized.");
}
}
}
private class PluginFieldReceiver : BroadcastReceiver
{
private readonly EntryActivity _activity;
public PluginFieldReceiver(EntryActivity activity)
{
_activity = activity;
}
public override void OnReceive(Context context, Intent intent)
{
if (intent.GetStringExtra(Strings.ExtraEntryId) != _activity.Entry.Uuid.ToHexString())
{
Kp2aLog.Log("received field for wrong entry " + intent.GetStringExtra(Strings.ExtraEntryId));
return;
}
if (!new PluginDatabase(context).IsValidAccessToken(intent.GetStringExtra(Strings.ExtraSender),
intent.GetStringExtra(Strings.ExtraAccessToken),
Strings.ScopeCurrentEntry))
{
Kp2aLog.Log("received field with invalid access token from " + intent.GetStringExtra(Strings.ExtraSender));
return;
}
string key = intent.GetStringExtra(Strings.ExtraFieldId);
string value = intent.GetStringExtra(Strings.ExtraFieldValue);
bool isProtected = intent.GetBooleanExtra(Strings.ExtraFieldProtected, false);
_activity.SetPluginField(key, value, isProtected);
}
}
private void SetPluginField(string key, string value, bool isProtected)
{
IStringView existingField;
if (_stringViews.TryGetValue(key, out existingField))
{
existingField.Text = value;
}
else
{
ViewGroup extraGroup = (ViewGroup) FindViewById(Resource.Id.extra_strings);
var view = CreateExtraSection(key, value, isProtected);
extraGroup.AddView(view.View);
}
}
private void AddPluginAction(string pluginPackage, string fieldId, string displayText, int iconId, Bundle bundleExtra)
{
if (fieldId != null)
{
_popupMenuItems[fieldId].Add(new PluginPopupMenuItem(this, pluginPackage, fieldId, displayText, iconId, bundleExtra));
}
else
{
//we need to add an option to the menu.
//As it is not sure that OnCreateOptionsMenu was called yet, we cannot access _menu without a check:
Intent i = new Intent(Strings.ActionEntryActionSelected);
i.SetPackage(pluginPackage);
i.PutExtra(Strings.ExtraActionData, bundleExtra);
i.PutExtra(Strings.ExtraSender, PackageName);
var menuOption = new PluginMenuOption()
{
DisplayText = displayText,
Icon = PackageManager.GetResourcesForApplication(pluginPackage).GetDrawable(iconId),
Intent = i
};
if (_menu != null)
{
AddMenuOption(menuOption);
}
else
{
lock (_pendingMenuOptions)
{
_pendingMenuOptions.Add(menuOption);
}
}
}
}
private void AddMenuOption(PluginMenuOption menuOption)
{
var menuItem = _menu.Add(menuOption.DisplayText);
menuItem.SetIcon(menuOption.Icon);
menuItem.SetIntent(menuOption.Intent);
}
public override bool OnCreateOptionsMenu(IMenu menu)
{
_menu = menu;
base.OnCreateOptionsMenu(menu);
MenuInflater inflater = MenuInflater;
inflater.Inflate(Resource.Menu.entry, menu);
lock (_pendingMenuOptions)
{
foreach (var option in _pendingMenuOptions)
AddMenuOption(option);
_pendingMenuOptions.Clear();
}
UpdateTogglePasswordMenu();
IMenuItem gotoUrl = menu.FindItem(Resource.Id.menu_goto_url);
//Disabled IMenuItem copyUser = menu.FindItem(Resource.Id.menu_copy_user);
//Disabled IMenuItem copyPass = menu.FindItem(Resource.Id.menu_copy_pass);
// In API >= 11 onCreateOptionsMenu may be called before onCreate completes
// so _entry may not be set
if (Entry == null)
{
gotoUrl.SetVisible(false);
//Disabled copyUser.SetVisible(false);
//Disabled copyPass.SetVisible(false);
}
else
{
String url = Entry.Strings.ReadSafe(PwDefs.UrlField);
if (String.IsNullOrEmpty(url))
{
// disable button if url is not available
gotoUrl.SetVisible(false);
}
if (String.IsNullOrEmpty(Entry.Strings.ReadSafe(PwDefs.UserNameField)))
{
// disable button if username is not available
//Disabled copyUser.SetVisible(false);
}
if (String.IsNullOrEmpty(Entry.Strings.ReadSafe(PwDefs.PasswordField)))
{
// disable button if password is not available
//Disabled copyPass.SetVisible(false);
}
}
return true;
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
//check if this is a plugin action
if ((item.Intent != null) && (item.Intent.Action == Strings.ActionEntryActionSelected))
{
//yes. let the plugin handle the click:
SendBroadcast(item.Intent);
return true;
}
switch (item.ItemId)
{
case Resource.Id.menu_donate:
try
{
// Util.GotoDonateUrl(this);
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.error_failed_to_launch_link, ToastLength.Long).Show();
return false;
}
return true;
case Resource.Id.menu_toggle_pass:
if (_showPassword)
{
item.SetTitle(Resource.String.show_password);
_showPassword = false;
}
else
{
item.SetTitle(Resource.String.menu_hide_password);
_showPassword = true;
}
SetPasswordStyle();
return true;
case Resource.Id.menu_goto_url:
string url = _stringViews[PwDefs.UrlField].Text;
if (url == null) return false;
// Default http:// if no protocol specified
if (!url.Contains("://"))
{
url = "http://" + url;
}
try
{
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
}
return true;
/* TODO: required?
case Resource.Id.menu_copy_user:
timeoutCopyToClipboard(_entry.Strings.ReadSafe (PwDefs.UserNameField));
return true;
case Resource.Id.menu_copy_pass:
timeoutCopyToClipboard(_entry.Strings.ReadSafe (PwDefs.UserNameField));
return true;
*/
case Resource.Id.menu_rate:
try
{
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
}
return true;
case Resource.Id.menu_suggest_improvements:
try
{
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
}
return true;
case Resource.Id.menu_lock:
return true;
case Resource.Id.menu_translate:
try
{
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
}
return true;
case Android.Resource.Id.Home:
//Currently the action bar only displays the home button when we come from a previous activity.
//So we can simply Finish. See this page for information on how to do this in more general (future?) cases:
//http://developer.android.com/training/implementing-navigation/ancestral.html
Finish();
return true;
}
return base.OnOptionsItemSelected(item);
}
protected override void OnCreate(Bundle savedInstanceState) protected override void OnCreate(Bundle savedInstanceState)
{ {
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this); ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this);
long usageCount = prefs.GetLong(GetString(Resource.String.UsageCount_key), 0); long usageCount = prefs.GetLong(GetString(Resource.String.UsageCount_key), 0);
ISharedPreferencesEditor edit = prefs.Edit(); ISharedPreferencesEditor edit = prefs.Edit();
edit.PutLong(GetString(Resource.String.UsageCount_key), usageCount+1); edit.PutLong(GetString(Resource.String.UsageCount_key), usageCount + 1);
edit.Commit(); edit.Commit();
_showPassword = ! prefs.GetBoolean(GetString(Resource.String.maskpass_key), Resources.GetBoolean(Resource.Boolean.maskpass_default)); _showPassword =
!prefs.GetBoolean(GetString(Resource.String.maskpass_key), Resources.GetBoolean(Resource.Boolean.maskpass_default));
Entry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "philipp "));
Entry.Strings.Set(PwDefs.PasswordField, new ProtectedString(true, "password value"));
Entry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "https://www.google.com"));
Entry.Strings.Set("field header", new ProtectedString(true, "protected field value"));
Entry.Strings.Set("public field header", new ProtectedString(false, "public field value"));
base.OnCreate(savedInstanceState); base.OnCreate(savedInstanceState);
SetEntryView(); SetEntryView();
FillData(false); FillData();
SetupEditButtons(); SetupEditButtons();
RegisterReceiver(new PluginActionReceiver(this), new IntentFilter(Strings.ActionAddEntryAction));
RegisterReceiver(new PluginFieldReceiver(this), new IntentFilter(Strings.ActionSetEntryField));
new Thread(NotifyPluginsOnOpen).Start();
}
private void NotifyPluginsOnOpen()
{
App.Kp2A.GetDb().SetEntry(Entry);
Intent i = new Intent(Strings.ActionOpenEntry);
i.PutExtra(Strings.ExtraSender, PackageName);
PluginHost.AddEntryToIntent(i, Entry);
foreach (var plugin in new PluginDatabase(this).GetPluginsWithAcceptedScope(Strings.ScopeCurrentEntry))
{
i.SetPackage(plugin);
SendBroadcast(i);
}
} }
public void CompleteOnCreate() public void CompleteOnCreate()
{ {
} }
private String getDateTime(DateTime dt) { private String getDateTime(DateTime dt)
return dt.ToString ("g", CultureInfo.CurrentUICulture); {
return dt.ToString("g", CultureInfo.CurrentUICulture);
} }
String concatTags(List<string> tags) private String concatTags(List<string> tags)
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
foreach (string tag in tags) foreach (string tag in tags)
@ -113,70 +448,98 @@ namespace keepass2android
sb.Append(", "); sb.Append(", ");
} }
if (tags.Count > 0) if (tags.Count > 0)
sb.Remove(sb.Length-2,2); sb.Remove(sb.Length - 2, 2);
return sb.ToString(); return sb.ToString();
} }
void PopulateExtraStrings(bool trimList) private void PopulateExtraStrings()
{ {
ViewGroup extraGroup = (ViewGroup)FindViewById(Resource.Id.extra_strings); ViewGroup extraGroup = (ViewGroup) FindViewById(Resource.Id.extra_strings);
if (trimList) foreach (var pair in Entry.Strings.Where(pair => !PwDefs.IsStandardField(pair.Key)).OrderBy(pair => pair.Key))
{ {
extraGroup.RemoveAllViews(); var stringView = CreateExtraSection(pair.Key, pair.Value.ReadString(), pair.Value.IsProtected);
extraGroup.AddView(stringView.View);
} }
bool hasExtraFields = false;
foreach (var view in from pair in new Dictionary<string, string>() { { "Field header", "field value" }, { "another header", "_aiaeiae" } }
orderby pair.Key
select CreateEditSection(pair.Key, pair.Value, true))
{
extraGroup.AddView(view);
hasExtraFields = true;
}
FindViewById(Resource.Id.entry_extra_strings_label).Visibility = hasExtraFields ? ViewStates.Visible : ViewStates.Gone;
} }
View CreateEditSection(string key, string value, bool isProtected) private ExtraStringView CreateExtraSection(string key, string value, bool isProtected)
{ {
LinearLayout layout = new LinearLayout(this, null) {Orientation = Orientation.Vertical}; LinearLayout layout = new LinearLayout(this, null) {Orientation = Orientation.Vertical};
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FillParent,
layoutParams.SetMargins(10,0,0,0); ViewGroup.LayoutParams.WrapContent);
layout.LayoutParameters = layoutParams; layout.LayoutParameters = layoutParams;
View viewInflated = LayoutInflater.Inflate(Resource.Layout.entry_extrastring_title,null); View viewInflated = LayoutInflater.Inflate(Resource.Layout.entry_extrastring_title, null);
TextView keyView = (TextView)viewInflated; TextView keyView = viewInflated.FindViewById<TextView>(Resource.Id.entry_title);
if (key != null) if (key != null)
keyView.Text = key; keyView.Text = key;
layout.AddView(keyView); layout.AddView(viewInflated);
TextView valueView = (TextView)LayoutInflater.Inflate(Resource.Layout.entry_extrastring_value, null); RelativeLayout valueViewContainer =
(RelativeLayout) LayoutInflater.Inflate(Resource.Layout.entry_extrastring_value, null);
var valueView = valueViewContainer.FindViewById<TextView>(Resource.Id.entry_extra);
if (value != null) if (value != null)
valueView.Text = value; valueView.Text = value;
SetPasswordTypeface(valueView); SetPasswordTypeface(valueView);
if (isProtected) if (isProtected)
{
RegisterProtectedTextView(valueView); RegisterProtectedTextView(valueView);
valueView.TransformationMethod = PasswordTransformationMethod.Instance;
}
layout.AddView(valueViewContainer);
var stringView = new ExtraStringView(layout, valueView, keyView);
_stringViews.Add(key, stringView);
RegisterTextPopup(valueViewContainer, valueViewContainer.FindViewById(Resource.Id.extra_vdots), key, isProtected);
return stringView;
if ((int)Build.VERSION.SdkInt >= 11)
valueView.SetTextIsSelectable(true);
layout.AddView(valueView);
return layout;
} }
private List<IPopupMenuItem> RegisterPopup(string popupKey, View clickView, View anchorView)
{
clickView.Click += (sender, args) =>
{
ShowPopup(anchorView, popupKey);
};
_popupMenuItems[popupKey] = new List<IPopupMenuItem>();
return _popupMenuItems[popupKey];
}
private void RegisterProtectedTextView(TextView protectedTextView) private void RegisterProtectedTextView(TextView protectedTextView)
{ {
_protectedTextViews.Add(protectedTextView); _protectedTextViews.Add(protectedTextView);
} }
void PopulateBinaries(bool trimList) private void PopulateBinaries()
{ {
ViewGroup binariesGroup = (ViewGroup)FindViewById(Resource.Id.binaries); ViewGroup binariesGroup = (ViewGroup) FindViewById(Resource.Id.binaries);
if (trimList) foreach (KeyValuePair<string, string> pair in new Dictionary<string, string>() {{"abc", ""}, {"test.png", "uia"}})
{
binariesGroup.RemoveAllViews();
}
foreach (KeyValuePair<string, string> pair in new Dictionary<string, string>() { {"abc",""}, {"test.png","uia"} })
{ {
String key = pair.Key; String key = pair.Key;
RelativeLayout valueViewContainer =
(RelativeLayout) LayoutInflater.Inflate(Resource.Layout.entry_extrastring_value, null);
var valueView = valueViewContainer.FindViewById<TextView>(Resource.Id.entry_extra);
if (key != null)
valueView.Text = key;
string popupKey = Strings.PrefixBinary + key;
var itemList = RegisterPopup(popupKey, valueViewContainer, valueViewContainer.FindViewById(Resource.Id.extra_vdots));
itemList.Add(new WriteBinaryToFilePopupItem(key, this));
itemList.Add(new OpenBinaryPopupItem(key, this));
binariesGroup.AddView(valueViewContainer);
/*
Button binaryButton = new Button(this); Button binaryButton = new Button(this);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent); RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
binaryButton.Text = key; binaryButton.Text = key;
@ -206,8 +569,8 @@ namespace keepass2android
}; };
binariesGroup.AddView(binaryButton,layoutParams); binariesGroup.AddView(binaryButton,layoutParams);
*/
} }
FindViewById(Resource.Id.entry_binaries_label).Visibility = true ? ViewStates.Visible : ViewStates.Gone; FindViewById(Resource.Id.entry_binaries_label).Visibility = true ? ViewStates.Visible : ViewStates.Gone;
} }
@ -217,7 +580,8 @@ namespace keepass2android
{ {
String type = null; String type = null;
String extension = MimeTypeMap.GetFileExtensionFromUrl(url); String extension = MimeTypeMap.GetFileExtensionFromUrl(url);
if (extension != null) { if (extension != null)
{
MimeTypeMap mime = MimeTypeMap.Singleton; MimeTypeMap mime = MimeTypeMap.Singleton;
type = mime.GetMimeTypeFromExtension(extension); type = mime.GetMimeTypeFromExtension(extension);
} }
@ -229,94 +593,182 @@ namespace keepass2android
base.OnBackPressed(); base.OnBackPressed();
} }
protected void FillData(bool trimList) protected void FillData()
{ {
_protectedTextViews = new List<TextView>(); _protectedTextViews = new List<TextView>();
ImageView iv = (ImageView)FindViewById(Resource.Id.entry_icon); ImageView iv = (ImageView) FindViewById(Resource.Id.entry_icon);
if (iv != null) if (iv != null)
{ {
iv.SetImageDrawable(Resources.GetDrawable(Resource.Drawable.ic00)); iv.SetImageDrawable(Resources.GetDrawable(Resource.Drawable.ic00));
} }
ActionBar.Title = "Entry title"; ActionBar.Title = "Entry title";
ActionBar.SetDisplayHomeAsUpEnabled(true); ActionBar.SetDisplayHomeAsUpEnabled(true);
PopulateText(Resource.Id.entry_user_name, Resource.Id.entry_user_name_label, "user name");
PopulateText(Resource.Id.entry_url, Resource.Id.entry_url_label, "www.google.com"); PopulateStandardText(Resource.Id.entry_user_name, Resource.Id.entryfield_container_username, PwDefs.UserNameField);
PopulateText(Resource.Id.entry_password, Resource.Id.entry_password_label, "my password"); PopulateStandardText(Resource.Id.entry_url, Resource.Id.entryfield_container_url, PwDefs.UrlField);
PopulateStandardText(Resource.Id.entry_password, Resource.Id.entryfield_container_password, PwDefs.PasswordField);
RegisterProtectedTextView(FindViewById<TextView>(Resource.Id.entry_password)); RegisterProtectedTextView(FindViewById<TextView>(Resource.Id.entry_password));
SetPasswordTypeface(FindViewById<TextView>(Resource.Id.entry_password)); SetPasswordTypeface(FindViewById<TextView>(Resource.Id.entry_password));
PopulateText(Resource.Id.entry_created, Resource.Id.entry_created_label, getDateTime(DateTime.Now));
PopulateText(Resource.Id.entry_modified, Resource.Id.entry_modified_label, getDateTime(DateTime.Now));
if (true)
{
FindViewById(Resource.Id.entry_expires).Visibility = ViewStates.Visible;
FindViewById(Resource.Id.entry_expires_label).Visibility = ViewStates.Visible;
PopulateText(Resource.Id.entry_expires, Resource.Id.entry_expires_label, getDateTime(DateTime.Now));
} RegisterTextPopup(FindViewById<RelativeLayout>(Resource.Id.username_container),
FindViewById(Resource.Id.username_vdots), PwDefs.UserNameField);
RegisterTextPopup(FindViewById<RelativeLayout>(Resource.Id.url_container),
FindViewById(Resource.Id.url_vdots), PwDefs.UrlField)
.Add(new GotoUrlMenuItem(this));
RegisterTextPopup(FindViewById<RelativeLayout>(Resource.Id.password_container),
FindViewById(Resource.Id.password_vdots), PwDefs.PasswordField);
PopulateText(Resource.Id.entry_created, Resource.Id.entryfield_container_created, getDateTime(Entry.CreationTime));
PopulateText(Resource.Id.entry_modified, Resource.Id.entryfield_container_modified, getDateTime(Entry.LastModificationTime));
if (Entry.Expires)
{
PopulateText(Resource.Id.entry_expires, Resource.Id.entryfield_container_expires, getDateTime(Entry.ExpiryTime));
}
else else
{ {
FindViewById(Resource.Id.entry_expires).Visibility = ViewStates.Gone; PopulateText(Resource.Id.entry_expires, Resource.Id.entryfield_container_expires, null);
FindViewById(Resource.Id.entry_expires_label).Visibility = ViewStates.Gone;
} }
PopulateText(Resource.Id.entry_comment, Resource.Id.entry_comment_label, "some text about this entry"); PopulateStandardText(Resource.Id.entry_comment, Resource.Id.entryfield_container_comment, PwDefs.NotesField);
PopulateText(Resource.Id.entry_tags, Resource.Id.entryfield_container_tags, concatTags(Entry.Tags));
PopulateText(Resource.Id.entry_override_url, Resource.Id.entryfield_container_overrideurl, Entry.OverrideUrl);
PopulateText(Resource.Id.entry_tags, Resource.Id.entry_tags_label, "bla; blubb; blablubb"); PopulateExtraStrings();
PopulateExtraStrings(trimList); PopulateBinaries();
PopulateBinaries(trimList);
SetPasswordStyle(); SetPasswordStyle();
} }
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (resultCode == /*TODO*/ 0)
{
if (resultCode == /*TODO*/ 0)
{
RequiresRefresh();
}
Recreate();
}
}
protected override void OnDestroy()
{
NotifyPluginsOnClose();
base.OnDestroy();
}
private void NotifyPluginsOnClose()
{
Intent i = new Intent(Strings.ActionCloseEntryView);
i.PutExtra(Strings.ExtraSender, PackageName);
foreach (var plugin in new PluginDatabase(this).GetPluginsWithAcceptedScope(Strings.ScopeCurrentEntry))
{
i.SetPackage(plugin);
SendBroadcast(i);
}
}
private List<IPopupMenuItem> RegisterTextPopup(View container, View anchor, string fieldKey)
{
return RegisterTextPopup(container, anchor, fieldKey, Entry.Strings.GetSafe(fieldKey).IsProtected);
}
private List<IPopupMenuItem> RegisterTextPopup(View container, View anchor, string fieldKey, bool isProtected)
{
string popupKey = Strings.PrefixString + fieldKey;
var popupItems = RegisterPopup(
popupKey,
container,
anchor);
popupItems.Add(new CopyToClipboardPopupMenuIcon(this, _stringViews[fieldKey]));
if (isProtected)
popupItems.Add(new ToggleVisibilityPopupMenuItem(this));
return popupItems;
}
private void ShowPopup(View anchor, string popupKey)
{
//PopupMenu popupMenu = new PopupMenu(this, FindViewById(Resource.Id.entry_user_name));
PopupMenu popupMenu = new PopupMenu(this, anchor);
AccessManager.PreparePopup(popupMenu);
int itemId = 0;
foreach (IPopupMenuItem popupItem in _popupMenuItems[popupKey])
{
popupMenu.Menu.Add(0, itemId, 0, popupItem.Text)
.SetIcon(popupItem.Icon);
itemId++;
}
popupMenu.MenuItemClick += delegate(object sender, PopupMenu.MenuItemClickEventArgs args)
{
_popupMenuItems[popupKey][args.Item.ItemId].HandleClick();
};
popupMenu.Show();
}
private void ShowPopup(int resAnchor, string popupKey)
{
ShowPopup(FindViewById(resAnchor), popupKey);
}
private void SetPasswordTypeface(TextView textView) private void SetPasswordTypeface(TextView textView)
{ {
} }
private void PopulateText(int viewId, int headerViewId,int resId) { private void PopulateText(int viewId, int containerViewId, int resId)
View header = FindViewById(headerViewId); {
TextView tv = (TextView)FindViewById(viewId); View header = FindViewById(containerViewId);
TextView tv = (TextView) FindViewById(viewId);
header.Visibility = tv.Visibility = ViewStates.Visible; header.Visibility = tv.Visibility = ViewStates.Visible;
tv.SetText (resId); tv.SetText(resId);
} }
private void PopulateText(int viewId, int headerViewId, String text) private void PopulateText(int viewId, int containerViewId, String text)
{ {
View header = FindViewById(headerViewId); View container = FindViewById(containerViewId);
TextView tv = (TextView)FindViewById(viewId); TextView tv = (TextView) FindViewById(viewId);
if (String.IsNullOrEmpty(text)) if (String.IsNullOrEmpty(text))
{ {
header.Visibility = tv.Visibility = ViewStates.Gone; container.Visibility = tv.Visibility = ViewStates.Gone;
} }
else else
{ {
header.Visibility = tv.Visibility = ViewStates.Visible; container.Visibility = tv.Visibility = ViewStates.Visible;
tv.Text = text; tv.Text = text;
} }
} }
void RequiresRefresh () private void PopulateStandardText(int viewId, int containerViewId, String key)
{ {
Intent ret = new Intent (); PopulateText(viewId, containerViewId, Entry.Strings.ReadSafe(key));
ret.PutExtra (KeyRefreshPos, _pos); _stringViews.Add(key, new StandardStringView(viewId, containerViewId, this));
} }
private void RequiresRefresh()
{
private void SetPasswordStyle() { Intent ret = new Intent();
ret.PutExtra(KeyRefreshPos, _pos);
}
private void SetPasswordStyle()
{
foreach (TextView password in _protectedTextViews) foreach (TextView password in _protectedTextViews)
{ {
@ -330,12 +782,13 @@ namespace keepass2android
} }
} }
} }
protected override void OnResume() protected override void OnResume()
{ {
base.OnResume(); base.OnResume();
} }
/// <summary> /// <summary>
/// brings up a dialog asking the user whether he wants to add the given URL to the entry for automatic finding /// brings up a dialog asking the user whether he wants to add the given URL to the entry for automatic finding
/// </summary> /// </summary>
@ -344,24 +797,50 @@ namespace keepass2android
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.SetTitle(GetString(Resource.String.AddUrlToEntryDialog_title)); builder.SetTitle(GetString(Resource.String.AddUrlToEntryDialog_title));
builder.SetMessage(GetString(Resource.String.AddUrlToEntryDialog_text, new Java.Lang.Object[] { url } )); builder.SetMessage(GetString(Resource.String.AddUrlToEntryDialog_text, new Java.Lang.Object[] {url}));
builder.SetPositiveButton(GetString(Resource.String.yes), (dlgSender, dlgEvt) => builder.SetPositiveButton(GetString(Resource.String.yes), (dlgSender, dlgEvt) =>
{ {
}); });
builder.SetNegativeButton(GetString(Resource.String.no), (dlgSender, dlgEvt) => builder.SetNegativeButton(GetString(Resource.String.no), (dlgSender, dlgEvt) =>
{ {
CompleteOnCreate(); CompleteOnCreate();
}); });
Dialog dialog = builder.Create(); Dialog dialog = builder.Create();
dialog.Show(); dialog.Show();
} }
public void ToggleVisibility()
{
_showPassword = !_showPassword;
SetPasswordStyle();
UpdateTogglePasswordMenu();
}
public Android.Net.Uri WriteBinaryToFile(string key, bool writeToCacheDirectory)
{
return Android.Net.Uri.Empty;
//TODO
}
private void UpdateTogglePasswordMenu()
{
//todo use real method
}
public void GotoUrl()
{
//TODO
}
public void OpenBinaryFile(Uri newUri)
{
Toast.MakeText(this, "opening file TODO", ToastLength.Short).Show();
}
} }
}
}

View File

@ -0,0 +1,44 @@
using System;
using Android.Views;
using Android.Widget;
namespace keepass2android
{
internal class ExtraStringView : IStringView
{
private readonly View _container;
private readonly TextView _valueView;
private readonly TextView _keyView;
public ExtraStringView(LinearLayout container, TextView valueView, TextView keyView)
{
_container = container;
_valueView = valueView;
_keyView = keyView;
}
public View View
{
get { return _container; }
}
public string Text
{
get { return _valueView.Text; }
set
{
if (String.IsNullOrEmpty(value))
{
_valueView.Visibility = ViewStates.Gone;
_keyView.Visibility = ViewStates.Gone;
}
else
{
_valueView.Visibility = ViewStates.Visible;
_keyView.Visibility = ViewStates.Visible;
_valueView.Text = value;
}
}
}
}
}

View File

@ -0,0 +1,112 @@
using System;
using Android.Content;
using Android.Graphics.Drawables;
using KeePassLib;
using PluginHostTest;
namespace keepass2android
{
internal interface IPopupMenuItem
{
Drawable Icon { get; }
String Text { get; }
void HandleClick();
}
class GotoUrlMenuItem : IPopupMenuItem
{
private readonly EntryActivity _ctx;
public GotoUrlMenuItem(EntryActivity ctx)
{
_ctx = ctx;
}
public Drawable Icon
{
get { return _ctx.Resources.GetDrawable(Android.Resource.Drawable.IcMenuUpload); }
}
public string Text
{
get { return _ctx.Resources.GetString(Resource.String.menu_url); }
}
public void HandleClick()
{
//TODO
_ctx.GotoUrl();
}
}
class ToggleVisibilityPopupMenuItem : IPopupMenuItem
{
private readonly EntryActivity _activity;
public ToggleVisibilityPopupMenuItem(EntryActivity activity)
{
_activity = activity;
}
public Drawable Icon
{
get
{
//return new TextDrawable("\uF06E", _activity);
return _activity.Resources.GetDrawable(Resource.Drawable.ic_action_eye_open);
}
}
public string Text
{
get
{
return _activity.Resources.GetString(
_activity._showPassword ?
Resource.String.menu_hide_password
: Resource.String.show_password);
}
}
public void HandleClick()
{
_activity.ToggleVisibility();
}
}
class CopyToClipboardPopupMenuIcon : IPopupMenuItem
{
private readonly Context _context;
private readonly IStringView _stringView;
public CopyToClipboardPopupMenuIcon(Context context, IStringView stringView)
{
_context = context;
_stringView = stringView;
}
public Drawable Icon
{
get
{
return _context.Resources.GetDrawable(Resource.Drawable.ic_menu_copy_holo_light);
}
}
public string Text
{
//TODO localize
get { return "Copy to clipboard"; }
}
public void HandleClick()
{
CopyToClipboardService.CopyValueToClipboardWithTimeout(_context, _stringView.Text);
}
}
}

View File

@ -0,0 +1,7 @@
namespace keepass2android
{
internal interface IStringView
{
string Text { set; get; }
}
}

View File

@ -0,0 +1,37 @@
using Android.Graphics.Drawables;
using PluginHostTest;
namespace keepass2android
{
internal class OpenBinaryPopupItem : IPopupMenuItem
{
private readonly string _key;
private readonly EntryActivity _entryActivity;
public OpenBinaryPopupItem(string key, EntryActivity entryActivity)
{
_key = key;
_entryActivity = entryActivity;
}
public Drawable Icon
{
get { return _entryActivity.Resources.GetDrawable(Android.Resource.Drawable.IcMenuShare); }
}
public string Text
{
get { return _entryActivity.Resources.GetString(Resource.String.SaveAttachmentDialog_open); }
}
public void HandleClick()
{
Android.Net.Uri newUri = _entryActivity.WriteBinaryToFile(_key, true);
if (newUri != null)
{
_entryActivity.OpenBinaryFile(newUri);
}
}
}
}

View File

@ -0,0 +1,14 @@
using Android.Content;
using Android.Graphics.Drawables;
namespace keepass2android
{
class PluginMenuOption
{
public string DisplayText { get; set; }
public Drawable Icon { get; set; }
public Intent Intent { get; set; }
}
}

View File

@ -0,0 +1,47 @@
using Android.Content;
using Android.Graphics.Drawables;
using Android.OS;
using Keepass2android.Pluginsdk;
namespace keepass2android
{
class PluginPopupMenuItem : IPopupMenuItem
{
private readonly Context _ctx;
private readonly string _pluginPackage;
private readonly string _fieldId;
private readonly string _displayText;
private readonly int _iconId;
private readonly Bundle _bundleExtra;
public PluginPopupMenuItem(Context ctx, string pluginPackage, string fieldId, string displayText, int iconId, Bundle bundleExtra)
{
_ctx = ctx;
_pluginPackage = pluginPackage;
_fieldId = fieldId;
_displayText = displayText;
_iconId = iconId;
_bundleExtra = bundleExtra;
}
public Drawable Icon
{
get { return _ctx.PackageManager.GetResourcesForApplication(_pluginPackage).GetDrawable(_iconId); }
}
public string Text
{
get { return _displayText; }
}
public void HandleClick()
{
Intent i = new Intent(Strings.ActionEntryActionSelected);
i.SetPackage(_pluginPackage);
i.PutExtra(Strings.ExtraActionData, _bundleExtra);
i.PutExtra(Strings.ExtraFieldId, _fieldId);
i.PutExtra(Strings.ExtraSender, _ctx.PackageName);
PluginHost.AddEntryToIntent(i, Entry);
_ctx.SendBroadcast(i);
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using Android.App;
using Android.Views;
using Android.Widget;
namespace keepass2android
{
internal class StandardStringView : IStringView
{
private readonly int _viewId;
private readonly int _containerViewId;
private readonly Activity _activity;
public StandardStringView(int viewId, int containerViewId, Activity activity)
{
_viewId = viewId;
_containerViewId = containerViewId;
_activity = activity;
}
public string Text
{
set
{
View container = _activity.FindViewById(_containerViewId);
TextView tv = (TextView) _activity.FindViewById(_viewId);
if (String.IsNullOrEmpty(value))
{
container.Visibility = tv.Visibility = ViewStates.Gone;
}
else
{
container.Visibility = tv.Visibility = ViewStates.Visible;
tv.Text = value;
}
}
get
{
TextView tv = (TextView) _activity.FindViewById(_viewId);
return tv.Text;
}
}
}
}

View File

@ -0,0 +1,32 @@
using Android.Graphics.Drawables;
using PluginHostTest;
namespace keepass2android
{
internal class WriteBinaryToFilePopupItem : IPopupMenuItem
{
private readonly string _key;
private readonly EntryActivity _activity;
public WriteBinaryToFilePopupItem(string key, EntryActivity activity)
{
_key = key;
_activity = activity;
}
public Drawable Icon
{
get { return _activity.Resources.GetDrawable(Android.Resource.Drawable.IcMenuSave); }
}
public string Text
{
get { return _activity.Resources.GetString(Resource.String.SaveAttachmentDialog_save); }
}
public void HandleClick()
{
_activity.WriteBinaryToFile(_key, false);
}
}
}

View File

@ -1,3 +1,6 @@
using Android.Content.PM;
using Android.Content.Res;
using Android.Graphics.Drawables;
using Android.Widget; using Android.Widget;
using Android.Content; using Android.Content;
using Android.Views; using Android.Views;
@ -11,26 +14,32 @@ namespace keepass2android
public class PluginItem public class PluginItem
{ {
private readonly string _package; private readonly string _package;
private readonly Context _ctx;
private readonly Resources _pluginRes;
public PluginItem(string package, string _label, int _icon, string _version, string _enabledStatus) public PluginItem(string package, string enabledStatus, Context ctx)
{ {
_package = package; _package = package;
Label = _label; _ctx = ctx;
Icon = _icon; EnabledStatus = enabledStatus;
Version = _version; _pluginRes = _ctx.PackageManager.GetResourcesForApplication(_package);
EnabledStatus = _enabledStatus;
} }
public string Label public string Label
{ {
get; get
set; {
return PluginDetailsActivity.GetStringFromPlugin(_pluginRes, _package, "kp2aplugin_title");
}
} }
public string Version public string Version
{ {
get; get
set; {
return _ctx.PackageManager.GetPackageInfo(_package, 0).VersionName;
}
} }
public string EnabledStatus public string EnabledStatus
@ -39,10 +48,12 @@ namespace keepass2android
set; set;
} }
public int Icon public Drawable Icon
{ {
get; get
set; {
return _ctx.PackageManager.GetApplicationIcon(_package);
}
} }
public string Package public string Package
@ -104,7 +115,7 @@ namespace keepass2android
holder.txtTitle.Text = item.Label; holder.txtTitle.Text = item.Label;
holder.txtVersion.Text = item.Version; holder.txtVersion.Text = item.Version;
holder.txtEnabledStatus.Text = item.EnabledStatus; holder.txtEnabledStatus.Text = item.EnabledStatus;
holder.imgIcon.SetImageResource(item.Icon); holder.imgIcon.SetImageDrawable(item.Icon);
return row; return row;
} }

View File

@ -125,6 +125,18 @@ namespace keepass2android
public bool IsValidAccessToken(string pluginPackage, string accessToken, string scope) public bool IsValidAccessToken(string pluginPackage, string accessToken, string scope)
{ {
if (pluginPackage == null)
{
Log.Warn(_tag, "No pluginPackage specified!");
return false;
}
if (accessToken == null)
{
Log.Warn(_tag, "No accessToken specified!");
return false;
}
var prefs = GetPreferencesForPlugin(pluginPackage); var prefs = GetPreferencesForPlugin(pluginPackage);
if (prefs.GetString(_accessToken, null) != accessToken) if (prefs.GetString(_accessToken, null) != accessToken)
{ {

View File

@ -96,7 +96,7 @@ namespace PluginHostTest
FindViewById<TextView>(resourceId).Visibility = ViewStates.Gone; FindViewById<TextView>(resourceId).Visibility = ViewStates.Gone;
} }
private static string GetStringFromPlugin(Resources pluginRes, string pluginPackage, string stringId) public static string GetStringFromPlugin(Resources pluginRes, string pluginPackage, string stringId)
{ {
int titleId = pluginRes.GetIdentifier(pluginPackage + ":string/"+stringId, null, null); int titleId = pluginRes.GetIdentifier(pluginPackage + ":string/"+stringId, null, null);
string title = null; string title = null;

View File

@ -1,8 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Xml.Linq;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.Content.PM; using Android.Content.PM;
@ -11,9 +12,12 @@ using Android.Runtime;
using Android.Util; using Android.Util;
using Android.Views; using Android.Views;
using Android.Widget; using Android.Widget;
using KeePassLib;
using KeePassLib.Serialization;
using KeePassLib.Utility;
using Keepass2android; using Keepass2android;
using Keepass2android.Pluginsdk; using Keepass2android.Pluginsdk;
using Org.Json;
using PluginHostTest; using PluginHostTest;
namespace keepass2android namespace keepass2android
@ -26,7 +30,7 @@ namespace keepass2android
private const string _tag = "KP2A_PluginHost"; private const string _tag = "KP2A_PluginHost";
private static readonly string[] _validScopes = { Strings.ScopeDatabaseActions }; private static readonly string[] _validScopes = { Strings.ScopeDatabaseActions, Strings.ScopeCurrentEntry };
public static void TriggerRequests(Context ctx) public static void TriggerRequests(Context ctx)
{ {
@ -138,6 +142,34 @@ namespace keepass2android
return true; return true;
} }
public static void AddEntryToIntent(Intent intent, PwEntry entry)
{
/*//add the entry XML
not yet implemented. What to do with attachments?
MemoryStream memStream = new MemoryStream();
KdbxFile.WriteEntries(memStream, new[] {entry});
string entryData = StrUtil.Utf8.GetString(memStream.ToArray());
intent.PutExtra(Strings.ExtraEntryData, entryData);
*/
//add the compiled string array (placeholders replaced taking into account the db context)
Dictionary<string, string> compiledFields = new Dictionary<string, string>();
foreach (var pair in entry.Strings)
{
String key = pair.Key;
String value = entry.Strings.ReadSafe(key);
value = SprEngine.Compile(value, new SprContext(entry, App.Kp2A.GetDb().KpDatabase, SprCompileFlags.All));
compiledFields.Add(StrUtil.SafeXmlString(pair.Key), value);
}
JSONObject json = new JSONObject(compiledFields);
var jsonStr = json.ToString();
intent.PutExtra(Strings.ExtraCompiledEntryData, jsonStr);
intent.PutExtra(Strings.ExtraEntryId, entry.Uuid.ToHexString());
}
} }
} }

View File

@ -56,24 +56,38 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Activity1.cs" /> <Compile Include="Activity1.cs" />
<Compile Include="App.cs" />
<Compile Include="ClickView.cs" /> <Compile Include="ClickView.cs" />
<Compile Include="CopyToClipboardService.cs" />
<Compile Include="EntryActivity.cs" /> <Compile Include="EntryActivity.cs" />
<Compile Include="EntryContentsView.cs" /> <Compile Include="EntryContentsView.cs" />
<Compile Include="EntrySection.cs" /> <Compile Include="EntrySection.cs" />
<Compile Include="EntryActivityClasses\ExtraStringView.cs" />
<Compile Include="EntryActivityClasses\IPopupMenuItem.cs" />
<Compile Include="EntryActivityClasses\IStringView.cs" />
<Compile Include="Kp2aShortHelpView.cs" /> <Compile Include="Kp2aShortHelpView.cs" />
<Compile Include="EntryActivityClasses\OpenBinaryPopupItem.cs" />
<Compile Include="PluginDatabase.cs" /> <Compile Include="PluginDatabase.cs" />
<Compile Include="PluginDetailsActivity.cs" /> <Compile Include="PluginDetailsActivity.cs" />
<Compile Include="PluginArrayAdapter.cs" /> <Compile Include="PluginArrayAdapter.cs" />
<Compile Include="PluginListActivity.cs" /> <Compile Include="PluginListActivity.cs" />
<Compile Include="PluginHost.cs" /> <Compile Include="PluginHost.cs" />
<Compile Include="EntryActivityClasses\PluginMenuOption.cs" />
<Compile Include="EntryActivityClasses\PluginPopupMenuItem.cs" />
<Compile Include="Resources\Resource.Designer.cs" /> <Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SprCompileFlags.cs" />
<Compile Include="SprContext.cs" />
<Compile Include="SprEngine.cs" />
<Compile Include="EntryActivityClasses\StandardStringView.cs" />
<Compile Include="TextDrawable.cs" />
<Compile Include="TextViewSelect.cs" /> <Compile Include="TextViewSelect.cs" />
<Compile Include="TextWithHelp.cs" /> <Compile Include="TextWithHelp.cs" />
<Compile Include="EntryActivityClasses\WriteBinaryToFilePopupItem.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<TransformFile Include="Assets\DejaVuSansMono.ttf" /> <AndroidAsset Include="Assets\DejaVuSansMono.ttf" />
<TransformFile Include="Assets\fontawesome-webfont.ttf" /> <AndroidAsset Include="Assets\fontawesome-webfont.ttf" />
<None Include="Resources\AboutResources.txt" /> <None Include="Resources\AboutResources.txt" />
<None Include="Assets\AboutAssets.txt" /> <None Include="Assets\AboutAssets.txt" />
<AndroidResource Include="Resources\Layout\plugin_list.xml"> <AndroidResource Include="Resources\Layout\plugin_list.xml">
@ -93,6 +107,10 @@
<TransformFile Include="Properties\AndroidManifest.xml" /> <TransformFile Include="Properties\AndroidManifest.xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj">
<Project>{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}</Project>
<Name>KeePassLib2Android</Name>
</ProjectReference>
<ProjectReference Include="..\PluginSdkBinding\PluginSdkBinding.csproj"> <ProjectReference Include="..\PluginSdkBinding\PluginSdkBinding.csproj">
<Project>{3da3911e-36de-465e-8f15-f1991b6437e5}</Project> <Project>{3da3911e-36de-465e-8f15-f1991b6437e5}</Project>
<Name>PluginSdkBinding</Name> <Name>PluginSdkBinding</Name>
@ -567,6 +585,18 @@
<ItemGroup> <ItemGroup>
<AndroidResource Include="Resources\Layout\entry_view.xml" /> <AndroidResource Include="Resources\Layout\entry_view.xml" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\vdots_holodark.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\vdots.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_menu_copy_holo_light.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Menu\entry.xml" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -14,6 +14,7 @@ namespace keepass2android
public class PluginListActivity : ListActivity public class PluginListActivity : ListActivity
{ {
private PluginArrayAdapter _pluginArrayAdapter; private PluginArrayAdapter _pluginArrayAdapter;
private List<PluginItem> _items;
protected override void OnCreate(Bundle bundle) protected override void OnCreate(Bundle bundle)
{ {
@ -22,31 +23,35 @@ namespace keepass2android
//TODO _design.ApplyTheme(); //TODO _design.ApplyTheme();
SetContentView(Resource.Layout.plugin_list); SetContentView(Resource.Layout.plugin_list);
ListView listView = FindViewById<ListView>(Android.Resource.Id.List);
listView.ItemClick +=
(sender, args) =>
{
Intent i = new Intent(this, typeof(PluginDetailsActivity));
i.PutExtra("PluginPackage", _items[args.Position].Package);
StartActivity(i);
};
// Create your application here
}
protected override void OnResume()
{
base.OnResume();
PluginDatabase pluginDb = new PluginDatabase(this); PluginDatabase pluginDb = new PluginDatabase(this);
List<PluginItem> items = (from pluginPackage in pluginDb.GetAllPluginPackages() _items = (from pluginPackage in pluginDb.GetAllPluginPackages()
let version = PackageManager.GetPackageInfo(pluginPackage, 0).VersionName let version = PackageManager.GetPackageInfo(pluginPackage, 0).VersionName
let enabledStatus = pluginDb.IsEnabled(pluginPackage) ? GetString(Resource.String.plugin_enabled) : GetString(Resource.String.plugin_disabled) let enabledStatus = pluginDb.IsEnabled(pluginPackage) ? GetString(Resource.String.plugin_enabled) : GetString(Resource.String.plugin_disabled)
select new PluginItem(pluginPackage, "the plugin", Resource.Drawable.Icon, version, enabledStatus)).ToList(); select new PluginItem(pluginPackage, enabledStatus, this)).ToList();
/* /*
{ {
new PluginItem("PluginA", Resource.Drawable.Icon, "keepass2android.plugina", "connected"), new PluginItem("PluginA", Resource.Drawable.Icon, "keepass2android.plugina", "connected"),
new PluginItem("KeepassNFC", Resource.Drawable.Icon, "com.bla.blubb.plugina", "disconnected") new PluginItem("KeepassNFC", Resource.Drawable.Icon, "com.bla.blubb.plugina", "disconnected")
}; };
* */ * */
_pluginArrayAdapter = new PluginArrayAdapter(this, Resource.Layout.ListViewPluginRow, items); _pluginArrayAdapter = new PluginArrayAdapter(this, Resource.Layout.ListViewPluginRow, _items);
ListAdapter = _pluginArrayAdapter; ListAdapter = _pluginArrayAdapter;
ListView listView = FindViewById<ListView>(Android.Resource.Id.List);
listView.ItemClick +=
(sender, args) =>
{
Intent i = new Intent(this, typeof(PluginDetailsActivity));
i.PutExtra("PluginPackage", items[args.Position].Package);
StartActivity(i);
};
// Create your application here
} }
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

View File

@ -1,7 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content" android:id="@+id/extra_title_container"
android:layout_height="wrap_content" android:layout_width="fill_parent"
android:text="Title" android:layout_height="wrap_content">
android:layout_marginLeft="10dp" <TextView
style="@style/ExtraFieldHeader" /> android:id="@+id/entry_title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Title"
style="@style/EntryFieldHeader" />
</LinearLayout>

View File

@ -1,8 +1,26 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout
android:layout_width="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content" android:id="@+id/extra_container"
android:typeface="monospace" android:layout_height="wrap_content"
android:text="Value" android:layout_width="fill_parent"
android:layout_marginLeft="30dp" android:orientation="horizontal"
style="@style/EntryItem" /> android:clickable="true"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:id="@+id/extra_vdots"
android:layout_width="wrap_content"
android:layout_height="15dp"
android:src="@drawable/vdots"
android:gravity="right|bottom"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
/>
<TextView
android:id="@+id/entry_extra"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:typeface="monospace"
android:layout_toLeftOf="@id/extra_vdots"
style="@style/EntryItem" />
</RelativeLayout>

View File

@ -5,162 +5,260 @@
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:orientation="vertical"> android:orientation="vertical">
<!-- Username -->
<TextView <LinearLayout android:id="@+id/entryfield_container_username"
android:id="@+id/entry_user_name_label" android:layout_height="wrap_content"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:orientation="vertical">
android:text="@string/entry_user_name"
style="@style/EntryFieldHeader" <TextView
android:id="@+id/entry_user_name_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/entry_user_name"
style="@style/EntryFieldHeader"
/> />
<TextView
android:id="@+id/entry_user_name" <!-- Username -->
android:layout_width="fill_parent" <RelativeLayout android:id="@+id/username_container"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textIsSelectable="true" android:layout_width="fill_parent"
style="@style/EntryItem" /> android:orientation="horizontal"
<!-- URL --> android:clickable="true"
<TextView android:background="?android:attr/selectableItemBackground">
android:id="@+id/entry_url_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/entry_url"
style="@style/EntryFieldHeader"
/>
<TextView
android:id="@+id/entry_url"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:autoLink="all"
android:textIsSelectable="true"
style="@style/EntryItem" />
<!-- Password -->
<TextView
android:id="@+id/entry_password_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/entry_password"
style="@style/EntryFieldHeader"
/>
<TextView
android:id="@+id/entry_password"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:password="true"
android:textIsSelectable="true"
android:typeface="monospace"
style="@style/EntryItem" />
<!-- Comment -->
<TextView
android:id="@+id/entry_comment_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/entry_comment"
style="@style/EntryFieldHeader"
/>
<TextView
android:id="@+id/entry_comment"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textIsSelectable="true"
style="@style/EntryItem" />
<TextView <ImageView
android:id="@+id/entry_extra_strings_label" android:id="@+id/username_vdots"
android:layout_width="fill_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="15dp"
android:text="@string/entry_extra_strings" android:src="@drawable/vdots"
style="@style/EntryFieldHeader" android:gravity="right|bottom"
/> android:layout_alignParentRight="true"
<LinearLayout android:layout_alignParentBottom="true"
android:id="@+id/extra_strings" />
android:layout_width="fill_parent" <TextView
android:layout_height="wrap_content" android:id="@+id/entry_user_name"
android:layout_marginLeft="12dp" android:layout_width="fill_parent"
android:layout_marginRight="12dp" android:layout_height="wrap_content"
android:orientation="vertical" /> android:layout_toLeftOf="@id/username_vdots"
<!-- file attachments --> style="@style/EntryItem" />
<TextView </RelativeLayout>
android:id="@+id/entry_binaries_label" </LinearLayout>
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/entry_binaries"
style="@style/EntryFieldHeader"
/>
<LinearLayout
android:id="@+id/binaries"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<!--Tags -->
<TextView
android:id="@+id/entry_tags_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/entry_tags"
style="@style/EntryFieldHeader"
/>
<TextView
android:id="@+id/entry_tags"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textIsSelectable="true"
style="@style/EntryItem" />
<!--Override URL-->
<TextView
android:id="@+id/entry_override_url_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/entry_override_url"
style="@style/EntryFieldHeader"
/>
<TextView
android:id="@+id/entry_override_url"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:autoLink="all"
android:textIsSelectable="true"
style="@style/EntryItem" />
<!-- Created --> <LinearLayout android:id="@+id/entryfield_container_url"
<TextView android:layout_height="wrap_content"
android:id="@+id/entry_created_label" android:layout_width="fill_parent"
android:layout_width="fill_parent" android:orientation="vertical">
android:layout_height="wrap_content"
android:text="@string/entry_created" <!-- URL -->
style="@style/EntryFieldHeader" <TextView
android:id="@+id/entry_url_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/entry_url"
style="@style/EntryFieldHeader"
/>
<RelativeLayout android:id="@+id/url_container"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal"
android:clickable="true"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:id="@+id/url_vdots"
android:layout_width="wrap_content"
android:layout_height="15dp"
android:src="@drawable/vdots"
android:gravity="right|bottom"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
/>
<TextView
android:id="@+id/entry_url"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:autoLink="all"
android:layout_toLeftOf="@id/url_vdots"
style="@style/EntryItem" />
</RelativeLayout>
</LinearLayout>
<LinearLayout android:id="@+id/entryfield_container_password"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="vertical">
<!-- Password -->
<TextView
android:id="@+id/entry_password_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/entry_password"
style="@style/EntryFieldHeader"
/> />
<TextView <RelativeLayout android:id="@+id/password_container"
android:id="@+id/entry_created" android:layout_height="wrap_content"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:orientation="horizontal"
style="@style/EntryItem" /> android:clickable="true"
<!-- Modified --> android:background="?android:attr/selectableItemBackground">
<TextView <ImageView
android:id="@+id/entry_modified_label" android:id="@+id/password_vdots"
android:layout_width="fill_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="15dp"
android:text="@string/entry_modified" android:src="@drawable/vdots"
style="@style/EntryFieldHeader" android:gravity="right|bottom"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
/>
<TextView
android:id="@+id/entry_password"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:password="true"
android:typeface="monospace"
android:layout_toLeftOf="@id/password_vdots"
style="@style/EntryItem" />
</RelativeLayout>
</LinearLayout>
<LinearLayout android:id="@+id/entryfield_container_comment"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="vertical">
<!-- Comment -->
<TextView
android:id="@+id/entry_comment_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/entry_comment"
style="@style/EntryFieldHeader"
/> />
<TextView <TextView
android:id="@+id/entry_modified" android:id="@+id/entry_comment"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/EntryItem" /> android:textIsSelectable="true"
<!-- Expires --> style="@style/EntryItem" />
<TextView </LinearLayout>
android:id="@+id/entry_expires_label"
android:layout_width="fill_parent" <LinearLayout
android:layout_height="wrap_content" android:id="@+id/extra_strings"
android:text="@string/entry_expires" android:layout_width="fill_parent"
style="@style/EntryFieldHeader" android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="vertical" />
<!-- file attachments -->
<TextView
android:id="@+id/entry_binaries_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/entry_binaries"
style="@style/EntryFieldHeader"
/> />
<TextView <LinearLayout
android:id="@+id/entry_expires" android:id="@+id/binaries"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/EntryItem" /> android:orientation="vertical" />
<!-- Property Change Conflict | id:@+id/entry_url_label --> <!--Tags -->
<LinearLayout android:id="@+id/entryfield_container_tags"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="vertical">
<TextView
android:id="@+id/entry_tags_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/entry_tags"
style="@style/EntryFieldHeader"
/>
<TextView
android:id="@+id/entry_tags"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textIsSelectable="true"
style="@style/EntryItem" />
</LinearLayout>
<LinearLayout android:id="@+id/entryfield_container_overrideurl"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="vertical">
<!--Override URL-->
<TextView
android:id="@+id/entry_override_url_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/entry_override_url"
style="@style/EntryFieldHeader"
/>
<TextView
android:id="@+id/entry_override_url"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:autoLink="all"
android:textIsSelectable="true"
style="@style/EntryItem" />
</LinearLayout>
<LinearLayout android:id="@+id/entryfield_container_created"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="vertical">
<!-- Created -->
<TextView
android:id="@+id/entry_created_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/entry_created"
style="@style/EntryFieldHeader"
/>
<TextView
android:id="@+id/entry_created"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="@style/EntryItem" />
</LinearLayout>
<LinearLayout android:id="@+id/entryfield_container_modified"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="vertical">
<!-- Modified -->
<TextView
android:id="@+id/entry_modified_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/entry_modified"
style="@style/EntryFieldHeader"
/>
<TextView
android:id="@+id/entry_modified"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="@style/EntryItem" />
</LinearLayout>
<LinearLayout android:id="@+id/entryfield_container_expires"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="vertical">
<!-- Expires -->
<TextView
android:id="@+id/entry_expires_label"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/entry_expires"
style="@style/EntryFieldHeader"
/>
<TextView
android:id="@+id/entry_expires"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="@style/EntryItem" />
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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 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 <http://www.gnu.org/licenses/>.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_donate"
android:icon="@android:drawable/ic_menu_share"
android:title="@string/menu_donate"
/>
<item android:id="@+id/menu_toggle_pass"
android:icon="@android:drawable/ic_menu_view"
android:title="@string/show_password"
/>
<item android:id="@+id/menu_goto_url"
android:icon="@android:drawable/ic_menu_upload"
android:title="@string/menu_url"
/>
<!--<item android:id="@+id/menu_copy_user"
android:icon="@android:drawable/ic_menu_set_as"
android:title="@string/menu_copy_user"
/>
<item android:id="@+id/menu_copy_pass"
android:icon="@android:drawable/ic_menu_agenda"
android:title="@string/menu_copy_pass"
/>-->
<item android:id="@+id/menu_lock"
android:icon="@android:drawable/ic_lock_lock"
android:title="@string/menu_lock"
/>
<item android:id="@+id/menu_suggest_improvements"
android:icon="@android:drawable/ic_menu_directions"
android:title="@string/suggest_improvements"
/>
<item android:id="@+id/menu_rate"
android:icon="@android:drawable/star_off"
android:title="@string/rate_app"
/>
<item android:id="@+id/menu_translate"
android:title="@string/translate_app"
/>
</menu>

View File

@ -54,7 +54,7 @@
<item name="android:layout_marginRight">12dip</item> <item name="android:layout_marginRight">12dip</item>
<item name="android:paddingLeft">4dp</item> <item name="android:paddingLeft">4dp</item>
<item name="android:textColor">?android:attr/textColorSecondary</item> <item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:textSize">18sp</item> <item name="android:textSize">16sp</item>
</style> </style>
<style name="EntryFieldHeader"> <style name="EntryFieldHeader">
@ -63,11 +63,11 @@
<item name="android:layout_marginLeft">12dip</item> <item name="android:layout_marginLeft">12dip</item>
<item name="android:layout_marginRight">12dip</item> <item name="android:layout_marginRight">12dip</item>
<item name="android:layout_marginBottom">3dp</item> <item name="android:layout_marginBottom">3dp</item>
<item name="android:layout_marginTop">8dp</item> <item name="android:layout_marginTop">14dp</item>
<item name="android:paddingLeft">4dp</item> <item name="android:paddingLeft">4dp</item>
<item name="android:textAllCaps">true</item> <item name="android:textAllCaps">true</item>
<item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">18sp</item> <item name="android:textSize">16sp</item>
<item name="android:textStyle">bold</item> <item name="android:textStyle">bold</item>
</style> </style>

View File

@ -8,6 +8,6 @@
<string name="SCOPE_DATABASE_ACTIONS_explanation">Plugin will be notified when a database is opened, closed or saved.</string> <string name="SCOPE_DATABASE_ACTIONS_explanation">Plugin will be notified when a database is opened, closed or saved.</string>
<string name="SCOPE_CURRENT_ENTRY_title">Current entry data</string> <string name="SCOPE_CURRENT_ENTRY_title">Current entry data</string>
<string name="SCOPE_CURRENT_ENTRY_explanation">Plugin will receive all data about the current database entry and will be allowed to offer actions on it.</string> <string name="SCOPE_CURRENT_ENTRY_explanation">Plugin will receive all data about the current database entry and will be allowed to offer actions and modify the display of it.</string>
</resources> </resources>

View File

@ -0,0 +1,7 @@
namespace keepass2android
{
public enum SprCompileFlags
{
All
}
}

View File

@ -0,0 +1,13 @@
using System;
using KeePassLib;
namespace keepass2android
{
public class SprContext
{
public SprContext(PwEntry entry, PwDatabase kpDatabase, object all)
{
}
}
}

View File

@ -0,0 +1,11 @@
namespace keepass2android
{
public class SprEngine
{
public static string Compile(string value, SprContext sprContext)
{
return value;
}
}
}

View File

@ -0,0 +1,56 @@
using System;
using Android.Content;
using Android.Graphics;
using Android.Graphics.Drawables;
namespace keepass2android
{
/// <summary>
/// Shows text as a drawable.
/// </summary>
/// Based on http://stackoverflow.com/questions/3972445/how-to-put-text-in-a-drawable
public class TextDrawable: Drawable {
private readonly String _text;
private readonly Paint _paint;
private static Typeface _iconFont;
public TextDrawable(String text, Context ctx) {
_text = text;
if (_iconFont == null)
_iconFont = Typeface.CreateFromAsset(ctx.Assets, "fontawesome-webfont.ttf");
_paint = new Paint {Color = (Color.White), TextSize = 22f, AntiAlias = true};
//_paint.SetTypeface(_iconFont);
_paint.SetShadowLayer(6f, 0, 0, Color.Black);
_paint.SetStyle(Paint.Style.Fill);
_paint.TextAlign = Paint.Align.Left;
}
public override void Draw(Canvas canvas) {
canvas.DrawText("x"+_text, 0, 0, _paint);
}
public override void SetAlpha(int alpha) {
_paint.Alpha = alpha;
}
public override void SetColorFilter(ColorFilter cf)
{
_paint.SetColorFilter(cf);
}
public override int Opacity
{
get { return -3; /*translucent*/ }
}
}
}

View File

@ -1,5 +1,7 @@
package keepass2android.pluginsdk; package keepass2android.pluginsdk;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import org.json.JSONArray; import org.json.JSONArray;
@ -10,10 +12,15 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor; import android.content.SharedPreferences.Editor;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.PopupMenu;
public class AccessManager public class AccessManager
{ {
private static final String _tag = "Kp2aPluginSDK";
private static final String PREF_KEY_SCOPE = "scope"; private static final String PREF_KEY_SCOPE = "scope";
private static final String PREF_KEY_TOKEN = "token"; private static final String PREF_KEY_TOKEN = "token";
@ -49,19 +56,40 @@ public class AccessManager
public static void storeAccessToken(Context ctx, String hostPackage, String accessToken, ArrayList<String> scopes) public static void storeAccessToken(Context ctx, String hostPackage, String accessToken, ArrayList<String> scopes)
{ {
SharedPreferences prefs = getPrefsForHost(ctx, hostPackage); SharedPreferences prefs = getPrefsForHost(ctx, hostPackage);
//
if (accessToken.equals(prefs.getString(PREF_KEY_TOKEN, "")))
{
//token already available
return;
}
Editor edit = prefs.edit(); Editor edit = prefs.edit();
edit.putString(PREF_KEY_TOKEN, accessToken); edit.putString(PREF_KEY_TOKEN, accessToken);
edit.putString(PREF_KEY_SCOPE, stringArrayToString(scopes)); String scopesString = stringArrayToString(scopes);
edit.putString(PREF_KEY_SCOPE, scopesString);
edit.commit(); edit.commit();
Log.d(_tag, "stored access token " + accessToken.substring(0, 4)+"... for "+scopes.size()+" scopes ("+scopesString+").");
}
public static void preparePopup(Object popupMenu)
{
try
{
Field[] fields = popupMenu.getClass().getDeclaredFields();
for (Field field : fields) {
if ("mPopup".equals(field.getName())) {
field.setAccessible(true);
Object menuPopupHelper = field.get(popupMenu);
Class<?> classPopupHelper = Class.forName(menuPopupHelper
.getClass().getName());
Method setForceIcons = classPopupHelper.getMethod(
"setForceShowIcon", boolean.class);
setForceIcons.invoke(menuPopupHelper, true);
break;
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
} }
private static SharedPreferences getPrefsForHost(Context ctx, private static SharedPreferences getPrefsForHost(Context ctx,
@ -72,14 +100,22 @@ public class AccessManager
public static String tryGetAccessToken(Context ctx, String hostPackage, ArrayList<String> scopes) { public static String tryGetAccessToken(Context ctx, String hostPackage, ArrayList<String> scopes) {
if (TextUtils.isEmpty(hostPackage))
{
Log.d(_tag, "hostPackage is empty!");
return null;
}
SharedPreferences prefs = getPrefsForHost(ctx, hostPackage); SharedPreferences prefs = getPrefsForHost(ctx, hostPackage);
ArrayList<String> currentScope = stringToStringArray(prefs.getString(PREF_KEY_SCOPE, "")); String scopesString = prefs.getString(PREF_KEY_SCOPE, "");
Log.d(_tag, "scopes: "+ scopesString);
ArrayList<String> currentScope = stringToStringArray(scopesString);
if (isSubset(scopes, currentScope)) if (isSubset(scopes, currentScope))
{ {
return prefs.getString(PREF_KEY_TOKEN, null); return prefs.getString(PREF_KEY_TOKEN, null);
} }
else else
{ {
Log.d(_tag, "looks like scope changed. Access token invalid.");
return null; return null;
} }
} }
@ -88,7 +124,10 @@ public class AccessManager
ArrayList<String> availableScopes) { ArrayList<String> availableScopes) {
for (String r: requiredScopes){ for (String r: requiredScopes){
if (availableScopes.indexOf(r)<0) if (availableScopes.indexOf(r)<0)
{
Log.d(_tag, "Scope "+r+" not available. "+availableScopes.size());
return false; return false;
}
} }
return true; return true;
} }
@ -106,4 +145,15 @@ public class AccessManager
} }
} }
/**
* Returns a valid access token or throws PluginAccessException
*/
public static String getAccessToken(Context context, String hostPackage,
ArrayList<String> scopes) throws PluginAccessException {
String accessToken = tryGetAccessToken(context, hostPackage, scopes);
if (accessToken == null)
throw new PluginAccessException(hostPackage, scopes);
return accessToken;
}
} }

View File

@ -34,6 +34,8 @@ public abstract class PluginAccessBroadcastReceiver extends BroadcastReceiver {
public void onReceive(Context ctx, Intent intent) { public void onReceive(Context ctx, Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
android.util.Log.d("KP2A.pluginsdk", "received broadcast with action="+action); android.util.Log.d("KP2A.pluginsdk", "received broadcast with action="+action);
if (action == null)
return;
if (action.equals(Strings.ACTION_TRIGGER_REQUEST_ACCESS)) if (action.equals(Strings.ACTION_TRIGGER_REQUEST_ACCESS))
{ {
requestAccess(ctx, intent); requestAccess(ctx, intent);

View File

@ -1,19 +1,114 @@
package keepass2android.pluginsdk; package keepass2android.pluginsdk;
public class Strings { public class Strings {
/**
* Plugin is notified about actions like open/close/update a database.
*/
public static final String SCOPE_DATABASE_ACTIONS = "keepass2android.SCOPE_DATABASE_ACTIONS"; public static final String SCOPE_DATABASE_ACTIONS = "keepass2android.SCOPE_DATABASE_ACTIONS";
/**
* Plugin is notified when an entry is opened.
*/
public static final String SCOPE_CURRENT_ENTRY = "keepass2android.SCOPE_CURRENT_ENTRY"; public static final String SCOPE_CURRENT_ENTRY = "keepass2android.SCOPE_CURRENT_ENTRY";
/**
* Extra key to transfer a (json serialized) list of scopes
*/
public static final String EXTRA_SCOPES = "keepass2android.EXTRA_SCOPES"; public static final String EXTRA_SCOPES = "keepass2android.EXTRA_SCOPES";
/**
* Extra key for sending the package name of the sender of a broadcast.
* Should be set in every broadcast.
*/
public static final String EXTRA_SENDER = "keepass2android.EXTRA_SENDER"; public static final String EXTRA_SENDER = "keepass2android.EXTRA_SENDER";
/**
* Extra key for sending a request token. The request token is passed from
* KP2A to the plugin. It's used in the authorization process.
*/
public static final String EXTRA_REQUEST_TOKEN = "keepass2android.EXTRA_REQUEST_TOKEN"; public static final String EXTRA_REQUEST_TOKEN = "keepass2android.EXTRA_REQUEST_TOKEN";
/**
* Action sent from KP2A to the plugin to indicate that the plugin should request
* access (sending it's scopes)
*/
public static final String ACTION_TRIGGER_REQUEST_ACCESS = "keepass2android.ACTION_TRIGGER_REQUEST_ACCESS"; public static final String ACTION_TRIGGER_REQUEST_ACCESS = "keepass2android.ACTION_TRIGGER_REQUEST_ACCESS";
/**
* Action sent from the plugin to KP2A including the scopes.
*/
public static final String ACTION_REQUEST_ACCESS = "keepass2android.ACTION_REQUEST_ACCESS"; public static final String ACTION_REQUEST_ACCESS = "keepass2android.ACTION_REQUEST_ACCESS";
/**
* Action sent from the KP2A to the plugin when the user grants access.
* Will contain an access token.
*/
public static final String ACTION_RECEIVE_ACCESS = "keepass2android.ACTION_RECEIVE_ACCESS"; public static final String ACTION_RECEIVE_ACCESS = "keepass2android.ACTION_RECEIVE_ACCESS";
/**
* Action sent from KP2A to the plugin to indicate that access is not or no longer valid.
*/
public static final String ACTION_REVOKE_ACCESS = "keepass2android.ACTION_REVOKE_ACCESS"; public static final String ACTION_REVOKE_ACCESS = "keepass2android.ACTION_REVOKE_ACCESS";
public static final String EXTRA_ACCESS_TOKEN = "EXTRA_ACCESS_TOKEN";
//static final String SCOPE_DATABASE_ACTIONS = "keepass2android.SCOPE_DATABASE_ACTIONS"; /**
* Action sent from KP2A to the plugin to indicate that an entry was opened.
* The Intent contains the full entry data.
*/
public static final String ACTION_OPEN_ENTRY= "keepass2android.ACTION_OPEN_ENTRY";
/**
* Action sent from KP2A to the plugin to indicate that an entry activity was closed.
*/
public static final String ACTION_CLOSE_ENTRY_VIEW= "keepass2android.ACTION_CLOSE_ENTRY_VIEW";
/**
* Extra key for a string containing the GUID of the entry.
*/
public static final String EXTRA_ENTRY_ID= "keepass2android.EXTRA_ENTRY_DATA";
/**
* Json serialized data of the PwEntry (C# class) representing the opened entry.
* currently not implemented.
*/
//public static final String EXTRA_ENTRY_DATA = "keepass2android.EXTRA_ENTRY_DATA";
/**
* Json serialized list of fields, compiled using the database context (i.e. placeholders are replaced already)
*/
public static final String EXTRA_COMPILED_ENTRY_DATA = "keepass2android.EXTRA_COMPILED_ENTRY_DATA";
/**
* Extra key for passing the access token (both ways)
*/
public static final String EXTRA_ACCESS_TOKEN = "keepass2android.EXTRA_ACCESS_TOKEN";
/**
* Action for an intent from the plugin to KP2A to add menu options regarding the currently open entry.
* Requires SCOPE_CURRENT_ENTRY.
*/
public static final String ACTION_ADD_ENTRY_ACTION = "keepass2android.ACTION_ADD_ENTRY_ACTION";
public static final String EXTRA_ACTION_DISPLAY_TEXT = "keepass2android.EXTRA_ACTION_DISPLAY_TEXT";
public static final String EXTRA_ACTION_ICON_RES_ID = "keepass2android.EXTRA_ACTION_ICON_RES_ID";
public static final String EXTRA_FIELD_ID = "keepass2android.EXTRA_FIELD_ID";
/** Extra for ACTION_ADD_ENTRY_ACTION and ACTION_ENTRY_ACTION_SELECTED to pass data specifying the action parameters.*/
public static final String EXTRA_ACTION_DATA = "keepass2android.EXTRA_ACTION_DATA";
/**
* Action for an intent from KP2A to the plugin when an action added with ACTION_ADD_ENTRY_ACTION was selected by the user.
*
*/
public static final String ACTION_ENTRY_ACTION_SELECTED = "keepass2android.ACTION_ENTRY_ACTION_SELECTED";
/**
* Action for an intent from the plugin to KP2A to set (i.e. add or update) a field in the entry.
* May be used to update existing or add new fields at any time while the entry is opened.
*/
public static final String ACTION_SET_ENTRY_FIELD = "keepass2android.ACTION_SET_ENTRY_FIELD";
public static final String EXTRA_FIELD_VALUE = "keepass2android.EXTRA_FIELD_VALUE";
public static final String EXTRA_FIELD_PROTECTED = "keepass2android.EXTRA_FIELD_PROTECTED";
public static final String PREFIX_STRING = "STRING_";
public static final String PREFIX_BINARY = "BINARY_";
} }