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:
		| @@ -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) | ||||||
| @@ -89,6 +95,12 @@ public class Strings { | |||||||
|  |  | ||||||
| 	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"; | ||||||
| 	 | 	 | ||||||
| @@ -111,4 +123,5 @@ public class Strings { | |||||||
| 	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(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
		Reference in New Issue
	
	Block a user
	 Philipp Crocoll
					Philipp Crocoll