mirror of
https://github.com/moparisthebest/keepass2android
synced 2024-12-23 15:38:47 -05:00
Plugins:
* EntryOutput is passed to CopyToClipboardService * Modifications of EntryOutput are passed to plugins to enable actions on added fields * PluginDatabase checks if Plugin is still installed and always updates the list of plugins (had an issue where a plugin had a request token but was not in pluginList) * first version of QR plugin implemented
This commit is contained in:
parent
07038d7549
commit
53dd47044b
@ -1,17 +1,74 @@
|
|||||||
|
using System;
|
||||||
using KeePassLib;
|
using KeePassLib;
|
||||||
|
using KeePassLib.Collections;
|
||||||
using KeePassLib.Keys;
|
using KeePassLib.Keys;
|
||||||
|
using KeePassLib.Security;
|
||||||
using KeePassLib.Serialization;
|
using KeePassLib.Serialization;
|
||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the strings which are output from a PwEntry.
|
||||||
|
/// </summary>
|
||||||
|
/// In contrast to the original PwEntry, this means that placeholders are replaced. Also, plugins may modify
|
||||||
|
/// or add fields.
|
||||||
|
public class PwEntryOutput
|
||||||
|
{
|
||||||
|
private readonly PwEntry _entry;
|
||||||
|
private readonly PwDatabase _db;
|
||||||
|
private readonly ProtectedStringDictionary _outputStrings = new ProtectedStringDictionary();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs the PwEntryOutput by replacing the placeholders
|
||||||
|
/// </summary>
|
||||||
|
public PwEntryOutput(PwEntry entry, PwDatabase db)
|
||||||
|
{
|
||||||
|
_entry = entry;
|
||||||
|
_db = db;
|
||||||
|
|
||||||
|
foreach (var pair in entry.Strings)
|
||||||
|
{
|
||||||
|
_outputStrings.Set(pair.Key, new ProtectedString(entry.Strings.Get(pair.Key).IsProtected, GetStringAndReplacePlaceholders(pair.Key)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetStringAndReplacePlaceholders(string key)
|
||||||
|
{
|
||||||
|
String value = Entry.Strings.ReadSafe(key);
|
||||||
|
value = SprEngine.Compile(value, new SprContext(Entry, _db, SprCompileFlags.All));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the ID of the entry
|
||||||
|
/// </summary>
|
||||||
|
public PwUuid Uuid
|
||||||
|
{
|
||||||
|
get { return Entry.Uuid; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The output strings for the represented entry
|
||||||
|
/// </summary>
|
||||||
|
public ProtectedStringDictionary OutputStrings { get { return _outputStrings; } }
|
||||||
|
|
||||||
|
public PwEntry Entry
|
||||||
|
{
|
||||||
|
get { return _entry; }
|
||||||
|
}
|
||||||
|
}
|
||||||
public class App
|
public class App
|
||||||
{
|
{
|
||||||
|
|
||||||
public class Kp2A
|
public class Kp2A
|
||||||
{
|
{
|
||||||
private static Db _mDb;
|
private static Db _mDb;
|
||||||
|
|
||||||
public class Db
|
public class Db
|
||||||
{
|
{
|
||||||
|
public PwEntryOutput LastOpenedEntry { get; set; }
|
||||||
|
|
||||||
public void SetEntry(PwEntry e)
|
public void SetEntry(PwEntry e)
|
||||||
{
|
{
|
||||||
KpDatabase = new PwDatabase();
|
KpDatabase = new PwDatabase();
|
||||||
|
@ -1,14 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using Android.App;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
using Android.Runtime;
|
||||||
using Android.Widget;
|
using Android.Widget;
|
||||||
using KeePassLib.Security;
|
using KeePassLib.Security;
|
||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
internal class CopyToClipboardService
|
[Service]
|
||||||
|
public class CopyToClipboardService: Service
|
||||||
{
|
{
|
||||||
|
public CopyToClipboardService()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public CopyToClipboardService(IntPtr javaReference, JniHandleOwnership transfer)
|
||||||
|
: base(javaReference, transfer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void CopyValueToClipboardWithTimeout(Context ctx, string text)
|
public static void CopyValueToClipboardWithTimeout(Context ctx, string text)
|
||||||
{
|
{
|
||||||
Toast.MakeText(ctx, text, ToastLength.Short).Show();
|
Toast.MakeText(ctx, text, ToastLength.Short).Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override IBinder OnBind(Intent intent)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -113,6 +113,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
_activity.AddPluginAction(pluginPackage,
|
_activity.AddPluginAction(pluginPackage,
|
||||||
intent.GetStringExtra(Strings.ExtraFieldId),
|
intent.GetStringExtra(Strings.ExtraFieldId),
|
||||||
|
intent.GetStringExtra(Strings.ExtraActionId),
|
||||||
intent.GetStringExtra(Strings.ExtraActionDisplayText),
|
intent.GetStringExtra(Strings.ExtraActionDisplayText),
|
||||||
intent.GetIntExtra(Strings.ExtraActionIconResId, -1),
|
intent.GetIntExtra(Strings.ExtraActionIconResId, -1),
|
||||||
intent.GetBundleExtra(Strings.ExtraActionData));
|
intent.GetBundleExtra(Strings.ExtraActionData));
|
||||||
@ -156,6 +157,7 @@ namespace keepass2android
|
|||||||
|
|
||||||
private void SetPluginField(string key, string value, bool isProtected)
|
private void SetPluginField(string key, string value, bool isProtected)
|
||||||
{
|
{
|
||||||
|
//update or add the string view:
|
||||||
IStringView existingField;
|
IStringView existingField;
|
||||||
if (_stringViews.TryGetValue(key, out existingField))
|
if (_stringViews.TryGetValue(key, out existingField))
|
||||||
{
|
{
|
||||||
@ -168,13 +170,47 @@ namespace keepass2android
|
|||||||
extraGroup.AddView(view.View);
|
extraGroup.AddView(view.View);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//update the Entry output in the App database and notify the CopyToClipboard service
|
||||||
|
App.Kp2A.GetDb().LastOpenedEntry.OutputStrings.Set(key, new ProtectedString(isProtected, value));
|
||||||
|
Intent updateKeyboardIntent = new Intent(this, typeof(CopyToClipboardService));
|
||||||
|
Intent.SetAction(Intents.UpdateKeyboard);
|
||||||
|
updateKeyboardIntent.PutExtra(KeyEntry, Entry.Uuid.ToHexString());
|
||||||
|
StartService(updateKeyboardIntent);
|
||||||
|
|
||||||
|
//notify plugins
|
||||||
|
NotifyPluginsOnModification(Strings.PrefixString+key);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddPluginAction(string pluginPackage, string fieldId, string displayText, int iconId, Bundle bundleExtra)
|
private void AddPluginAction(string pluginPackage, string fieldId, string popupItemId, string displayText, int iconId, Bundle bundleExtra)
|
||||||
{
|
{
|
||||||
if (fieldId != null)
|
if (fieldId != null)
|
||||||
{
|
{
|
||||||
_popupMenuItems[fieldId].Add(new PluginPopupMenuItem(this, pluginPackage, fieldId, displayText, iconId, bundleExtra));
|
try
|
||||||
|
{
|
||||||
|
//create a new popup item for the plugin action:
|
||||||
|
var newPopup = new PluginPopupMenuItem(this, pluginPackage, fieldId, popupItemId, displayText, iconId, bundleExtra);
|
||||||
|
//see if we already have a popup item for this field with the same item id
|
||||||
|
var popupsForField = _popupMenuItems[fieldId];
|
||||||
|
var popupItemPos = popupsForField.FindIndex(0,
|
||||||
|
item =>
|
||||||
|
(item is PluginPopupMenuItem) &&
|
||||||
|
((PluginPopupMenuItem)item).PopupItemId == popupItemId);
|
||||||
|
|
||||||
|
//replace existing or add
|
||||||
|
if (popupItemPos >= 0)
|
||||||
|
{
|
||||||
|
popupsForField[popupItemPos] = newPopup;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
popupsForField.Add(newPopup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Kp2aLog.Log(e.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -185,6 +221,7 @@ namespace keepass2android
|
|||||||
i.SetPackage(pluginPackage);
|
i.SetPackage(pluginPackage);
|
||||||
i.PutExtra(Strings.ExtraActionData, bundleExtra);
|
i.PutExtra(Strings.ExtraActionData, bundleExtra);
|
||||||
i.PutExtra(Strings.ExtraSender, PackageName);
|
i.PutExtra(Strings.ExtraSender, PackageName);
|
||||||
|
PluginHost.AddEntryToIntent(i, App.Kp2A.GetDb().LastOpenedEntry);
|
||||||
|
|
||||||
var menuOption = new PluginMenuOption()
|
var menuOption = new PluginMenuOption()
|
||||||
{
|
{
|
||||||
@ -407,6 +444,8 @@ namespace keepass2android
|
|||||||
|
|
||||||
SetupEditButtons();
|
SetupEditButtons();
|
||||||
|
|
||||||
|
App.Kp2A.GetDb().LastOpenedEntry = new PwEntryOutput(Entry, App.Kp2A.GetDb().KpDatabase);
|
||||||
|
|
||||||
RegisterReceiver(new PluginActionReceiver(this), new IntentFilter(Strings.ActionAddEntryAction));
|
RegisterReceiver(new PluginActionReceiver(this), new IntentFilter(Strings.ActionAddEntryAction));
|
||||||
RegisterReceiver(new PluginFieldReceiver(this), new IntentFilter(Strings.ActionSetEntryField));
|
RegisterReceiver(new PluginFieldReceiver(this), new IntentFilter(Strings.ActionSetEntryField));
|
||||||
|
|
||||||
@ -419,7 +458,22 @@ namespace keepass2android
|
|||||||
|
|
||||||
Intent i = new Intent(Strings.ActionOpenEntry);
|
Intent i = new Intent(Strings.ActionOpenEntry);
|
||||||
i.PutExtra(Strings.ExtraSender, PackageName);
|
i.PutExtra(Strings.ExtraSender, PackageName);
|
||||||
PluginHost.AddEntryToIntent(i, Entry);
|
AddEntryToIntent(i);
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var plugin in new PluginDatabase(this).GetPluginsWithAcceptedScope(Strings.ScopeCurrentEntry))
|
||||||
|
{
|
||||||
|
i.SetPackage(plugin);
|
||||||
|
SendBroadcast(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void NotifyPluginsOnModification(string fieldId)
|
||||||
|
{
|
||||||
|
Intent i = new Intent(Strings.ActionEntryOutputModified);
|
||||||
|
i.PutExtra(Strings.ExtraSender, PackageName);
|
||||||
|
i.PutExtra(Strings.ExtraFieldId, fieldId);
|
||||||
|
AddEntryToIntent(i);
|
||||||
|
|
||||||
|
|
||||||
foreach (var plugin in new PluginDatabase(this).GetPluginsWithAcceptedScope(Strings.ScopeCurrentEntry))
|
foreach (var plugin in new PluginDatabase(this).GetPluginsWithAcceptedScope(Strings.ScopeCurrentEntry))
|
||||||
{
|
{
|
||||||
@ -842,5 +896,10 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
Toast.MakeText(this, "opening file TODO", ToastLength.Short).Show();
|
Toast.MakeText(this, "opening file TODO", ToastLength.Short).Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddEntryToIntent(Intent intent)
|
||||||
|
{
|
||||||
|
PluginHost.AddEntryToIntent(intent, App.Kp2A.GetDb().LastOpenedEntry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
using Android.Content;
|
||||||
|
using Android.Graphics.Drawables;
|
||||||
|
using PluginHostTest;
|
||||||
|
|
||||||
|
namespace keepass2android
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reperesents the popup menu item in EntryActivity to copy a string to clipboard
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
src/PluginHostTest/EntryActivityClasses/GotoUrlMenuItem.cs
Normal file
34
src/PluginHostTest/EntryActivityClasses/GotoUrlMenuItem.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using Android.Graphics.Drawables;
|
||||||
|
using PluginHostTest;
|
||||||
|
|
||||||
|
namespace keepass2android
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reperesents the popup menu item in EntryActivity to go to the URL in the field
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using Android.Content;
|
|
||||||
using Android.Graphics.Drawables;
|
using Android.Graphics.Drawables;
|
||||||
using KeePassLib;
|
using KeePassLib;
|
||||||
using PluginHostTest;
|
|
||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for popup menu items in EntryActivity
|
||||||
|
/// </summary>
|
||||||
internal interface IPopupMenuItem
|
internal interface IPopupMenuItem
|
||||||
{
|
{
|
||||||
Drawable Icon { get; }
|
Drawable Icon { get; }
|
||||||
@ -13,100 +14,4 @@ namespace keepass2android
|
|||||||
|
|
||||||
void HandleClick();
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -3,6 +3,9 @@ using PluginHostTest;
|
|||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the popup menu item in EntryActivity to open the associated attachment
|
||||||
|
/// </summary>
|
||||||
internal class OpenBinaryPopupItem : IPopupMenuItem
|
internal class OpenBinaryPopupItem : IPopupMenuItem
|
||||||
{
|
{
|
||||||
private readonly string _key;
|
private readonly string _key;
|
||||||
|
@ -5,20 +5,25 @@ using Keepass2android.Pluginsdk;
|
|||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a popup menu item in EntryActivity which was added by a plugin. The click will therefore broadcast to the plugin.
|
||||||
|
/// </summary>
|
||||||
class PluginPopupMenuItem : IPopupMenuItem
|
class PluginPopupMenuItem : IPopupMenuItem
|
||||||
{
|
{
|
||||||
private readonly Context _ctx;
|
private readonly EntryActivity _activity;
|
||||||
private readonly string _pluginPackage;
|
private readonly string _pluginPackage;
|
||||||
private readonly string _fieldId;
|
private readonly string _fieldId;
|
||||||
|
private readonly string _popupItemId;
|
||||||
private readonly string _displayText;
|
private readonly string _displayText;
|
||||||
private readonly int _iconId;
|
private readonly int _iconId;
|
||||||
private readonly Bundle _bundleExtra;
|
private readonly Bundle _bundleExtra;
|
||||||
|
|
||||||
public PluginPopupMenuItem(Context ctx, string pluginPackage, string fieldId, string displayText, int iconId, Bundle bundleExtra)
|
public PluginPopupMenuItem(EntryActivity activity, string pluginPackage, string fieldId, string popupItemId, string displayText, int iconId, Bundle bundleExtra)
|
||||||
{
|
{
|
||||||
_ctx = ctx;
|
_activity = activity;
|
||||||
_pluginPackage = pluginPackage;
|
_pluginPackage = pluginPackage;
|
||||||
_fieldId = fieldId;
|
_fieldId = fieldId;
|
||||||
|
_popupItemId = popupItemId;
|
||||||
_displayText = displayText;
|
_displayText = displayText;
|
||||||
_iconId = iconId;
|
_iconId = iconId;
|
||||||
_bundleExtra = bundleExtra;
|
_bundleExtra = bundleExtra;
|
||||||
@ -26,22 +31,29 @@ namespace keepass2android
|
|||||||
|
|
||||||
public Drawable Icon
|
public Drawable Icon
|
||||||
{
|
{
|
||||||
get { return _ctx.PackageManager.GetResourcesForApplication(_pluginPackage).GetDrawable(_iconId); }
|
get { return _activity.PackageManager.GetResourcesForApplication(_pluginPackage).GetDrawable(_iconId); }
|
||||||
}
|
}
|
||||||
public string Text
|
public string Text
|
||||||
{
|
{
|
||||||
get { return _displayText; }
|
get { return _displayText; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string PopupItemId
|
||||||
|
{
|
||||||
|
get { return _popupItemId; }
|
||||||
|
}
|
||||||
|
|
||||||
public void HandleClick()
|
public void HandleClick()
|
||||||
{
|
{
|
||||||
Intent i = new Intent(Strings.ActionEntryActionSelected);
|
Intent i = new Intent(Strings.ActionEntryActionSelected);
|
||||||
i.SetPackage(_pluginPackage);
|
i.SetPackage(_pluginPackage);
|
||||||
i.PutExtra(Strings.ExtraActionData, _bundleExtra);
|
i.PutExtra(Strings.ExtraActionData, _bundleExtra);
|
||||||
i.PutExtra(Strings.ExtraFieldId, _fieldId);
|
i.PutExtra(Strings.ExtraFieldId, _fieldId);
|
||||||
i.PutExtra(Strings.ExtraSender, _ctx.PackageName);
|
i.PutExtra(Strings.ExtraSender, _activity.PackageName);
|
||||||
PluginHost.AddEntryToIntent(i, Entry);
|
|
||||||
|
|
||||||
_ctx.SendBroadcast(i);
|
_activity.AddEntryToIntent(i);
|
||||||
|
|
||||||
|
_activity.SendBroadcast(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
using Android.Graphics.Drawables;
|
||||||
|
using PluginHostTest;
|
||||||
|
|
||||||
|
namespace keepass2android
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reperesents the popup menu item in EntryActivity to toggle visibility of all protected strings (e.g. Password)
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,9 @@ using PluginHostTest;
|
|||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the popup menu item in EntryActivity to store the binary attachment on SD card
|
||||||
|
/// </summary>
|
||||||
internal class WriteBinaryToFilePopupItem : IPopupMenuItem
|
internal class WriteBinaryToFilePopupItem : IPopupMenuItem
|
||||||
{
|
{
|
||||||
private readonly string _key;
|
private readonly string _key;
|
||||||
|
@ -10,7 +10,9 @@ using PluginHostTest;
|
|||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents information about a plugin for display in the plugin list activity
|
||||||
|
/// </summary>
|
||||||
public class PluginItem
|
public class PluginItem
|
||||||
{
|
{
|
||||||
private readonly string _package;
|
private readonly string _package;
|
||||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
|
using Android.Content.PM;
|
||||||
using Android.Util;
|
using Android.Util;
|
||||||
using Keepass2android.Pluginsdk;
|
using Keepass2android.Pluginsdk;
|
||||||
|
|
||||||
@ -32,16 +33,15 @@ namespace keepass2android
|
|||||||
var editor = prefs.Edit();
|
var editor = prefs.Edit();
|
||||||
editor.PutString(_requesttoken, Guid.NewGuid().ToString());
|
editor.PutString(_requesttoken, Guid.NewGuid().ToString());
|
||||||
editor.Commit();
|
editor.Commit();
|
||||||
|
|
||||||
var hostPrefs = GetHostPrefs();
|
|
||||||
var plugins = hostPrefs.GetStringSet(_pluginlist, new List<string>());
|
|
||||||
if (!plugins.Contains(packageName))
|
|
||||||
{
|
|
||||||
plugins.Add(packageName);
|
|
||||||
hostPrefs.Edit().PutStringSet(_pluginlist, plugins).Commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
var hostPrefs = GetHostPrefs();
|
||||||
|
var plugins = hostPrefs.GetStringSet(_pluginlist, new List<string>());
|
||||||
|
if (!plugins.Contains(packageName))
|
||||||
|
{
|
||||||
|
plugins.Add(packageName);
|
||||||
|
hostPrefs.Edit().PutStringSet(_pluginlist, plugins).Commit();
|
||||||
|
}
|
||||||
|
|
||||||
return prefs;
|
return prefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +63,20 @@ namespace keepass2android
|
|||||||
public IEnumerable<String> GetAllPluginPackages()
|
public IEnumerable<String> GetAllPluginPackages()
|
||||||
{
|
{
|
||||||
var hostPrefs = GetHostPrefs();
|
var hostPrefs = GetHostPrefs();
|
||||||
return hostPrefs.GetStringSet(_pluginlist, new List<string>());
|
return hostPrefs.GetStringSet(_pluginlist, new List<string>()).Where(IsPackageInstalled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPackageInstalled(string targetPackage)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PackageInfo info = _ctx.PackageManager.GetPackageInfo(targetPackage, PackageInfoFlags.MetaData);
|
||||||
|
}
|
||||||
|
catch (PackageManager.NameNotFoundException e)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsEnabled(string pluginPackage)
|
public bool IsEnabled(string pluginPackage)
|
||||||
|
@ -13,6 +13,7 @@ using Android.Util;
|
|||||||
using Android.Views;
|
using Android.Views;
|
||||||
using Android.Widget;
|
using Android.Widget;
|
||||||
using KeePassLib;
|
using KeePassLib;
|
||||||
|
using KeePassLib.Collections;
|
||||||
using KeePassLib.Serialization;
|
using KeePassLib.Serialization;
|
||||||
using KeePassLib.Utility;
|
using KeePassLib.Utility;
|
||||||
using Keepass2android;
|
using Keepass2android;
|
||||||
@ -142,7 +143,7 @@ namespace keepass2android
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AddEntryToIntent(Intent intent, PwEntry entry)
|
public static void AddEntryToIntent(Intent intent, PwEntryOutput entry)
|
||||||
{
|
{
|
||||||
/*//add the entry XML
|
/*//add the entry XML
|
||||||
not yet implemented. What to do with attachments?
|
not yet implemented. What to do with attachments?
|
||||||
@ -151,22 +152,12 @@ namespace keepass2android
|
|||||||
string entryData = StrUtil.Utf8.GetString(memStream.ToArray());
|
string entryData = StrUtil.Utf8.GetString(memStream.ToArray());
|
||||||
intent.PutExtra(Strings.ExtraEntryData, entryData);
|
intent.PutExtra(Strings.ExtraEntryData, entryData);
|
||||||
*/
|
*/
|
||||||
//add the compiled string array (placeholders replaced taking into account the db context)
|
//add the output string array (placeholders replaced taking into account the db context)
|
||||||
Dictionary<string, string> compiledFields = new Dictionary<string, string>();
|
Dictionary<string, string> outputFields = entry.OutputStrings.ToDictionary(pair => StrUtil.SafeXmlString(pair.Key), pair => pair.Value.ReadString());
|
||||||
foreach (var pair in entry.Strings)
|
|
||||||
{
|
|
||||||
String key = pair.Key;
|
|
||||||
|
|
||||||
String value = entry.Strings.ReadSafe(key);
|
JSONObject json = new JSONObject(outputFields);
|
||||||
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();
|
var jsonStr = json.ToString();
|
||||||
intent.PutExtra(Strings.ExtraCompiledEntryData, jsonStr);
|
intent.PutExtra(Strings.ExtraEntryOutputData, jsonStr);
|
||||||
|
|
||||||
intent.PutExtra(Strings.ExtraEntryId, entry.Uuid.ToHexString());
|
intent.PutExtra(Strings.ExtraEntryId, entry.Uuid.ToHexString());
|
||||||
|
|
||||||
|
@ -60,11 +60,15 @@
|
|||||||
<Compile Include="ClickView.cs" />
|
<Compile Include="ClickView.cs" />
|
||||||
<Compile Include="CopyToClipboardService.cs" />
|
<Compile Include="CopyToClipboardService.cs" />
|
||||||
<Compile Include="EntryActivity.cs" />
|
<Compile Include="EntryActivity.cs" />
|
||||||
|
<Compile Include="EntryActivityClasses\CopyToClipboardPopupMenuIcon.cs" />
|
||||||
|
<Compile Include="EntryActivityClasses\GotoUrlMenuItem.cs" />
|
||||||
|
<Compile Include="EntryActivityClasses\ToggleVisibilityPopupMenuItem.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\ExtraStringView.cs" />
|
||||||
<Compile Include="EntryActivityClasses\IPopupMenuItem.cs" />
|
<Compile Include="EntryActivityClasses\IPopupMenuItem.cs" />
|
||||||
<Compile Include="EntryActivityClasses\IStringView.cs" />
|
<Compile Include="EntryActivityClasses\IStringView.cs" />
|
||||||
|
<Compile Include="Intents.cs" />
|
||||||
<Compile Include="Kp2aShortHelpView.cs" />
|
<Compile Include="Kp2aShortHelpView.cs" />
|
||||||
<Compile Include="EntryActivityClasses\OpenBinaryPopupItem.cs" />
|
<Compile Include="EntryActivityClasses\OpenBinaryPopupItem.cs" />
|
||||||
<Compile Include="PluginDatabase.cs" />
|
<Compile Include="PluginDatabase.cs" />
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
package keepass2android.pluginsdk;
|
||||||
|
|
||||||
|
public class KeepassDefs {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default identifier string for the title field. Should not contain
|
||||||
|
/// spaces, tabs or other whitespace.
|
||||||
|
/// </summary>
|
||||||
|
public static String TitleField = "Title";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default identifier string for the user name field. Should not contain
|
||||||
|
/// spaces, tabs or other whitespace.
|
||||||
|
/// </summary>
|
||||||
|
public static String UserNameField = "UserName";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default identifier string for the password field. Should not contain
|
||||||
|
/// spaces, tabs or other whitespace.
|
||||||
|
/// </summary>
|
||||||
|
public static String PasswordField = "Password";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default identifier string for the URL field. Should not contain
|
||||||
|
/// spaces, tabs or other whitespace.
|
||||||
|
/// </summary>
|
||||||
|
public static String UrlField = "URL";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default identifier string for the notes field. Should not contain
|
||||||
|
/// spaces, tabs or other whitespace.
|
||||||
|
/// </summary>
|
||||||
|
public static String NotesField = "Notes";
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean IsStandardField(String strFieldName)
|
||||||
|
{
|
||||||
|
if(strFieldName == null)
|
||||||
|
return false;
|
||||||
|
if(strFieldName.equals(TitleField)) return true;
|
||||||
|
if(strFieldName.equals(UserNameField)) return true;
|
||||||
|
if(strFieldName.equals(PasswordField)) return true;
|
||||||
|
if(strFieldName.equals(UrlField)) return true;
|
||||||
|
if(strFieldName.equals(NotesField)) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package keepass2android.pluginsdk;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class PluginAccessException extends Exception {
|
||||||
|
|
||||||
|
public PluginAccessException(String what)
|
||||||
|
{
|
||||||
|
super(what);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PluginAccessException(String hostPackage, ArrayList<String> scopes) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,224 @@
|
|||||||
|
package keepass2android.pluginsdk;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public abstract class PluginActionBroadcastReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
|
protected abstract class PluginActionBase
|
||||||
|
{
|
||||||
|
protected Context _context;
|
||||||
|
protected Intent _intent;
|
||||||
|
|
||||||
|
public PluginActionBase(Context context, Intent intent)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_intent = intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHostPackage() {
|
||||||
|
return _intent.getStringExtra(Strings.EXTRA_SENDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context getContext()
|
||||||
|
{
|
||||||
|
return _context;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HashMap<String, String> getEntryFieldsFromIntent()
|
||||||
|
{
|
||||||
|
HashMap<String, String> res = new HashMap<String, String>();
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject(_intent.getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA));
|
||||||
|
for(Iterator<String> iter = json.keys();iter.hasNext();) {
|
||||||
|
String key = iter.next();
|
||||||
|
String value = json.get(key).toString();
|
||||||
|
Log.d("KP2APluginSDK", "received " + key+"/"+value);
|
||||||
|
res.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class ActionSelected extends PluginActionBase
|
||||||
|
{
|
||||||
|
public ActionSelected(Context ctx, Intent intent) {
|
||||||
|
super(ctx, intent);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return the Bundle associated with the action. This bundle can be set in OpenEntry.add(Entry)FieldAction
|
||||||
|
*/
|
||||||
|
public Bundle getActionData()
|
||||||
|
{
|
||||||
|
return _intent.getBundleExtra(Strings.EXTRA_ACTION_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return the field id which was selected. null if an entry action (in the options menu) was selected.
|
||||||
|
*/
|
||||||
|
public String getFieldId()
|
||||||
|
{
|
||||||
|
return _intent.getStringExtra(Strings.EXTRA_FIELD_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return true if an entry action, i.e. an option from the options menu, was selected. False if an option
|
||||||
|
* in a popup menu for a certain field was selected.
|
||||||
|
*/
|
||||||
|
public boolean isEntryAction()
|
||||||
|
{
|
||||||
|
return getFieldId() == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<String, String> getEntryFields()
|
||||||
|
{
|
||||||
|
return getEntryFieldsFromIntent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class CloseEntryView extends PluginActionBase
|
||||||
|
{
|
||||||
|
public CloseEntryView(Context context, Intent intent) {
|
||||||
|
super(context, intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEntryId()
|
||||||
|
{
|
||||||
|
return _intent.getStringExtra(Strings.EXTRA_ENTRY_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class OpenEntry extends PluginActionBase
|
||||||
|
{
|
||||||
|
|
||||||
|
public OpenEntry(Context context, Intent intent)
|
||||||
|
{
|
||||||
|
super(context, intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEntryId()
|
||||||
|
{
|
||||||
|
return _intent.getStringExtra(Strings.EXTRA_ENTRY_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<String, String> getEntryFields()
|
||||||
|
{
|
||||||
|
return getEntryFieldsFromIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEntryAction(String actionDisplayText, int actionIconResourceId, Bundle actionData) throws PluginAccessException
|
||||||
|
{
|
||||||
|
addEntryFieldAction(null, null, actionDisplayText, actionIconResourceId, actionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEntryFieldAction(String actionId, String fieldId, String actionDisplayText, int actionIconResourceId, Bundle actionData) throws PluginAccessException
|
||||||
|
{
|
||||||
|
Intent i = new Intent(Strings.ACTION_ADD_ENTRY_ACTION);
|
||||||
|
ArrayList<String> scope = new ArrayList<String>();
|
||||||
|
scope.add(Strings.SCOPE_CURRENT_ENTRY);
|
||||||
|
i.putExtra(Strings.EXTRA_ACCESS_TOKEN, AccessManager.getAccessToken(_context, getHostPackage(), scope));
|
||||||
|
i.setPackage(getHostPackage());
|
||||||
|
i.putExtra(Strings.EXTRA_SENDER, _context.getPackageName());
|
||||||
|
i.putExtra(Strings.EXTRA_ACTION_DATA, actionData);
|
||||||
|
i.putExtra(Strings.EXTRA_ACTION_DISPLAY_TEXT, actionDisplayText);
|
||||||
|
i.putExtra(Strings.EXTRA_ACTION_ICON_RES_ID, actionIconResourceId);
|
||||||
|
i.putExtra(Strings.EXTRA_ENTRY_ID, getEntryId());
|
||||||
|
i.putExtra(Strings.EXTRA_FIELD_ID, fieldId);
|
||||||
|
i.putExtra(Strings.EXTRA_ACTION_ID, actionId);
|
||||||
|
|
||||||
|
_context.sendBroadcast(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEntryField(String fieldId, String fieldValue, boolean isProtected) throws PluginAccessException
|
||||||
|
{
|
||||||
|
Intent i = new Intent(Strings.ACTION_SET_ENTRY_FIELD);
|
||||||
|
ArrayList<String> scope = new ArrayList<String>();
|
||||||
|
scope.add(Strings.SCOPE_CURRENT_ENTRY);
|
||||||
|
i.putExtra(Strings.EXTRA_ACCESS_TOKEN, AccessManager.getAccessToken(_context, getHostPackage(), scope));
|
||||||
|
i.setPackage(getHostPackage());
|
||||||
|
i.putExtra(Strings.EXTRA_SENDER, _context.getPackageName());
|
||||||
|
i.putExtra(Strings.EXTRA_FIELD_VALUE, fieldValue);
|
||||||
|
i.putExtra(Strings.EXTRA_ENTRY_ID, getEntryId());
|
||||||
|
i.putExtra(Strings.EXTRA_FIELD_ID, fieldId);
|
||||||
|
i.putExtra(Strings.EXTRA_FIELD_PROTECTED, isProtected);
|
||||||
|
|
||||||
|
_context.sendBroadcast(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//EntryOutputModified is very similar to OpenEntry because it receives the same
|
||||||
|
//data (+ the field id which was modified)
|
||||||
|
protected class EntryOutputModified extends OpenEntry
|
||||||
|
{
|
||||||
|
|
||||||
|
public EntryOutputModified(Context context, Intent intent)
|
||||||
|
{
|
||||||
|
super(context, intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getModifiedFieldId()
|
||||||
|
{
|
||||||
|
return _intent.getStringExtra(Strings.EXTRA_FIELD_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context ctx, Intent intent) {
|
||||||
|
String action = intent.getAction();
|
||||||
|
android.util.Log.d("KP2A.pluginsdk", "received broadcast in PluginActionBroadcastReceiver with action="+action);
|
||||||
|
if (action == null)
|
||||||
|
return;
|
||||||
|
if (action.equals(Strings.ACTION_OPEN_ENTRY))
|
||||||
|
{
|
||||||
|
openEntry(new OpenEntry(ctx, intent));
|
||||||
|
}
|
||||||
|
else if (action.equals(Strings.ACTION_CLOSE_ENTRY_VIEW))
|
||||||
|
{
|
||||||
|
closeEntryView(new CloseEntryView(ctx, intent));
|
||||||
|
}
|
||||||
|
else if (action.equals(Strings.ACTION_ENTRY_ACTION_SELECTED))
|
||||||
|
{
|
||||||
|
actionSelected(new ActionSelected(ctx, intent));
|
||||||
|
}
|
||||||
|
else if (action.equals(Strings.ACTION_ENTRY_OUTPUT_MODIFIED))
|
||||||
|
{
|
||||||
|
entryOutputModified(new EntryOutputModified(ctx, intent));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//TODO handle unexpected action
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void closeEntryView(CloseEntryView closeEntryView) {}
|
||||||
|
|
||||||
|
protected void actionSelected(ActionSelected actionSelected) {}
|
||||||
|
|
||||||
|
protected void openEntry(OpenEntry oe) {}
|
||||||
|
|
||||||
|
protected void entryOutputModified(EntryOutputModified eom) {}
|
||||||
|
|
||||||
|
}
|
@ -52,6 +52,12 @@ public class Strings {
|
|||||||
*/
|
*/
|
||||||
public static final String ACTION_OPEN_ENTRY= "keepass2android.ACTION_OPEN_ENTRY";
|
public static final String ACTION_OPEN_ENTRY= "keepass2android.ACTION_OPEN_ENTRY";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action sent from KP2A to the plugin to indicate that an entry output field was modified/added.
|
||||||
|
* The Intent contains the full new entry data.
|
||||||
|
*/
|
||||||
|
public static final String ACTION_ENTRY_OUTPUT_MODIFIED= "keepass2android.ACTION_ENTRY_OUTPUT_MODIFIED";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action sent from KP2A to the plugin to indicate that an entry activity was closed.
|
* Action sent from KP2A to the plugin to indicate that an entry activity was closed.
|
||||||
*/
|
*/
|
||||||
@ -69,9 +75,9 @@ public class Strings {
|
|||||||
//public static final String EXTRA_ENTRY_DATA = "keepass2android.EXTRA_ENTRY_DATA";
|
//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)
|
* Json serialized list of fields, transformed using the database context (i.e. placeholders are replaced already)
|
||||||
*/
|
*/
|
||||||
public static final String EXTRA_COMPILED_ENTRY_DATA = "keepass2android.EXTRA_COMPILED_ENTRY_DATA";
|
public static final String EXTRA_ENTRY_OUTPUT_DATA = "keepass2android.EXTRA_ENTRY_OUTPUT_DATA";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extra key for passing the access token (both ways)
|
* Extra key for passing the access token (both ways)
|
||||||
@ -88,6 +94,12 @@ public class Strings {
|
|||||||
public static final String EXTRA_ACTION_ICON_RES_ID = "keepass2android.EXTRA_ACTION_ICON_RES_ID";
|
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";
|
public static final String EXTRA_FIELD_ID = "keepass2android.EXTRA_FIELD_ID";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to pass an id for the action. Each actionId may occur only once per field, otherwise the previous
|
||||||
|
* action with same id is replaced by the new action.
|
||||||
|
*/
|
||||||
|
public static final String EXTRA_ACTION_ID = "keepass2android.EXTRA_ACTION_ID";
|
||||||
|
|
||||||
/** Extra for ACTION_ADD_ENTRY_ACTION and ACTION_ENTRY_ACTION_SELECTED to pass data specifying the action parameters.*/
|
/** 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";
|
public static final String EXTRA_ACTION_DATA = "keepass2android.EXTRA_ACTION_DATA";
|
||||||
@ -110,5 +122,6 @@ public class Strings {
|
|||||||
public static final String PREFIX_STRING = "STRING_";
|
public static final String PREFIX_STRING = "STRING_";
|
||||||
public static final String PREFIX_BINARY = "BINARY_";
|
public static final String PREFIX_BINARY = "BINARY_";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
9
src/java/PluginQR/.classpath
Normal file
9
src/java/PluginQR/.classpath
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" path="src"/>
|
||||||
|
<classpathentry kind="src" path="gen"/>
|
||||||
|
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||||
|
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||||
|
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||||
|
<classpathentry kind="output" path="bin/classes"/>
|
||||||
|
</classpath>
|
33
src/java/PluginQR/.project
Normal file
33
src/java/PluginQR/.project
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>PluginQR</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
4
src/java/PluginQR/.settings/org.eclipse.jdt.core.prefs
Normal file
4
src/java/PluginQR/.settings/org.eclipse.jdt.core.prefs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
eclipse.preferences.version=1
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||||
|
org.eclipse.jdt.core.compiler.compliance=1.6
|
||||||
|
org.eclipse.jdt.core.compiler.source=1.6
|
46
src/java/PluginQR/AndroidManifest.xml
Normal file
46
src/java/PluginQR/AndroidManifest.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="keepass2android.plugin.qr"
|
||||||
|
android:versionCode="1"
|
||||||
|
android:versionName="1.0" >
|
||||||
|
|
||||||
|
<uses-sdk
|
||||||
|
android:minSdkVersion="14"
|
||||||
|
android:targetSdkVersion="19" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@drawable/qrcode"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/AppTheme" >
|
||||||
|
<activity
|
||||||
|
android:name="keepass2android.plugin.qr.QRActivity"
|
||||||
|
android:label="@string/title_activity_qr" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
|
||||||
|
<receiver android:name="AccessReceiver" android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="keepass2android.ACTION_TRIGGER_REQUEST_ACCESS" />
|
||||||
|
<action android:name="keepass2android.ACTION_RECEIVE_ACCESS" />
|
||||||
|
<action android:name="keepass2android.ACTION_REVOKE_ACCESS" />
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
|
||||||
|
<receiver android:name="ActionReceiver" android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="keepass2android.ACTION_OPEN_ENTRY" />
|
||||||
|
<action android:name="keepass2android.ACTION_ENTRY_OUTPUT_MODIFIED" />
|
||||||
|
<action android:name="keepass2android.ACTION_ENTRY_ACTION_SELECTED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
@ -0,0 +1,6 @@
|
|||||||
|
/** Automatically generated file. DO NOT MODIFY */
|
||||||
|
package keepass2android.plugin.qr;
|
||||||
|
|
||||||
|
public final class BuildConfig {
|
||||||
|
public final static boolean DEBUG = true;
|
||||||
|
}
|
93
src/java/PluginQR/gen/keepass2android/plugin/qr/R.java
Normal file
93
src/java/PluginQR/gen/keepass2android/plugin/qr/R.java
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/* AUTO-GENERATED FILE. DO NOT MODIFY.
|
||||||
|
*
|
||||||
|
* This class was automatically generated by the
|
||||||
|
* aapt tool from the resource data it found. It
|
||||||
|
* should not be modified by hand.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package keepass2android.plugin.qr;
|
||||||
|
|
||||||
|
public final class R {
|
||||||
|
public static final class attr {
|
||||||
|
}
|
||||||
|
public static final class dimen {
|
||||||
|
/** Default screen margins, per the Android Design guidelines.
|
||||||
|
|
||||||
|
Example customization of dimensions originally defined in res/values/dimens.xml
|
||||||
|
(such as screen margins) for screens with more than 820dp of available width. This
|
||||||
|
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively).
|
||||||
|
|
||||||
|
*/
|
||||||
|
public static final int activity_horizontal_margin=0x7f060000;
|
||||||
|
public static final int activity_vertical_margin=0x7f060001;
|
||||||
|
}
|
||||||
|
public static final class drawable {
|
||||||
|
public static final int ic_launcher=0x7f020000;
|
||||||
|
public static final int qrcode=0x7f020001;
|
||||||
|
}
|
||||||
|
public static final class id {
|
||||||
|
public static final int cbIncludeLabel=0x7f080003;
|
||||||
|
public static final int container=0x7f080000;
|
||||||
|
public static final int expanded_image=0x7f080005;
|
||||||
|
public static final int qrView=0x7f080002;
|
||||||
|
public static final int spinner=0x7f080001;
|
||||||
|
public static final int tvError=0x7f080004;
|
||||||
|
}
|
||||||
|
public static final class layout {
|
||||||
|
public static final int activity_qr=0x7f030000;
|
||||||
|
public static final int fragment_qr=0x7f030001;
|
||||||
|
}
|
||||||
|
public static final class menu {
|
||||||
|
public static final int qr=0x7f070000;
|
||||||
|
}
|
||||||
|
public static final class string {
|
||||||
|
public static final int action_settings=0x7f040002;
|
||||||
|
public static final int action_show_qr=0x7f040003;
|
||||||
|
public static final int all_fields=0x7f040005;
|
||||||
|
public static final int app_name=0x7f040000;
|
||||||
|
public static final int include_label=0x7f040004;
|
||||||
|
public static final int kp2aplugin_author=0x7f040008;
|
||||||
|
public static final int kp2aplugin_shortdesc=0x7f040007;
|
||||||
|
public static final int kp2aplugin_title=0x7f040006;
|
||||||
|
public static final int title_activity_qr=0x7f040001;
|
||||||
|
}
|
||||||
|
public static final class style {
|
||||||
|
/**
|
||||||
|
Base application theme, dependent on API level. This theme is replaced
|
||||||
|
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
|
||||||
|
|
||||||
|
|
||||||
|
Theme customizations available in newer API levels can go in
|
||||||
|
res/values-vXX/styles.xml, while customizations related to
|
||||||
|
backward-compatibility can go here.
|
||||||
|
|
||||||
|
|
||||||
|
Base application theme for API 11+. This theme completely replaces
|
||||||
|
AppBaseTheme from res/values/styles.xml on API 11+ devices.
|
||||||
|
|
||||||
|
API 11 theme customizations can go here.
|
||||||
|
|
||||||
|
Base application theme for API 14+. This theme completely replaces
|
||||||
|
AppBaseTheme from BOTH res/values/styles.xml and
|
||||||
|
res/values-v11/styles.xml on API 14+ devices.
|
||||||
|
|
||||||
|
API 14 theme customizations can go here.
|
||||||
|
|
||||||
|
Base application theme, dependent on API level. This theme is replaced
|
||||||
|
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
|
||||||
|
|
||||||
|
|
||||||
|
Theme customizations available in newer API levels can go in
|
||||||
|
res/values-vXX/styles.xml, while customizations related to
|
||||||
|
backward-compatibility can go here.
|
||||||
|
|
||||||
|
*/
|
||||||
|
public static final int AppBaseTheme=0x7f050000;
|
||||||
|
/** Application theme.
|
||||||
|
All customizations that are NOT specific to a particular API-level can go here.
|
||||||
|
Application theme.
|
||||||
|
All customizations that are NOT specific to a particular API-level can go here.
|
||||||
|
*/
|
||||||
|
public static final int AppTheme=0x7f050001;
|
||||||
|
}
|
||||||
|
}
|
20
src/java/PluginQR/gen/keepass2android/pluginsdk/R.java
Normal file
20
src/java/PluginQR/gen/keepass2android/pluginsdk/R.java
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/* AUTO-GENERATED FILE. DO NOT MODIFY.
|
||||||
|
*
|
||||||
|
* This class was automatically generated by the
|
||||||
|
* aapt tool from the resource data it found. It
|
||||||
|
* should not be modified by hand.
|
||||||
|
*/
|
||||||
|
package keepass2android.pluginsdk;
|
||||||
|
|
||||||
|
public final class R {
|
||||||
|
public static final class drawable {
|
||||||
|
public static final int ic_launcher = 0x7f020000;
|
||||||
|
}
|
||||||
|
public static final class string {
|
||||||
|
public static final int app_name = 0x7f040000;
|
||||||
|
}
|
||||||
|
public static final class style {
|
||||||
|
public static final int AppBaseTheme = 0x7f050000;
|
||||||
|
public static final int AppTheme = 0x7f050001;
|
||||||
|
}
|
||||||
|
}
|
BIN
src/java/PluginQR/libs/core.jar
Normal file
BIN
src/java/PluginQR/libs/core.jar
Normal file
Binary file not shown.
20
src/java/PluginQR/proguard-project.txt
Normal file
20
src/java/PluginQR/proguard-project.txt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# To enable ProGuard in your project, edit project.properties
|
||||||
|
# to define the proguard.config property as described in that file.
|
||||||
|
#
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the ProGuard
|
||||||
|
# include property in project.properties.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
15
src/java/PluginQR/project.properties
Normal file
15
src/java/PluginQR/project.properties
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# This file is automatically generated by Android Tools.
|
||||||
|
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||||
|
#
|
||||||
|
# This file must be checked in Version Control Systems.
|
||||||
|
#
|
||||||
|
# To customize properties used by the Ant build system edit
|
||||||
|
# "ant.properties", and override values to adapt the script to your
|
||||||
|
# project structure.
|
||||||
|
#
|
||||||
|
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||||
|
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||||
|
|
||||||
|
# Project target.
|
||||||
|
target=android-19
|
||||||
|
android.library.reference.1=../Keepass2AndroidPluginSDK
|
BIN
src/java/PluginQR/res/drawable-hdpi/qrcode.png
Normal file
BIN
src/java/PluginQR/res/drawable-hdpi/qrcode.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
BIN
src/java/PluginQR/res/drawable-xhdpi/qrcode.png
Normal file
BIN
src/java/PluginQR/res/drawable-xhdpi/qrcode.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
8
src/java/PluginQR/res/layout/activity_qr.xml
Normal file
8
src/java/PluginQR/res/layout/activity_qr.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context="keepass2android.plugin.qr.QRActivity"
|
||||||
|
tools:ignore="MergeRootFrame" />
|
||||||
|
|
56
src/java/PluginQR/res/layout/fragment_qr.xml
Normal file
56
src/java/PluginQR/res/layout/fragment_qr.xml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
|
tools:context="keepass2android.plugin.qr.QRActivity$PlaceholderFragment" >
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinner"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_below="@+id/spinner"
|
||||||
|
android:id="@+id/qrView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:src="@drawable/qrcode"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/cbIncludeLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/qrView"
|
||||||
|
android:text="@string/include_label" />
|
||||||
|
<TextView android:id="@+id/tvError"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/cbIncludeLabel"
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
</ScrollView>
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/expanded_image"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="invisible"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
7
src/java/PluginQR/res/menu/qr.xml
Normal file
7
src/java/PluginQR/res/menu/qr.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:context="keepass2android.plugin.qr.QRActivity" >
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</menu>
|
10
src/java/PluginQR/res/values-w820dp/dimens.xml
Normal file
10
src/java/PluginQR/res/values-w820dp/dimens.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<resources>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Example customization of dimensions originally defined in res/values/dimens.xml
|
||||||
|
(such as screen margins) for screens with more than 820dp of available width. This
|
||||||
|
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively).
|
||||||
|
-->
|
||||||
|
<dimen name="activity_horizontal_margin">64dp</dimen>
|
||||||
|
|
||||||
|
</resources>
|
7
src/java/PluginQR/res/values/dimens.xml
Normal file
7
src/java/PluginQR/res/values/dimens.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||||
|
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||||
|
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||||
|
|
||||||
|
</resources>
|
18
src/java/PluginQR/res/values/strings.xml
Normal file
18
src/java/PluginQR/res/values/strings.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="app_name">QR Plugin for KP2A</string>
|
||||||
|
<string name="title_activity_qr">QRActivity</string>
|
||||||
|
<string name="action_settings">Settings</string>
|
||||||
|
|
||||||
|
<string name="action_show_qr">Show QR Code</string>
|
||||||
|
<string name="include_label">Include field label</string>
|
||||||
|
<string name="all_fields">All fields</string>
|
||||||
|
|
||||||
|
<string name="kp2aplugin_title">QR Plugin</string>
|
||||||
|
<string name="kp2aplugin_shortdesc">Displays password entries as QR code</string>
|
||||||
|
<string name="kp2aplugin_author">Philipp Crocoll</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</resources>
|
20
src/java/PluginQR/res/values/styles.xml
Normal file
20
src/java/PluginQR/res/values/styles.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<resources>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Base application theme, dependent on API level. This theme is replaced
|
||||||
|
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
|
||||||
|
-->
|
||||||
|
<style name="AppBaseTheme" parent="android:Theme.Light">
|
||||||
|
<!--
|
||||||
|
Theme customizations available in newer API levels can go in
|
||||||
|
res/values-vXX/styles.xml, while customizations related to
|
||||||
|
backward-compatibility can go here.
|
||||||
|
-->
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- Application theme. -->
|
||||||
|
<style name="AppTheme" parent="AppBaseTheme">
|
||||||
|
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
@ -0,0 +1,17 @@
|
|||||||
|
package keepass2android.plugin.qr;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import keepass2android.pluginsdk.PluginAccessBroadcastReceiver;
|
||||||
|
import keepass2android.pluginsdk.Strings;
|
||||||
|
|
||||||
|
public class AccessReceiver extends PluginAccessBroadcastReceiver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArrayList<String> getScopes() {
|
||||||
|
ArrayList<String> scopes = new ArrayList<String>();
|
||||||
|
scopes.add(Strings.SCOPE_CURRENT_ENTRY);
|
||||||
|
return scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package keepass2android.plugin.qr;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import keepass2android.pluginsdk.PluginAccessException;
|
||||||
|
import keepass2android.pluginsdk.PluginActionBroadcastReceiver;
|
||||||
|
import keepass2android.pluginsdk.Strings;
|
||||||
|
|
||||||
|
public class ActionReceiver extends PluginActionBroadcastReceiver{
|
||||||
|
@Override
|
||||||
|
protected void openEntry(OpenEntry oe) {
|
||||||
|
try {
|
||||||
|
oe.addEntryAction(oe.getContext().getString(R.string.action_show_qr),
|
||||||
|
R.drawable.qrcode, null);
|
||||||
|
|
||||||
|
for (String field: oe.getEntryFields().keySet())
|
||||||
|
{
|
||||||
|
oe.addEntryFieldAction("keepass2android.plugin.qr.show", Strings.PREFIX_STRING+field, oe.getContext().getString(R.string.action_show_qr),
|
||||||
|
R.drawable.qrcode, null);
|
||||||
|
}
|
||||||
|
} catch (PluginAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void actionSelected(ActionSelected actionSelected) {
|
||||||
|
Intent i = new Intent(actionSelected.getContext(), QRActivity.class);
|
||||||
|
i.putExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA, new JSONObject(actionSelected.getEntryFields()).toString());
|
||||||
|
i.putExtra(Strings.EXTRA_FIELD_ID, actionSelected.getFieldId());
|
||||||
|
i.putExtra(Strings.EXTRA_SENDER, actionSelected.getHostPackage());
|
||||||
|
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
actionSelected.getContext().startActivity(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void entryOutputModified(EntryOutputModified eom) {
|
||||||
|
try {
|
||||||
|
eom.addEntryFieldAction("keepass2android.plugin.qr.show", eom.getModifiedFieldId(), eom.getContext().getString(R.string.action_show_qr),
|
||||||
|
R.drawable.qrcode, null);
|
||||||
|
} catch (PluginAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
//
|
||||||
|
// * Copyright (C) 2008 ZXing authors
|
||||||
|
// *
|
||||||
|
// * Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// * you may not use this file except in compliance with the License.
|
||||||
|
// * You may obtain a copy of the License at
|
||||||
|
// *
|
||||||
|
// * http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// *
|
||||||
|
// * Unless required by applicable law or agreed to in writing, software
|
||||||
|
// * distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// * See the License for the specific language governing permissions and
|
||||||
|
// * limitations under the License.
|
||||||
|
//
|
||||||
|
package keepass2android.plugin.qr;
|
||||||
|
|
||||||
|
import android.provider.ContactsContract;
|
||||||
|
|
||||||
|
public final class Contents {
|
||||||
|
private Contents() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Type {
|
||||||
|
|
||||||
|
// Plain text. Use Intent.putExtra(DATA, string). This can be used for URLs too, but string
|
||||||
|
// must include "http://" or "https://".
|
||||||
|
public static final String TEXT = "TEXT_TYPE";
|
||||||
|
|
||||||
|
// An email type. Use Intent.putExtra(DATA, string) where string is the email address.
|
||||||
|
public static final String EMAIL = "EMAIL_TYPE";
|
||||||
|
|
||||||
|
// Use Intent.putExtra(DATA, string) where string is the phone number to call.
|
||||||
|
public static final String PHONE = "PHONE_TYPE";
|
||||||
|
|
||||||
|
// An SMS type. Use Intent.putExtra(DATA, string) where string is the number to SMS.
|
||||||
|
public static final String SMS = "SMS_TYPE";
|
||||||
|
|
||||||
|
public static final String CONTACT = "CONTACT_TYPE";
|
||||||
|
|
||||||
|
public static final String LOCATION = "LOCATION_TYPE";
|
||||||
|
|
||||||
|
private Type() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String URL_KEY = "URL_KEY";
|
||||||
|
|
||||||
|
public static final String NOTE_KEY = "NOTE_KEY";
|
||||||
|
|
||||||
|
// When using Type.CONTACT, these arrays provide the keys for adding or retrieving multiple phone numbers and addresses.
|
||||||
|
public static final String[] PHONE_KEYS = {
|
||||||
|
ContactsContract.Intents.Insert.PHONE, ContactsContract.Intents.Insert.SECONDARY_PHONE,
|
||||||
|
ContactsContract.Intents.Insert.TERTIARY_PHONE
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final String[] PHONE_TYPE_KEYS = {
|
||||||
|
ContactsContract.Intents.Insert.PHONE_TYPE,
|
||||||
|
ContactsContract.Intents.Insert.SECONDARY_PHONE_TYPE,
|
||||||
|
ContactsContract.Intents.Insert.TERTIARY_PHONE_TYPE
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final String[] EMAIL_KEYS = {
|
||||||
|
ContactsContract.Intents.Insert.EMAIL, ContactsContract.Intents.Insert.SECONDARY_EMAIL,
|
||||||
|
ContactsContract.Intents.Insert.TERTIARY_EMAIL
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final String[] EMAIL_TYPE_KEYS = {
|
||||||
|
ContactsContract.Intents.Insert.EMAIL_TYPE,
|
||||||
|
ContactsContract.Intents.Insert.SECONDARY_EMAIL_TYPE,
|
||||||
|
ContactsContract.Intents.Insert.TERTIARY_EMAIL_TYPE
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
455
src/java/PluginQR/src/keepass2android/plugin/qr/QRActivity.java
Normal file
455
src/java/PluginQR/src/keepass2android/plugin/qr/QRActivity.java
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
package keepass2android.plugin.qr;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import keepass2android.pluginsdk.AccessManager;
|
||||||
|
import keepass2android.pluginsdk.KeepassDefs;
|
||||||
|
import keepass2android.pluginsdk.Strings;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
import com.google.zxing.WriterException;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.AnimatorSet;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.ActionBar;
|
||||||
|
import android.app.Fragment;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.Display;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.animation.DecelerateInterpolator;
|
||||||
|
import android.widget.Adapter;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.AdapterView.OnItemSelectedListener;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.Spinner;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.preference.Preference;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
public class QRActivity extends Activity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
if ((getIntent() != null) && (getIntent().getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA)!= null))
|
||||||
|
Log.d("QR", getIntent().getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA));
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_qr);
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
getFragmentManager().beginTransaction()
|
||||||
|
.add(R.id.container, new PlaceholderFragment()).commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
|
||||||
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
|
getMenuInflater().inflate(R.menu.qr, menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A placeholder fragment containing a simple view.
|
||||||
|
*/
|
||||||
|
public static class PlaceholderFragment extends Fragment {
|
||||||
|
|
||||||
|
// Hold a reference to the current animator,
|
||||||
|
// so that it can be canceled mid-way.
|
||||||
|
private Animator mCurrentAnimator;
|
||||||
|
|
||||||
|
|
||||||
|
private int mShortAnimationDuration;
|
||||||
|
|
||||||
|
Bitmap mBitmap;
|
||||||
|
ImageView mImageView;
|
||||||
|
TextView mErrorView;
|
||||||
|
HashMap<String, String> mEntryOutput;
|
||||||
|
ArrayList<String> mFieldList = new ArrayList<String>();
|
||||||
|
Spinner mSpinner;
|
||||||
|
String mHostname;
|
||||||
|
|
||||||
|
private CheckBox mCbIncludeLabel;
|
||||||
|
|
||||||
|
|
||||||
|
private Resources kp2aRes;
|
||||||
|
|
||||||
|
public PlaceholderFragment() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HashMap<String, String> getEntryFieldsFromIntent(Intent intent)
|
||||||
|
{
|
||||||
|
HashMap<String, String> res = new HashMap<String, String>();
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject(intent.getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA));
|
||||||
|
for(Iterator<String> iter = json.keys();iter.hasNext();) {
|
||||||
|
String key = iter.next();
|
||||||
|
String value = json.get(key).toString();
|
||||||
|
res.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View rootView = inflater.inflate(R.layout.fragment_qr, container,
|
||||||
|
false);
|
||||||
|
|
||||||
|
mSpinner = (Spinner) rootView.findViewById(R.id.spinner);
|
||||||
|
|
||||||
|
mEntryOutput = getEntryFieldsFromIntent(getActivity().getIntent());
|
||||||
|
|
||||||
|
ArrayList<String> spinnerItems = new ArrayList<String>();
|
||||||
|
spinnerItems.add(getActivity().getString(R.string.all_fields));
|
||||||
|
mFieldList.add(null); //all fields
|
||||||
|
|
||||||
|
try {
|
||||||
|
mHostname = getActivity().getIntent().getStringExtra(Strings.EXTRA_SENDER);
|
||||||
|
kp2aRes = getActivity().getPackageManager().getResourcesForApplication(mHostname);
|
||||||
|
} catch (NameNotFoundException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
addIfExists(KeepassDefs.UserNameField, "entry_user_name", spinnerItems);
|
||||||
|
addIfExists(KeepassDefs.UrlField, "entry_url", spinnerItems);
|
||||||
|
addIfExists(KeepassDefs.PasswordField, "entry_password", spinnerItems);
|
||||||
|
addIfExists(KeepassDefs.TitleField, "entry_title", spinnerItems);
|
||||||
|
addIfExists(KeepassDefs.NotesField, "entry_comment", spinnerItems);
|
||||||
|
|
||||||
|
//add non-standard fields:
|
||||||
|
ArrayList<String> allKeys = new ArrayList<String>(mEntryOutput.keySet());
|
||||||
|
Collections.sort(allKeys);
|
||||||
|
|
||||||
|
for (String k: allKeys)
|
||||||
|
{
|
||||||
|
if (!KeepassDefs.IsStandardField(k))
|
||||||
|
{
|
||||||
|
if (!TextUtils.isEmpty(mEntryOutput.get(k)))
|
||||||
|
mFieldList.add(k);
|
||||||
|
spinnerItems.add(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mCbIncludeLabel = (CheckBox)rootView.findViewById(R.id.cbIncludeLabel);
|
||||||
|
|
||||||
|
boolean includeLabel = PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("includeLabels", false);
|
||||||
|
mCbIncludeLabel.setChecked(includeLabel);
|
||||||
|
mCbIncludeLabel.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
|
|
||||||
|
updateQrCode(buildQrData(mFieldList.get( mSpinner.getSelectedItemPosition() )));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_spinner_item, spinnerItems);
|
||||||
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
mSpinner.setAdapter(adapter);
|
||||||
|
|
||||||
|
mImageView = ((ImageView)rootView.findViewById(R.id.qrView));
|
||||||
|
mErrorView = ((TextView)rootView.findViewById(R.id.tvError));
|
||||||
|
String fieldId = null;
|
||||||
|
|
||||||
|
if (getActivity().getIntent() != null)
|
||||||
|
{
|
||||||
|
fieldId = getActivity().getIntent().getStringExtra(Strings.EXTRA_FIELD_ID);
|
||||||
|
if (fieldId != null)
|
||||||
|
{
|
||||||
|
fieldId = fieldId.substring(Strings.PREFIX_STRING.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateQrCode(buildQrData(fieldId));
|
||||||
|
|
||||||
|
mImageView.setOnClickListener(new OnClickListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
zoomImageFromThumb();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(AdapterView<?> arg0, View arg1,
|
||||||
|
int arg2, long arg3) {
|
||||||
|
if (arg2 != 0)
|
||||||
|
mCbIncludeLabel.setVisibility(View.VISIBLE);
|
||||||
|
else
|
||||||
|
mCbIncludeLabel.setVisibility(View.GONE);
|
||||||
|
updateQrCode(buildQrData(mFieldList.get(arg2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNothingSelected(AdapterView<?> arg0) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mSpinner.setSelection(mFieldList.indexOf(fieldId));
|
||||||
|
|
||||||
|
mShortAnimationDuration = getResources().getInteger(
|
||||||
|
android.R.integer.config_shortAnimTime);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return rootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addIfExists(String fieldKey, String resKey,
|
||||||
|
ArrayList<String> spinnerItems) {
|
||||||
|
if (!TextUtils.isEmpty(mEntryOutput.get(fieldKey)))
|
||||||
|
{
|
||||||
|
mFieldList.add(fieldKey);
|
||||||
|
String displayString = fieldKey;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
displayString = kp2aRes.getString(kp2aRes.getIdentifier(resKey, "string", mHostname));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
spinnerItems.add(displayString);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildQrData(String fieldId) {
|
||||||
|
String res = "";
|
||||||
|
|
||||||
|
if (fieldId == null)
|
||||||
|
{
|
||||||
|
res = "kp2a:\n";
|
||||||
|
for (String k:mFieldList)
|
||||||
|
{
|
||||||
|
if (k == null)
|
||||||
|
continue;
|
||||||
|
res += QRCodeEncoder.escapeMECARD(k)+":";
|
||||||
|
res += QRCodeEncoder.escapeMECARD(mEntryOutput.get(k))+";\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((mCbIncludeLabel.isChecked()))
|
||||||
|
{
|
||||||
|
res = fieldId+": ";
|
||||||
|
|
||||||
|
}
|
||||||
|
res += mEntryOutput.get(fieldId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateQrCode(String qrData) {
|
||||||
|
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||||
|
WindowManager wm = (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE); // the results will be higher than using the activity context object or the getWindowManager() shortcut
|
||||||
|
wm.getDefaultDisplay().getMetrics(displayMetrics);
|
||||||
|
int screenWidth = displayMetrics.widthPixels;
|
||||||
|
int screenHeight = displayMetrics.heightPixels;
|
||||||
|
|
||||||
|
int qrCodeDimension = screenWidth > screenHeight ? screenHeight : screenWidth;
|
||||||
|
QRCodeEncoder qrCodeEncoder = new QRCodeEncoder(qrData, null,
|
||||||
|
Contents.Type.TEXT, BarcodeFormat.QR_CODE.toString(), qrCodeDimension);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
mBitmap = qrCodeEncoder.encodeAsBitmap();
|
||||||
|
mImageView.setImageBitmap(mBitmap);
|
||||||
|
mImageView.setVisibility(View.VISIBLE);
|
||||||
|
mErrorView.setVisibility(View.GONE);
|
||||||
|
} catch (WriterException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
mErrorView.setText("Error: "+e.getMessage());
|
||||||
|
mErrorView.setVisibility(View.VISIBLE);
|
||||||
|
mImageView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void zoomImageFromThumb() {
|
||||||
|
// If there's an animation in progress, cancel it
|
||||||
|
// immediately and proceed with this one.
|
||||||
|
if (mCurrentAnimator != null) {
|
||||||
|
mCurrentAnimator.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the high-resolution "zoomed-in" image.
|
||||||
|
final ImageView expandedImageView = (ImageView) getActivity().findViewById(
|
||||||
|
R.id.expanded_image);
|
||||||
|
expandedImageView.setImageBitmap(mBitmap);
|
||||||
|
|
||||||
|
// Calculate the starting and ending bounds for the zoomed-in image.
|
||||||
|
// This step involves lots of math. Yay, math.
|
||||||
|
final Rect startBounds = new Rect();
|
||||||
|
final Rect finalBounds = new Rect();
|
||||||
|
final Point globalOffset = new Point();
|
||||||
|
|
||||||
|
// The start bounds are the global visible rectangle of the thumbnail,
|
||||||
|
// and the final bounds are the global visible rectangle of the container
|
||||||
|
// view. Also set the container view's offset as the origin for the
|
||||||
|
// bounds, since that's the origin for the positioning animation
|
||||||
|
// properties (X, Y).
|
||||||
|
mImageView.getGlobalVisibleRect(startBounds);
|
||||||
|
getActivity().findViewById(R.id.container)
|
||||||
|
.getGlobalVisibleRect(finalBounds, globalOffset);
|
||||||
|
startBounds.offset(-globalOffset.x, -globalOffset.y);
|
||||||
|
finalBounds.offset(-globalOffset.x, -globalOffset.y);
|
||||||
|
|
||||||
|
// Adjust the start bounds to be the same aspect ratio as the final
|
||||||
|
// bounds using the "center crop" technique. This prevents undesirable
|
||||||
|
// stretching during the animation. Also calculate the start scaling
|
||||||
|
// factor (the end scaling factor is always 1.0).
|
||||||
|
float startScale;
|
||||||
|
if ((float) finalBounds.width() / finalBounds.height()
|
||||||
|
> (float) startBounds.width() / startBounds.height()) {
|
||||||
|
// Extend start bounds horizontally
|
||||||
|
startScale = (float) startBounds.height() / finalBounds.height();
|
||||||
|
float startWidth = startScale * finalBounds.width();
|
||||||
|
float deltaWidth = (startWidth - startBounds.width()) / 2;
|
||||||
|
startBounds.left -= deltaWidth;
|
||||||
|
startBounds.right += deltaWidth;
|
||||||
|
} else {
|
||||||
|
// Extend start bounds vertically
|
||||||
|
startScale = (float) startBounds.width() / finalBounds.width();
|
||||||
|
float startHeight = startScale * finalBounds.height();
|
||||||
|
float deltaHeight = (startHeight - startBounds.height()) / 2;
|
||||||
|
startBounds.top -= deltaHeight;
|
||||||
|
startBounds.bottom += deltaHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the thumbnail and show the zoomed-in view. When the animation
|
||||||
|
// begins, it will position the zoomed-in view in the place of the
|
||||||
|
// thumbnail.
|
||||||
|
mImageView.setAlpha(0f);
|
||||||
|
expandedImageView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
// Set the pivot point for SCALE_X and SCALE_Y transformations
|
||||||
|
// to the top-left corner of the zoomed-in view (the default
|
||||||
|
// is the center of the view).
|
||||||
|
expandedImageView.setPivotX(0f);
|
||||||
|
expandedImageView.setPivotY(0f);
|
||||||
|
|
||||||
|
// Construct and run the parallel animation of the four translation and
|
||||||
|
// scale properties (X, Y, SCALE_X, and SCALE_Y).
|
||||||
|
AnimatorSet set = new AnimatorSet();
|
||||||
|
set
|
||||||
|
.play(ObjectAnimator.ofFloat(expandedImageView, View.X,
|
||||||
|
startBounds.left, finalBounds.left))
|
||||||
|
.with(ObjectAnimator.ofFloat(expandedImageView, View.Y,
|
||||||
|
startBounds.top, finalBounds.top))
|
||||||
|
.with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X,
|
||||||
|
startScale, 1f)).with(ObjectAnimator.ofFloat(expandedImageView,
|
||||||
|
View.SCALE_Y, startScale, 1f));
|
||||||
|
set.setDuration(mShortAnimationDuration);
|
||||||
|
set.setInterpolator(new DecelerateInterpolator());
|
||||||
|
set.addListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
mCurrentAnimator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
mCurrentAnimator = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
set.start();
|
||||||
|
mCurrentAnimator = set;
|
||||||
|
|
||||||
|
// Upon clicking the zoomed-in image, it should zoom back down
|
||||||
|
// to the original bounds and show the thumbnail instead of
|
||||||
|
// the expanded image.
|
||||||
|
final float startScaleFinal = startScale;
|
||||||
|
expandedImageView.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
if (mCurrentAnimator != null) {
|
||||||
|
mCurrentAnimator.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animate the four positioning/sizing properties in parallel,
|
||||||
|
// back to their original values.
|
||||||
|
AnimatorSet set = new AnimatorSet();
|
||||||
|
set.play(ObjectAnimator
|
||||||
|
.ofFloat(expandedImageView, View.X, startBounds.left))
|
||||||
|
.with(ObjectAnimator
|
||||||
|
.ofFloat(expandedImageView,
|
||||||
|
View.Y,startBounds.top))
|
||||||
|
.with(ObjectAnimator
|
||||||
|
.ofFloat(expandedImageView,
|
||||||
|
View.SCALE_X, startScaleFinal))
|
||||||
|
.with(ObjectAnimator
|
||||||
|
.ofFloat(expandedImageView,
|
||||||
|
View.SCALE_Y, startScaleFinal));
|
||||||
|
set.setDuration(mShortAnimationDuration);
|
||||||
|
set.setInterpolator(new DecelerateInterpolator());
|
||||||
|
set.addListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
mImageView.setAlpha(1f);
|
||||||
|
expandedImageView.setVisibility(View.GONE);
|
||||||
|
mCurrentAnimator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
mImageView.setAlpha(1f);
|
||||||
|
expandedImageView.setVisibility(View.GONE);
|
||||||
|
mCurrentAnimator = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
set.start();
|
||||||
|
mCurrentAnimator = set;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,252 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2008 ZXing authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package keepass2android.plugin.qr;
|
||||||
|
|
||||||
|
import android.provider.ContactsContract;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.telephony.PhoneNumberUtils;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
import com.google.zxing.EncodeHintType;
|
||||||
|
import com.google.zxing.MultiFormatWriter;
|
||||||
|
import com.google.zxing.WriterException;
|
||||||
|
import com.google.zxing.common.BitMatrix;
|
||||||
|
|
||||||
|
public final class QRCodeEncoder {
|
||||||
|
private static final int WHITE = 0xFFFFFFFF;
|
||||||
|
private static final int BLACK = 0xFF000000;
|
||||||
|
|
||||||
|
private int dimension = Integer.MIN_VALUE;
|
||||||
|
private String contents = null;
|
||||||
|
private String displayContents = null;
|
||||||
|
private String title = null;
|
||||||
|
private BarcodeFormat format = null;
|
||||||
|
private boolean encoded = false;
|
||||||
|
|
||||||
|
public QRCodeEncoder(String data, Bundle bundle, String type, String format, int dimension) {
|
||||||
|
this.dimension = dimension;
|
||||||
|
encoded = encodeContents(data, bundle, type, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContents() {
|
||||||
|
return contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayContents() {
|
||||||
|
return displayContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean encodeContents(String data, Bundle bundle, String type, String formatString) {
|
||||||
|
// Default to QR_CODE if no format given.
|
||||||
|
format = null;
|
||||||
|
if (formatString != null) {
|
||||||
|
try {
|
||||||
|
format = BarcodeFormat.valueOf(formatString);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
// Ignore it then
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (format == null || format == BarcodeFormat.QR_CODE) {
|
||||||
|
this.format = BarcodeFormat.QR_CODE;
|
||||||
|
encodeQRCodeContents(data, bundle, type);
|
||||||
|
} else if (data != null && data.length() > 0) {
|
||||||
|
contents = data;
|
||||||
|
displayContents = data;
|
||||||
|
title = "Text";
|
||||||
|
}
|
||||||
|
return contents != null && contents.length() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encodeQRCodeContents(String data, Bundle bundle, String type) {
|
||||||
|
if (type.equals(Contents.Type.TEXT)) {
|
||||||
|
if (data != null && data.length() > 0) {
|
||||||
|
contents = data;
|
||||||
|
displayContents = data;
|
||||||
|
title = "Text";
|
||||||
|
}
|
||||||
|
} else if (type.equals(Contents.Type.EMAIL)) {
|
||||||
|
data = trim(data);
|
||||||
|
if (data != null) {
|
||||||
|
contents = "mailto:" + data;
|
||||||
|
displayContents = data;
|
||||||
|
title = "E-Mail";
|
||||||
|
}
|
||||||
|
} else if (type.equals(Contents.Type.PHONE)) {
|
||||||
|
data = trim(data);
|
||||||
|
if (data != null) {
|
||||||
|
contents = "tel:" + data;
|
||||||
|
displayContents = PhoneNumberUtils.formatNumber(data);
|
||||||
|
title = "Phone";
|
||||||
|
}
|
||||||
|
} else if (type.equals(Contents.Type.SMS)) {
|
||||||
|
data = trim(data);
|
||||||
|
if (data != null) {
|
||||||
|
contents = "sms:" + data;
|
||||||
|
displayContents = PhoneNumberUtils.formatNumber(data);
|
||||||
|
title = "SMS";
|
||||||
|
}
|
||||||
|
} else if (type.equals(Contents.Type.CONTACT)) {
|
||||||
|
if (bundle != null) {
|
||||||
|
StringBuilder newContents = new StringBuilder(100);
|
||||||
|
StringBuilder newDisplayContents = new StringBuilder(100);
|
||||||
|
|
||||||
|
newContents.append("MECARD:");
|
||||||
|
|
||||||
|
String name = trim(bundle.getString(ContactsContract.Intents.Insert.NAME));
|
||||||
|
if (name != null) {
|
||||||
|
newContents.append("N:").append(escapeMECARD(name)).append(';');
|
||||||
|
newDisplayContents.append(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
String address = trim(bundle.getString(ContactsContract.Intents.Insert.POSTAL));
|
||||||
|
if (address != null) {
|
||||||
|
newContents.append("ADR:").append(escapeMECARD(address)).append(';');
|
||||||
|
newDisplayContents.append('\n').append(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<String> uniquePhones = new HashSet<String>(Contents.PHONE_KEYS.length);
|
||||||
|
for (int x = 0; x < Contents.PHONE_KEYS.length; x++) {
|
||||||
|
String phone = trim(bundle.getString(Contents.PHONE_KEYS[x]));
|
||||||
|
if (phone != null) {
|
||||||
|
uniquePhones.add(phone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String phone : uniquePhones) {
|
||||||
|
newContents.append("TEL:").append(escapeMECARD(phone)).append(';');
|
||||||
|
newDisplayContents.append('\n').append(PhoneNumberUtils.formatNumber(phone));
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<String> uniqueEmails = new HashSet<String>(Contents.EMAIL_KEYS.length);
|
||||||
|
for (int x = 0; x < Contents.EMAIL_KEYS.length; x++) {
|
||||||
|
String email = trim(bundle.getString(Contents.EMAIL_KEYS[x]));
|
||||||
|
if (email != null) {
|
||||||
|
uniqueEmails.add(email);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String email : uniqueEmails) {
|
||||||
|
newContents.append("EMAIL:").append(escapeMECARD(email)).append(';');
|
||||||
|
newDisplayContents.append('\n').append(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = trim(bundle.getString(Contents.URL_KEY));
|
||||||
|
if (url != null) {
|
||||||
|
// escapeMECARD(url) -> wrong escape e.g. http\://zxing.google.com
|
||||||
|
newContents.append("URL:").append(url).append(';');
|
||||||
|
newDisplayContents.append('\n').append(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
String note = trim(bundle.getString(Contents.NOTE_KEY));
|
||||||
|
if (note != null) {
|
||||||
|
newContents.append("NOTE:").append(escapeMECARD(note)).append(';');
|
||||||
|
newDisplayContents.append('\n').append(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we've encoded at least one field.
|
||||||
|
if (newDisplayContents.length() > 0) {
|
||||||
|
newContents.append(';');
|
||||||
|
contents = newContents.toString();
|
||||||
|
displayContents = newDisplayContents.toString();
|
||||||
|
title = "Contact";
|
||||||
|
} else {
|
||||||
|
contents = null;
|
||||||
|
displayContents = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if (type.equals(Contents.Type.LOCATION)) {
|
||||||
|
if (bundle != null) {
|
||||||
|
// These must use Bundle.getFloat(), not getDouble(), it's part of the API.
|
||||||
|
float latitude = bundle.getFloat("LAT", Float.MAX_VALUE);
|
||||||
|
float longitude = bundle.getFloat("LONG", Float.MAX_VALUE);
|
||||||
|
if (latitude != Float.MAX_VALUE && longitude != Float.MAX_VALUE) {
|
||||||
|
contents = "geo:" + latitude + ',' + longitude;
|
||||||
|
displayContents = latitude + "," + longitude;
|
||||||
|
title = "Location";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap encodeAsBitmap() throws WriterException {
|
||||||
|
if (!encoded) return null;
|
||||||
|
|
||||||
|
Map<EncodeHintType, Object> hints = null;
|
||||||
|
String encoding = guessAppropriateEncoding(contents);
|
||||||
|
hints = new EnumMap<EncodeHintType, Object>(EncodeHintType.class);
|
||||||
|
if (encoding != null) {
|
||||||
|
|
||||||
|
hints.put(EncodeHintType.CHARACTER_SET, encoding);
|
||||||
|
}
|
||||||
|
hints.put(EncodeHintType.MARGIN, 2); /* default = 4 */
|
||||||
|
|
||||||
|
|
||||||
|
MultiFormatWriter writer = new MultiFormatWriter();
|
||||||
|
BitMatrix result = writer.encode(contents, format, dimension, dimension, hints);
|
||||||
|
int width = result.getWidth();
|
||||||
|
int height = result.getHeight();
|
||||||
|
int[] pixels = new int[width * height];
|
||||||
|
// All are 0, or black, by default
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
int offset = y * width;
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
pixels[offset + x] = result.get(x, y) ? BLACK : WHITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||||
|
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String guessAppropriateEncoding(CharSequence contents) {
|
||||||
|
// Very crude at the moment
|
||||||
|
for (int i = 0; i < contents.length(); i++) {
|
||||||
|
if (contents.charAt(i) > 0xFF) { return "UTF-8"; }
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String trim(String s) {
|
||||||
|
if (s == null) { return null; }
|
||||||
|
String result = s.trim();
|
||||||
|
return result.length() == 0 ? null : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String escapeMECARD(String input) {
|
||||||
|
if (input == null || (input.indexOf(':') < 0 && input.indexOf(';') < 0)) { return input; }
|
||||||
|
int length = input.length();
|
||||||
|
StringBuilder result = new StringBuilder(length);
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
char c = input.charAt(i);
|
||||||
|
if (c == ':' || c == ';') {
|
||||||
|
result.append('\\');
|
||||||
|
}
|
||||||
|
result.append(c);
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user