Integrated new EntryActivity to KP2A

This commit is contained in:
Philipp Crocoll 2014-05-11 07:52:38 +02:00
parent 685c6c6453
commit 9008785c69
14 changed files with 3767 additions and 5154 deletions

View File

@ -36,52 +36,69 @@ using Android.Graphics;
using Java.IO; using Java.IO;
using KeePassLib; using KeePassLib;
using KeePassLib.Security; using KeePassLib.Security;
using KeePassLib.Utility;
using Keepass2android.Pluginsdk; using Keepass2android.Pluginsdk;
using PluginHostTest; using PluginHostTest;
using keepass2android.Io;
using Uri = Android.Net.Uri; using Uri = Android.Net.Uri;
namespace keepass2android namespace keepass2android
{ {
[Activity(Label = "@string/app_name", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.KeyboardHidden, [Activity (Label = "@string/app_name", ConfigurationChanges=ConfigChanges.Orientation|ConfigChanges.KeyboardHidden, Theme="@style/NoTitleBar")]
Theme = "@style/NoTitleBar")] public class EntryActivity : LockCloseActivity
public class EntryActivity : Activity
{ {
public const String KeyEntry = "entry"; public const String KeyEntry = "entry";
public const String KeyRefreshPos = "refresh_pos"; public const String KeyRefreshPos = "refresh_pos";
public const String KeyCloseAfterCreate = "close_after_create"; public const String KeyCloseAfterCreate = "close_after_create";
protected PwEntry Entry = new PwEntry(true, true); public static void Launch(Activity act, PwEntry pw, int pos, AppTask appTask)
{
Intent i = new Intent(act, typeof(EntryActivity));
i.PutExtra(KeyEntry, pw.Uuid.ToHexString());
i.PutExtra(KeyRefreshPos, pos);
appTask.ToIntent(i);
act.StartActivityForResult(i, 0);
}
protected PwEntry Entry;
private static Typeface _passwordFont; private static Typeface _passwordFont;
internal bool _showPassword; internal bool _showPassword;
private int _pos; private int _pos;
AppTask _appTask;
private List<TextView> _protectedTextViews; private List<TextView> _protectedTextViews;
private IMenu _menu;
private readonly Dictionary<string, List<IPopupMenuItem>> _popupMenuItems = private readonly Dictionary<string, List<IPopupMenuItem>> _popupMenuItems =
new Dictionary<string, List<IPopupMenuItem>>(); new Dictionary<string, List<IPopupMenuItem>>();
private readonly Dictionary<string, IStringView> _stringViews = new Dictionary<string, IStringView>(); private readonly Dictionary<string, IStringView> _stringViews = new Dictionary<string, IStringView>();
private readonly List<PluginMenuOption> _pendingMenuOptions = new List<PluginMenuOption>(); private readonly List<PluginMenuOption> _pendingMenuOptions = new List<PluginMenuOption>();
private IMenu _menu;
protected void SetEntryView() protected void SetEntryView()
{ {
SetContentView(Resource.Layout.entry_view); SetContentView(Resource.Layout.entry_view);
} }
protected void SetupEditButtons() protected void SetupEditButtons() {
{ View edit = FindViewById(Resource.Id.entry_edit);
View edit = FindViewById(Resource.Id.entry_edit); if (App.Kp2a.GetDb().CanWrite)
if (true)
{ {
edit.Visibility = ViewStates.Visible; edit.Visibility = ViewStates.Visible;
edit.Click += (sender, e) => edit.Click += (sender, e) =>
{ {
EntryEditActivity.Launch(this, Entry, _appTask);
}; };
} }
else else
{ {
@ -171,7 +188,7 @@ namespace keepass2android
} }
//update the Entry output in the App database and notify the CopyToClipboard service //update the Entry output in the App database and notify the CopyToClipboard service
App.Kp2A.GetDb().LastOpenedEntry.OutputStrings.Set(key, new ProtectedString(isProtected, value)); App.Kp2a.GetDb().LastOpenedEntry.OutputStrings.Set(key, new ProtectedString(isProtected, value));
Intent updateKeyboardIntent = new Intent(this, typeof(CopyToClipboardService)); Intent updateKeyboardIntent = new Intent(this, typeof(CopyToClipboardService));
Intent.SetAction(Intents.UpdateKeyboard); Intent.SetAction(Intents.UpdateKeyboard);
updateKeyboardIntent.PutExtra(KeyEntry, Entry.Uuid.ToHexString()); updateKeyboardIntent.PutExtra(KeyEntry, Entry.Uuid.ToHexString());
@ -221,7 +238,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); PluginHost.AddEntryToIntent(i, App.Kp2a.GetDb().LastOpenedEntry);
var menuOption = new PluginMenuOption() var menuOption = new PluginMenuOption()
{ {
@ -254,167 +271,7 @@ namespace keepass2android
menuItem.SetIntent(menuOption.Intent); menuItem.SetIntent(menuOption.Intent);
} }
public override bool OnCreateOptionsMenu(IMenu menu)
{
_menu = menu;
base.OnCreateOptionsMenu(menu);
MenuInflater inflater = MenuInflater;
inflater.Inflate(Resource.Menu.entry, menu);
lock (_pendingMenuOptions)
{
foreach (var option in _pendingMenuOptions)
AddMenuOption(option);
_pendingMenuOptions.Clear();
}
UpdateTogglePasswordMenu();
IMenuItem gotoUrl = menu.FindItem(Resource.Id.menu_goto_url);
//Disabled IMenuItem copyUser = menu.FindItem(Resource.Id.menu_copy_user);
//Disabled IMenuItem copyPass = menu.FindItem(Resource.Id.menu_copy_pass);
// In API >= 11 onCreateOptionsMenu may be called before onCreate completes
// so _entry may not be set
if (Entry == null)
{
gotoUrl.SetVisible(false);
//Disabled copyUser.SetVisible(false);
//Disabled copyPass.SetVisible(false);
}
else
{
String url = Entry.Strings.ReadSafe(PwDefs.UrlField);
if (String.IsNullOrEmpty(url))
{
// disable button if url is not available
gotoUrl.SetVisible(false);
}
if (String.IsNullOrEmpty(Entry.Strings.ReadSafe(PwDefs.UserNameField)))
{
// disable button if username is not available
//Disabled copyUser.SetVisible(false);
}
if (String.IsNullOrEmpty(Entry.Strings.ReadSafe(PwDefs.PasswordField)))
{
// disable button if password is not available
//Disabled copyPass.SetVisible(false);
}
}
return true;
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
//check if this is a plugin action
if ((item.Intent != null) && (item.Intent.Action == Strings.ActionEntryActionSelected))
{
//yes. let the plugin handle the click:
SendBroadcast(item.Intent);
return true;
}
switch (item.ItemId)
{
case Resource.Id.menu_donate:
try
{
// Util.GotoDonateUrl(this);
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.error_failed_to_launch_link, ToastLength.Long).Show();
return false;
}
return true;
case Resource.Id.menu_toggle_pass:
if (_showPassword)
{
item.SetTitle(Resource.String.show_password);
_showPassword = false;
}
else
{
item.SetTitle(Resource.String.menu_hide_password);
_showPassword = true;
}
SetPasswordStyle();
return true;
case Resource.Id.menu_goto_url:
string url = _stringViews[PwDefs.UrlField].Text;
if (url == null) return false;
// Default http:// if no protocol specified
if (!url.Contains("://"))
{
url = "http://" + url;
}
try
{
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
}
return true;
/* TODO: required?
case Resource.Id.menu_copy_user:
timeoutCopyToClipboard(_entry.Strings.ReadSafe (PwDefs.UserNameField));
return true;
case Resource.Id.menu_copy_pass:
timeoutCopyToClipboard(_entry.Strings.ReadSafe (PwDefs.UserNameField));
return true;
*/
case Resource.Id.menu_rate:
try
{
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
}
return true;
case Resource.Id.menu_suggest_improvements:
try
{
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
}
return true;
case Resource.Id.menu_lock:
return true;
case Resource.Id.menu_translate:
try
{
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
}
return true;
case Android.Resource.Id.Home:
//Currently the action bar only displays the home button when we come from a previous activity.
//So we can simply Finish. See this page for information on how to do this in more general (future?) cases:
//http://developer.android.com/training/implementing-navigation/ancestral.html
Finish();
return true;
}
return base.OnOptionsItemSelected(item);
}
protected override void OnCreate(Bundle savedInstanceState) protected override void OnCreate(Bundle savedInstanceState)
@ -431,20 +288,55 @@ namespace keepass2android
_showPassword = _showPassword =
!prefs.GetBoolean(GetString(Resource.String.maskpass_key), Resources.GetBoolean(Resource.Boolean.maskpass_default)); !prefs.GetBoolean(GetString(Resource.String.maskpass_key), Resources.GetBoolean(Resource.Boolean.maskpass_default));
Entry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, "philipp "));
Entry.Strings.Set(PwDefs.PasswordField, new ProtectedString(true, "password value"));
Entry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, "https://www.google.com"));
Entry.Strings.Set("field header", new ProtectedString(true, "protected field value"));
Entry.Strings.Set("public field header", new ProtectedString(false, "public field value"));
base.OnCreate(savedInstanceState); base.OnCreate(savedInstanceState);
new ActivityDesign(this).ApplyTheme();
SetEntryView(); SetEntryView();
Database db = App.Kp2a.GetDb();
// Likely the app has been killed exit the activity
if (!db.Loaded || (App.Kp2a.QuickLocked))
{
Finish();
return;
}
SetResult(KeePass.ExitNormal);
Intent i = Intent;
PwUuid uuid = new PwUuid(MemUtil.HexStringToByteArray(i.GetStringExtra(KeyEntry)));
_pos = i.GetIntExtra(KeyRefreshPos, -1);
_appTask = AppTask.GetTaskInOnCreate(savedInstanceState, Intent);
Entry = db.Entries[uuid];
// Refresh Menu contents in case onCreateMenuOptions was called before Entry was set
ActivityCompat.InvalidateOptionsMenu(this);
// Update last access time.
Entry.Touch(false);
if (PwDefs.IsTanEntry(Entry) && prefs.GetBoolean(GetString(Resource.String.TanExpiresOnUse_key), Resources.GetBoolean(Resource.Boolean.TanExpiresOnUse_default)) && ((Entry.Expires == false) || Entry.ExpiryTime > DateTime.Now))
{
PwEntry backupEntry = Entry.CloneDeep();
Entry.ExpiryTime = DateTime.Now;
Entry.Expires = true;
Entry.Touch(true);
RequiresRefresh();
UpdateEntry update = new UpdateEntry(this, App.Kp2a, backupEntry, Entry, null);
ProgressTask pt = new ProgressTask(App.Kp2a, this, update);
pt.Run();
}
FillData(); FillData();
SetupEditButtons(); SetupEditButtons();
App.Kp2A.GetDb().LastOpenedEntry = new PwEntryOutput(Entry, App.Kp2A.GetDb().KpDatabase); //depending on the app task, the final things to do might be delayed, so let the appTask call CompleteOnCreate when appropriate
_appTask.OnCompleteCreateEntryActivity(this);
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));
@ -454,8 +346,6 @@ namespace keepass2android
private void NotifyPluginsOnOpen() private void NotifyPluginsOnOpen()
{ {
App.Kp2A.GetDb().SetEntry(Entry);
Intent i = new Intent(Strings.ActionOpenEntry); Intent i = new Intent(Strings.ActionOpenEntry);
i.PutExtra(Strings.ExtraSender, PackageName); i.PutExtra(Strings.ExtraSender, PackageName);
AddEntryToIntent(i); AddEntryToIntent(i);
@ -485,6 +375,26 @@ namespace keepass2android
public void CompleteOnCreate() public void CompleteOnCreate()
{ {
Intent showNotIntent = new Intent(this, typeof(CopyToClipboardService));
Intent.SetAction(Intents.ShowNotification);
showNotIntent.PutExtra(KeyEntry, Entry.Uuid.ToHexString());
bool closeAfterCreate = _appTask.CloseEntryActivityAfterCreate;
showNotIntent.PutExtra(KeyCloseAfterCreate, closeAfterCreate);
StartService(showNotIntent);
Kp2aLog.Log("Requesting copy to clipboard for Uuid=" + Entry.Uuid.ToHexString());
/*foreach (PwUuid key in App.Kp2a.GetDb().entries.Keys)
{
Kp2aLog.Log(this,key.ToHexString() + " -> " + App.Kp2a.GetDb().entries[key].Uuid.ToHexString());
}*/
if (closeAfterCreate)
{
SetResult(KeePass.ExitCloseAfterTaskComplete);
Finish();
}
} }
@ -552,6 +462,8 @@ namespace keepass2android
} }
private List<IPopupMenuItem> RegisterPopup(string popupKey, View clickView, View anchorView) private List<IPopupMenuItem> RegisterPopup(string popupKey, View clickView, View anchorView)
{ {
clickView.Click += (sender, args) => clickView.Click += (sender, args) =>
@ -561,6 +473,97 @@ namespace keepass2android
_popupMenuItems[popupKey] = new List<IPopupMenuItem>(); _popupMenuItems[popupKey] = new List<IPopupMenuItem>();
return _popupMenuItems[popupKey]; return _popupMenuItems[popupKey];
} }
internal Android.Net.Uri WriteBinaryToFile(string key, bool writeToCacheDirectory)
{
ProtectedBinary pb = Entry.Binaries.Get(key);
System.Diagnostics.Debug.Assert(pb != null);
if (pb == null)
throw new ArgumentException();
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this);
string binaryDirectory = prefs.GetString(GetString(Resource.String.BinaryDirectory_key), GetString(Resource.String.BinaryDirectory_default));
if (writeToCacheDirectory)
binaryDirectory = CacheDir.Path + File.Separator + AttachmentContentProvider.AttachmentCacheSubDir;
string filepart = key;
if (writeToCacheDirectory)
filepart = filepart.Replace(" ", "");
var targetFile = new File(binaryDirectory, filepart);
File parent = targetFile.ParentFile;
if (parent == null || (parent.Exists() && !parent.IsDirectory))
{
Toast.MakeText(this,
Resource.String.error_invalid_path,
ToastLength.Long).Show();
return null;
}
if (!parent.Exists())
{
// Create parent directory
if (!parent.Mkdirs())
{
Toast.MakeText(this,
Resource.String.error_could_not_create_parent,
ToastLength.Long).Show();
return null;
}
}
string filename = targetFile.AbsolutePath;
Android.Net.Uri fileUri = Android.Net.Uri.FromFile(targetFile);
byte[] pbData = pb.ReadData();
try
{
System.IO.File.WriteAllBytes(filename, pbData);
}
catch (Exception exWrite)
{
Toast.MakeText(this, GetString(Resource.String.SaveAttachment_Failed, new Java.Lang.Object[] { filename })
+ exWrite.Message, ToastLength.Long).Show();
return null;
}
finally
{
MemUtil.ZeroByteArray(pbData);
}
Toast.MakeText(this, GetString(Resource.String.SaveAttachment_doneMessage, new Java.Lang.Object[] { filename }), ToastLength.Short).Show();
if (writeToCacheDirectory)
{
return Android.Net.Uri.Parse("content://" + AttachmentContentProvider.Authority + "/"
+ filename);
}
return fileUri;
}
internal void OpenBinaryFile(Android.Net.Uri uri)
{
String theMimeType = GetMimeType(uri.Path);
if (theMimeType != null)
{
Intent theIntent = new Intent(Intent.ActionView);
theIntent.AddFlags(ActivityFlags.NewTask | ActivityFlags.ExcludeFromRecents);
theIntent.SetDataAndType(uri, theMimeType);
try
{
StartActivity(theIntent);
}
catch (ActivityNotFoundException)
{
//ignore
Toast.MakeText(this, "Couldn't open file", ToastLength.Short).Show();
}
}
}
private void RegisterProtectedTextView(TextView protectedTextView) private void RegisterProtectedTextView(TextView protectedTextView)
@ -572,7 +575,7 @@ namespace keepass2android
private void PopulateBinaries() private void PopulateBinaries()
{ {
ViewGroup binariesGroup = (ViewGroup) FindViewById(Resource.Id.binaries); ViewGroup binariesGroup = (ViewGroup) FindViewById(Resource.Id.binaries);
foreach (KeyValuePair<string, string> pair in new Dictionary<string, string>() {{"abc", ""}, {"test.png", "uia"}}) foreach (KeyValuePair<string, ProtectedBinary> pair in Entry.Binaries)
{ {
String key = pair.Key; String key = pair.Key;
@ -645,6 +648,7 @@ namespace keepass2android
public override void OnBackPressed() public override void OnBackPressed()
{ {
base.OnBackPressed(); base.OnBackPressed();
OverridePendingTransition(Resource.Animation.anim_enter_back, Resource.Animation.anim_leave_back);
} }
protected void FillData() protected void FillData()
@ -658,11 +662,9 @@ namespace keepass2android
ActionBar.Title = "Entry title"; ActionBar.Title = Entry.Strings.ReadSafe(PwDefs.TitleField);
ActionBar.SetDisplayHomeAsUpEnabled(true); ActionBar.SetDisplayHomeAsUpEnabled(true);
PopulateStandardText(Resource.Id.entry_user_name, Resource.Id.entryfield_container_username, PwDefs.UserNameField); PopulateStandardText(Resource.Id.entry_user_name, Resource.Id.entryfield_container_username, PwDefs.UserNameField);
PopulateStandardText(Resource.Id.entry_url, Resource.Id.entryfield_container_url, PwDefs.UrlField); PopulateStandardText(Resource.Id.entry_url, Resource.Id.entryfield_container_url, PwDefs.UrlField);
PopulateStandardText(Resource.Id.entry_password, Resource.Id.entryfield_container_password, PwDefs.PasswordField); PopulateStandardText(Resource.Id.entry_password, Resource.Id.entryfield_container_password, PwDefs.PasswordField);
@ -701,18 +703,7 @@ namespace keepass2android
SetPasswordStyle(); SetPasswordStyle();
} }
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (resultCode == /*TODO*/ 0)
{
if (resultCode == /*TODO*/ 0)
{
RequiresRefresh();
}
Recreate();
}
}
protected override void OnDestroy() protected override void OnDestroy()
{ {
@ -778,7 +769,9 @@ namespace keepass2android
private void SetPasswordTypeface(TextView textView) private void SetPasswordTypeface(TextView textView)
{ {
if (_passwordFont == null)
_passwordFont = Typeface.CreateFromAsset(Assets, "DejaVuSansMono.ttf");
textView.Typeface = _passwordFont;
} }
private void PopulateText(int viewId, int containerViewId, int resId) private void PopulateText(int viewId, int containerViewId, int resId)
@ -817,9 +810,83 @@ namespace keepass2android
Intent ret = new Intent(); Intent ret = new Intent();
ret.PutExtra(KeyRefreshPos, _pos); ret.PutExtra(KeyRefreshPos, _pos);
SetResult(KeePass.ExitRefresh, ret);
} }
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) {
base.OnActivityResult(requestCode, resultCode, data);
if ( resultCode == KeePass.ExitRefresh || resultCode == KeePass.ExitRefreshTitle ) {
if ( resultCode == KeePass.ExitRefreshTitle ) {
RequiresRefresh ();
}
Recreate();
}
}
public override bool OnCreateOptionsMenu(IMenu menu)
{
_menu = menu;
base.OnCreateOptionsMenu(menu);
MenuInflater inflater = MenuInflater;
inflater.Inflate(Resource.Menu.entry, menu);
lock (_pendingMenuOptions)
{
foreach (var option in _pendingMenuOptions)
AddMenuOption(option);
_pendingMenuOptions.Clear();
}
UpdateTogglePasswordMenu();
IMenuItem gotoUrl = menu.FindItem(Resource.Id.menu_goto_url);
//Disabled IMenuItem copyUser = menu.FindItem(Resource.Id.menu_copy_user);
//Disabled IMenuItem copyPass = menu.FindItem(Resource.Id.menu_copy_pass);
// In API >= 11 onCreateOptionsMenu may be called before onCreate completes
// so _entry may not be set
if (Entry == null)
{
gotoUrl.SetVisible(false);
//Disabled copyUser.SetVisible(false);
//Disabled copyPass.SetVisible(false);
}
else
{
String url = Entry.Strings.ReadSafe(PwDefs.UrlField);
if (String.IsNullOrEmpty(url))
{
// disable button if url is not available
gotoUrl.SetVisible(false);
}
if (String.IsNullOrEmpty(Entry.Strings.ReadSafe(PwDefs.UserNameField)))
{
// disable button if username is not available
//Disabled copyUser.SetVisible(false);
}
if (String.IsNullOrEmpty(Entry.Strings.ReadSafe(PwDefs.PasswordField)))
{
// disable button if password is not available
//Disabled copyPass.SetVisible(false);
}
}
return true;
}
private void UpdateTogglePasswordMenu()
{
IMenuItem togglePassword = _menu.FindItem(Resource.Id.menu_toggle_pass);
if (_showPassword)
{
togglePassword.SetTitle(Resource.String.menu_hide_password);
}
else
{
togglePassword.SetTitle(Resource.String.show_password);
}
}
private void SetPasswordStyle() private void SetPasswordStyle()
{ {
@ -839,10 +906,123 @@ namespace keepass2android
protected override void OnResume() protected override void OnResume()
{ {
ClearCache();
base.OnResume(); base.OnResume();
} }
public void ClearCache()
{
try
{
File dir = new File(CacheDir.Path + File.Separator + AttachmentContentProvider.AttachmentCacheSubDir);
if (dir.IsDirectory)
{
IoUtil.DeleteDir(dir);
}
}
catch (Exception)
{
}
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
//check if this is a plugin action
if ((item.Intent != null) && (item.Intent.Action == Strings.ActionEntryActionSelected))
{
//yes. let the plugin handle the click:
SendBroadcast(item.Intent);
return true;
}
switch (item.ItemId)
{
case Resource.Id.menu_donate:
try
{
Util.GotoDonateUrl(this);
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.error_failed_to_launch_link, ToastLength.Long).Show();
return false;
}
return true;
case Resource.Id.menu_toggle_pass:
if (_showPassword)
{
item.SetTitle(Resource.String.show_password);
_showPassword = false;
}
else
{
item.SetTitle(Resource.String.menu_hide_password);
_showPassword = true;
}
SetPasswordStyle();
return true;
case Resource.Id.menu_goto_url:
return GotoUrl();
/* TODO: required?
case Resource.Id.menu_copy_user:
timeoutCopyToClipboard(_entry.Strings.ReadSafe (PwDefs.UserNameField));
return true;
case Resource.Id.menu_copy_pass:
timeoutCopyToClipboard(_entry.Strings.ReadSafe (PwDefs.UserNameField));
return true;
*/
case Resource.Id.menu_rate:
try
{
Util.GotoMarket(this);
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
}
return true;
case Resource.Id.menu_suggest_improvements:
try
{
Util.GotoUrl(this, Resource.String.SuggestionsURL);
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
}
return true;
case Resource.Id.menu_lock:
return true;
case Resource.Id.menu_translate:
try
{
Util.GotoUrl(this, Resource.String.TranslationURL);
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
}
return true;
case Android.Resource.Id.Home:
//Currently the action bar only displays the home button when we come from a previous activity.
//So we can simply Finish. See this page for information on how to do this in more general (future?) cases:
//http://developer.android.com/training/implementing-navigation/ancestral.html
Finish();
OverridePendingTransition(Resource.Animation.anim_enter_back, Resource.Animation.anim_leave_back);
return true;
}
return base.OnOptionsItemSelected(item);
}
/// <summary> /// <summary>
/// brings up a dialog asking the user whether he wants to add the given URL to the entry for automatic finding /// brings up a dialog asking the user whether he wants to add the given URL to the entry for automatic finding
/// </summary> /// </summary>
@ -856,6 +1036,8 @@ namespace keepass2android
builder.SetPositiveButton(GetString(Resource.String.yes), (dlgSender, dlgEvt) => builder.SetPositiveButton(GetString(Resource.String.yes), (dlgSender, dlgEvt) =>
{ {
AddUrlToEntryThenCompleteCreate(url);
}); });
builder.SetNegativeButton(GetString(Resource.String.no), (dlgSender, dlgEvt) => builder.SetNegativeButton(GetString(Resource.String.no), (dlgSender, dlgEvt) =>
@ -867,7 +1049,47 @@ namespace keepass2android
dialog.Show(); dialog.Show();
} }
private void AddUrlToEntryThenCompleteCreate(string url)
{
PwEntry initialEntry = Entry.CloneDeep();
PwEntry newEntry = Entry;
newEntry.History = newEntry.History.CloneDeep();
newEntry.CreateBackup(null);
newEntry.Touch(true, false); // Touch *after* backup
//if there is no URL in the entry, set that field. If it's already in use, use an additional (not existing) field
if (String.IsNullOrEmpty(newEntry.Strings.ReadSafe(PwDefs.UrlField)))
{
newEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(false, url));
}
else
{
int c = 1;
while (newEntry.Strings.Get("KP2A_URL_" + c) != null)
{
c++;
}
newEntry.Strings.Set("KP2A_URL_" + c, new ProtectedString(false, url));
}
//save the entry:
ActionOnFinish closeOrShowError = new ActionOnFinish((success, message) =>
{
OnFinish.DisplayMessage(this, message);
CompleteOnCreate();
});
RunnableOnFinish runnable = new UpdateEntry(this, App.Kp2a, initialEntry, newEntry, closeOrShowError);
ProgressTask pt = new ProgressTask(App.Kp2a, this, runnable);
pt.Run();
}
public void ToggleVisibility() public void ToggleVisibility()
{ {
_showPassword = !_showPassword; _showPassword = !_showPassword;
@ -875,31 +1097,32 @@ namespace keepass2android
UpdateTogglePasswordMenu(); UpdateTogglePasswordMenu();
} }
public Android.Net.Uri WriteBinaryToFile(string key, bool writeToCacheDirectory)
{
return Android.Net.Uri.Empty;
//TODO
}
private void UpdateTogglePasswordMenu() public bool GotoUrl()
{ {
//todo use real method string url = _stringViews[PwDefs.UrlField].Text;
} if (url == null) return false;
public void GotoUrl() // Default http:// if no protocol specified
{ if (!url.Contains("://"))
//TODO {
url = "http://" + url;
}
} try
{
public void OpenBinaryFile(Uri newUri) Util.GotoUrl(this, url);
{ }
Toast.MakeText(this, "opening file TODO", ToastLength.Short).Show(); catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
}
return true;
} }
public void AddEntryToIntent(Intent intent) public void AddEntryToIntent(Intent intent)
{ {
PluginHost.AddEntryToIntent(intent, App.Kp2A.GetDb().LastOpenedEntry); PluginHost.AddEntryToIntent(intent, App.Kp2a.GetDb().LastOpenedEntry);
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,82 +1,131 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent"
xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_height="fill_parent">
android:id="@+id/plugin_scroll" <LinearLayout
android:layout_width="fill_parent" android:id="@+id/bottom_bar"
android:layout_height="fill_parent" android:layout_width="match_parent"
android:fillViewport="true" android:layout_height="wrap_content"
android:scrollbarStyle="insideOverlay"> android:orientation="horizontal"
<LinearLayout android:id="@+id/scopes_list" android:divider="?android:attr/dividerVertical"
android:layout_width="fill_parent" android:showDividers="middle"
android:layout_height="wrap_content" android:layout_alignParentBottom="true"
android:orientation="vertical"> android:dividerPadding="12dp"
<RelativeLayout android:baselineAligned="false">
android:layout_width="fill_parent" <FrameLayout
android:layout_height="wrap_content" android:id="@+id/accept_button"
android:padding="4dp"> style="?android:attr/actionButtonStyle"
<ImageView android:layout_width="0dp"
android:id="@+id/imgIcon" android:layout_height="wrap_content"
android:layout_width="64dp" android:layout_weight="1">
android:layout_height="64dp"
android:layout_alignParentTop="true"
android:layout_marginRight="15dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp" />
<TextView <TextView
android:id="@+id/txtLabel" style="?android:actionBarTabTextStyle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/imgIcon"
android:textSize="22dp"
/>
<TextView
android:id="@+id/txtVersion"
android:text="0.9.3"
android:textSize="14dp"
android:textColor="#888888"
android:layout_toRightOf="@id/imgIcon"
android:layout_below="@id/txtLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/txtAuthor"
android:text=""
android:textSize="14dp"
android:textColor="#888888"
android:layout_below="@id/txtVersion"
android:layout_toRightOf="@id/imgIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
<TextView
android:id="@+id/txtShortDesc"
android:text=""
android:textSize="14dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/cb_enabled"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/plugin_enabled_checkbox" android:layout_gravity="center"
android:checked="true" android:paddingRight="20dp"
/> android:drawablePadding="8dp"
android:drawableLeft="?attr/NavigationAcceptDrawable"
<TextView android:gravity="center_vertical"
android:id="@+id/scopes_title" android:text="@string/accept" />
android:layout_width="wrap_content" </FrameLayout>
<FrameLayout
android:id="@+id/deny_button"
style="?android:attr/actionButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<TextView
style="?android:actionBarTabTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingRight="20dp"
android:drawableLeft="?attr/CancelDrawable"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:text="@string/deny" />
</FrameLayout>
</LinearLayout>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/plugin_scroll"
android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Scopes" android:padding="12dp"
android:layout_marginLeft="10dp" android:scrollbarStyle="insideOverlay">
android:layout_marginTop="18dp"
style="@style/ExtraFieldHeader" />
<LinearLayout android:id="@+id/scopes_list" <LinearLayout android:id="@+id/scopes_list"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="4dp">
<ImageView
android:id="@+id/imgIcon"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_alignParentTop="true"
android:layout_marginRight="15dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp" />
<TextView
android:id="@+id/txtLabel"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/imgIcon"
android:textSize="22dp"
/>
<TextView
android:id="@+id/txtVersion"
android:text="0.9.3"
android:textSize="14dp"
android:textColor="#888888"
android:layout_toRightOf="@id/imgIcon"
android:layout_below="@id/txtLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/txtAuthor"
android:text=""
android:textSize="14dp"
android:textColor="#888888"
android:layout_below="@id/txtVersion"
android:layout_toRightOf="@id/imgIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
<TextView
android:id="@+id/txtShortDesc"
android:text=""
android:textSize="14dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/cb_enabled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/plugin_enabled_checkbox"
android:checked="true"
/>
<TextView
android:id="@+id/scopes_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Scopes"
android:layout_marginLeft="10dp"
android:layout_marginTop="18dp"
style="@style/ExtraFieldHeader" />
<LinearLayout android:id="@+id/scopes_list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </ScrollView>
</ScrollView> </RelativeLayout>

View File

@ -4,14 +4,15 @@
<LinearLayout <LinearLayout
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical"
<TextView android:padding="12dp">
android:layout_width="wrap_content" <Button
android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge" android:textAppearance="?android:attr/textAppearanceMedium"
android:text="plugin text" android:text="@string/plugin_web"
style="@style/PaddedElement" style="@style/PaddedElement"
android:id="@+id/textView" android:id="@+id/btnPluginsOnline"
android:layout_gravity="left|center_vertical" /> android:layout_gravity="left|center_vertical" />
<ListView <ListView
android:id="@android:id/list" android:id="@android:id/list"

View File

@ -7,6 +7,7 @@
<string name="CreditsText">The User Interface is based on a port of KeepassDroid developed by Brian Pellin. Code for database operations is based on KeePass by Dominik Reichl. The Android robot is reproduced or modified from work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.</string> <string name="CreditsText">The User Interface is based on a port of KeepassDroid developed by Brian Pellin. Code for database operations is based on KeePass by Dominik Reichl. The Android robot is reproduced or modified from work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.</string>
<string name="CreditsTextSFTP">SFTP support is implemented using the JSch library under BSD licence, created by JCraft, Inc.</string> <string name="CreditsTextSFTP">SFTP support is implemented using the JSch library under BSD licence, created by JCraft, Inc.</string>
<string name="accept">Accept</string> <string name="accept">Accept</string>
<string name="deny">Deny</string>
<string name="add_entry">Add entry</string> <string name="add_entry">Add entry</string>
<string name="add_url_entry">Create entry for URL</string> <string name="add_url_entry">Create entry for URL</string>
<string name="add_group">Add group</string> <string name="add_group">Add group</string>
@ -402,15 +403,13 @@
<string name="loading">Loading…</string> <string name="loading">Loading…</string>
<string name="enable_plugin_title">Enable plug-in?</string>
<string name="enable_plugin_question">Keepass2Android detected a plug-in with name: %1$s from package name: %2$s. Do you want to enable this plug-in? It will have access to the data stored in your Keepass databases.</string>
<string name="plugins">Plug-ins</string> <string name="plugins">Plug-ins</string>
<string name="plugin_packagename">Package name:</string> <string name="plugin_packagename">Package name:</string>
<string name="plugin_description">Description (not verified):</string> <string name="plugin_description">Description (not verified):</string>
<string name="plugin_author">Author (not verified):</string> <string name="plugin_author">Author (not verified):</string>
<string name="plugin_enabled">enabled</string> <string name="plugin_enabled">enabled</string>
<string name="plugin_disabled">disabled</string> <string name="plugin_disabled">disabled</string>
<string name="plugin_web">Find plug-ins online</string>
<string name="plugin_enabled_checkbox">Enabled</string> <string name="plugin_enabled_checkbox">Enabled</string>

View File

@ -63,9 +63,6 @@
<item name="@android:textSize">12sp</item> <item name="@android:textSize">12sp</item>
<item name="@android:background">#222222</item> <item name="@android:background">#222222</item>
</style> </style>
<style name="EntryFieldHeader" parent="WhiteOnDarkSmall">
<item name="@android:textSize">12sp</item>
</style>
<style name="ElementTextTitle" parent="WhiteOnBlack"> <style name="ElementTextTitle" parent="WhiteOnBlack">
<item name="@android:textColor">@color/group</item> <item name="@android:textColor">@color/group</item>
<item name="@android:background">@android:color/transparent</item> <item name="@android:background">@android:color/transparent</item>

View File

@ -284,4 +284,8 @@
</PreferenceScreen> </PreferenceScreen>
<PreferenceScreen android:key="plugin_key" android:title="@string/plugins">
<intent android:action="keepass2android.PluginListActivity"/>
</PreferenceScreen>
</PreferenceScreen> </PreferenceScreen>

View File

@ -99,6 +99,17 @@
<Compile Include="app\OtpAuxCacheSupervisor.cs" /> <Compile Include="app\OtpAuxCacheSupervisor.cs" />
<Compile Include="CreateDatabaseActivity.cs" /> <Compile Include="CreateDatabaseActivity.cs" />
<Compile Include="CreateNewFilename.cs" /> <Compile Include="CreateNewFilename.cs" />
<Compile Include="EntryActivityClasses\CopyToClipboardPopupMenuIcon.cs" />
<Compile Include="EntryActivityClasses\ExtraStringView.cs" />
<Compile Include="EntryActivityClasses\GotoUrlMenuItem.cs" />
<Compile Include="EntryActivityClasses\IPopupMenuItem.cs" />
<Compile Include="EntryActivityClasses\IStringView.cs" />
<Compile Include="EntryActivityClasses\OpenBinaryPopupItem.cs" />
<Compile Include="EntryActivityClasses\PluginMenuOption.cs" />
<Compile Include="EntryActivityClasses\PluginPopupMenuItem.cs" />
<Compile Include="EntryActivityClasses\StandardStringView.cs" />
<Compile Include="EntryActivityClasses\ToggleVisibilityPopupMenuItem.cs" />
<Compile Include="EntryActivityClasses\WriteBinaryToFilePopupItem.cs" />
<Compile Include="ExportDatabaseActivity.cs" /> <Compile Include="ExportDatabaseActivity.cs" />
<Compile Include="fileselect\FileChooserFileProvider.cs" /> <Compile Include="fileselect\FileChooserFileProvider.cs" />
<Compile Include="fileselect\FileStorageSetupActivity.cs" /> <Compile Include="fileselect\FileStorageSetupActivity.cs" />
@ -649,10 +660,6 @@
<Project>{3c0f7fe5-639f-4422-a087-8b26cf862d1b}</Project> <Project>{3c0f7fe5-639f-4422-a087-8b26cf862d1b}</Project>
<Name>AndroidFileChooserBinding</Name> <Name>AndroidFileChooserBinding</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\AppCompatV7Binding\AppCompatV7Binding.csproj">
<Project>{23233a28-d74f-4bf8-b4d8-834060840bd7}</Project>
<Name>AppCompatV7Binding</Name>
</ProjectReference>
<ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj"> <ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj">
<Project>{48574278-4779-4b3a-a9e4-9cf1bc285d0b}</Project> <Project>{48574278-4779-4b3a-a9e4-9cf1bc285d0b}</Project>
<Name>JavaFileStorageBindings</Name> <Name>JavaFileStorageBindings</Name>
@ -956,4 +963,7 @@
<ItemGroup> <ItemGroup>
<AndroidResource Include="Resources\drawable\vdots.png" /> <AndroidResource Include="Resources\drawable\vdots.png" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\ic_menu_copy_holo_light.png" />
</ItemGroup>
</Project> </Project>

View File

@ -87,21 +87,12 @@ namespace keepass2android
public void StorePlugin(string pluginPackage, string accessToken, IList<string> requestedScopes) public void StorePlugin(string pluginPackage, string accessToken, IList<string> requestedScopes)
{ {
ISharedPreferences hostPrefs = GetHostPrefs();
ISharedPreferences pluginPrefs = GetPreferencesForPlugin(pluginPackage); ISharedPreferences pluginPrefs = GetPreferencesForPlugin(pluginPackage);
var stringSet = hostPrefs.GetStringSet(_pluginlist, new Collection<string>());
if (!stringSet.Contains(pluginPackage))
{
stringSet.Add(pluginPackage);
hostPrefs.Edit()
.PutStringSet(_pluginlist, stringSet)
.Commit();
}
pluginPrefs.Edit() pluginPrefs.Edit()
.PutString(_scopes, AccessManager.StringArrayToString(requestedScopes)) .PutString(_scopes, AccessManager.StringArrayToString(requestedScopes))
.PutString(_accessToken, accessToken) .PutString(_accessToken, accessToken)
.Commit(); .Commit();
} }
private ISharedPreferences GetHostPrefs() private ISharedPreferences GetHostPrefs()
@ -123,7 +114,7 @@ namespace keepass2android
i.PutExtra(Strings.ExtraAccessToken, accessToken); i.PutExtra(Strings.ExtraAccessToken, accessToken);
_ctx.SendBroadcast(i); _ctx.SendBroadcast(i);
StorePlugin(pluginPackage, accessToken, GetPluginScopes( pluginPackage)); StorePlugin(pluginPackage, accessToken, GetPluginScopes(pluginPackage));
} }
else else
{ {
@ -182,12 +173,12 @@ namespace keepass2android
public IEnumerable<string> GetPluginsWithAcceptedScope(string scope) public IEnumerable<string> GetPluginsWithAcceptedScope(string scope)
{ {
return GetAllPluginPackages().Where(plugin => return GetAllPluginPackages().Where(plugin =>
{ {
var prefs = GetPreferencesForPlugin(plugin); var prefs = GetPreferencesForPlugin(plugin);
return (prefs.GetString(_accessToken, null) != null) return (prefs.GetString(_accessToken, null) != null)
&& AccessManager.StringToStringArray(prefs.GetString(_scopes, "")).Contains(scope); && AccessManager.StringToStringArray(prefs.GetString(_scopes, "")).Contains(scope);
}); });
} }
public void ClearPlugin(string plugin) public void ClearPlugin(string plugin)

View File

@ -20,35 +20,27 @@ using keepass2android.views;
namespace PluginHostTest namespace PluginHostTest
{ {
[Activity(Label = "@string/app_name", Theme = "@style/Base")] [Activity(Label = AppNames.AppName)]
[IntentFilter(new[] { Strings.ActionEditPluginSettings },
Label = AppNames.AppName,
Categories = new[] { Intent.CategoryDefault })]
public class PluginDetailsActivity : Activity public class PluginDetailsActivity : Activity
{ {
private CheckBox _checkbox;
private string _pluginPackageName;
protected override void OnCreate(Bundle bundle) protected override void OnCreate(Bundle bundle)
{ {
base.OnCreate(bundle); base.OnCreate(bundle);
new ActivityDesign(this).ApplyTheme();
string pluginPackage = Intent.GetStringExtra("PluginPackage"); _pluginPackageName = Intent.GetStringExtra(Strings.ExtraPluginPackage);
Intent mainIntent = new Intent(Intent.ActionMain, null); var pluginRes = PackageManager.GetResourcesForApplication(_pluginPackageName);
mainIntent.AddCategory(Intent.CategoryLauncher); var title = GetStringFromPlugin(pluginRes, _pluginPackageName, "kp2aplugin_title");
var author = GetStringFromPlugin(pluginRes, _pluginPackageName, "kp2aplugin_author");
IList<ResolveInfo> appList = PackageManager.QueryIntentActivities(mainIntent, 0); var shortDesc = GetStringFromPlugin(pluginRes, _pluginPackageName, "kp2aplugin_shortdesc");
//Collections.Sort(appList, new ResolveInfo.DisplayNameComparator(PackageManager)); var version = PackageManager.GetPackageInfo(_pluginPackageName, 0).VersionName;
foreach (ResolveInfo temp in appList)
{
Log.Verbose("my logs", "package and activity name = "
+ temp.ActivityInfo.PackageName + " "
+ temp.ActivityInfo.Name + " " + temp.ActivityInfo.IconResource);
}
var pluginRes = PackageManager.GetResourcesForApplication(pluginPackage);
var title = GetStringFromPlugin(pluginRes, pluginPackage, "kp2aplugin_title");
var author = GetStringFromPlugin(pluginRes, pluginPackage, "kp2aplugin_author");
var shortDesc = GetStringFromPlugin(pluginRes, pluginPackage, "kp2aplugin_shortdesc");
var version = PackageManager.GetPackageInfo(pluginPackage, 0).VersionName;
SetContentView(Resource.Layout.plugin_details); SetContentView(Resource.Layout.plugin_details);
if (title != null) if (title != null)
@ -57,32 +49,86 @@ namespace PluginHostTest
SetTextOrHide(Resource.Id.txtAuthor, author); SetTextOrHide(Resource.Id.txtAuthor, author);
SetTextOrHide(Resource.Id.txtShortDesc, shortDesc); SetTextOrHide(Resource.Id.txtShortDesc, shortDesc);
var checkbox = FindViewById<CheckBox>(Resource.Id.cb_enabled); _checkbox = FindViewById<CheckBox>(Resource.Id.cb_enabled);
PluginDatabase pluginDb = new PluginDatabase(this); _checkbox.CheckedChange += delegate
checkbox.Checked = pluginDb.IsEnabled(pluginPackage); {
checkbox.CheckedChange += delegate(object sender, CompoundButton.CheckedChangeEventArgs args) new PluginDatabase(this).SetEnabled(_pluginPackageName, _checkbox.Checked);
{ };
pluginDb.SetEnabled(pluginPackage, checkbox.Checked);
};
Drawable d = PackageManager.GetApplicationIcon(pluginPackage); Drawable d = PackageManager.GetApplicationIcon(_pluginPackageName);
FindViewById<ImageView>(Resource.Id.imgIcon).SetImageDrawable(d); FindViewById<ImageView>(Resource.Id.imgIcon).SetImageDrawable(d);
FindViewById<TextView>(Resource.Id.txtVersion).Text = version; FindViewById<TextView>(Resource.Id.txtVersion).Text = version;
var scopesContainer = FindViewById<LinearLayout>(Resource.Id.scopes_list); //cannot be wrong to update the view when we received an update
PluginHost.OnReceivedRequest += OnPluginHostOnOnReceivedRequest;
foreach (string scope in pluginDb.GetPluginScopes(pluginPackage)) if (Intent.Action == Strings.ActionEditPluginSettings)
{
//this action can be triggered by external apps so we don't know if anybody has ever triggered
//the plugin to request access. Do this now, don't set the view right now
PluginHost.TriggerRequest(this, _pluginPackageName, new PluginDatabase(this));
//show the buttons instead of the checkbox
_checkbox.Visibility = ViewStates.Invisible;
FindViewById(Resource.Id.accept_button).Visibility = ViewStates.Visible;
FindViewById(Resource.Id.deny_button).Visibility = ViewStates.Visible;
FindViewById(Resource.Id.accept_button).Click += delegate(object sender, EventArgs args)
{
new PluginDatabase(this).SetEnabled(_pluginPackageName, true);
SetResult(Result.Ok);
Finish();
};
FindViewById(Resource.Id.deny_button).Click += delegate(object sender, EventArgs args)
{
new PluginDatabase(this).SetEnabled(_pluginPackageName, false);
SetResult(Result.Canceled);
Finish();
};
}
else
{
UpdateView();
_checkbox.Visibility = ViewStates.Visible;
FindViewById(Resource.Id.accept_button).Visibility = ViewStates.Gone;
FindViewById(Resource.Id.deny_button).Visibility = ViewStates.Gone;
}
}
private void OnPluginHostOnOnReceivedRequest(object sender, PluginHost.PluginHostEventArgs args)
{
if (args.Package == _pluginPackageName)
{
UpdateView();
}
}
protected override void OnDestroy()
{
PluginHost.OnReceivedRequest -= OnPluginHostOnOnReceivedRequest;
base.OnDestroy();
}
private void UpdateView()
{
var scopesContainer = FindViewById<LinearLayout>(Resource.Id.scopes_list);
//scopesContainer.RemoveAllViews();
var pluginDb = new PluginDatabase(this);
_checkbox.Checked = pluginDb.IsEnabled(_pluginPackageName);
foreach (string scope in pluginDb.GetPluginScopes(_pluginPackageName))
{ {
string scopeId = scope.Substring("keepass2android.".Length); string scopeId = scope.Substring("keepass2android.".Length);
TextWithHelp help = new TextWithHelp(this, GetString(Resources.GetIdentifier(scopeId + "_title", "string", PackageName)), GetString(Resources.GetIdentifier(scopeId + "_explanation", "string", PackageName))); TextWithHelp help = new TextWithHelp(this,
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent); GetString(Resources.GetIdentifier(scopeId + "_title", "string", PackageName)),
GetString(Resources.GetIdentifier(scopeId + "_explanation", "string", PackageName)));
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FillParent,
ViewGroup.LayoutParams.WrapContent);
help.LayoutParameters = layoutParams; help.LayoutParameters = layoutParams;
scopesContainer.AddView(help); scopesContainer.AddView(help);
} }
} }
private void SetTextOrHide(int resourceId, string text) private void SetTextOrHide(int resourceId, string text)
@ -98,7 +144,7 @@ namespace PluginHostTest
public static string GetStringFromPlugin(Resources pluginRes, string pluginPackage, string stringId) public static string GetStringFromPlugin(Resources pluginRes, string pluginPackage, string stringId)
{ {
int titleId = pluginRes.GetIdentifier(pluginPackage + ":string/"+stringId, null, null); int titleId = pluginRes.GetIdentifier(pluginPackage + ":string/" + stringId, null, null);
string title = null; string title = null;
if (titleId != 0) if (titleId != 0)
title = pluginRes.GetString(titleId); title = pluginRes.GetString(titleId);

View File

@ -11,9 +11,12 @@ using Org.Json;
namespace keepass2android namespace keepass2android
{ {
[BroadcastReceiver()] /// <summary>
[IntentFilter(new[] { Strings.ActionRequestAccess})] /// Class which manages plugins inside the app
public class PluginHost: BroadcastReceiver /// </summary>
[BroadcastReceiver]
[IntentFilter(new[] { Strings.ActionRequestAccess })]
public class PluginHost : BroadcastReceiver
{ {
private const string _tag = "KP2A_PluginHost"; private const string _tag = "KP2A_PluginHost";
@ -21,6 +24,9 @@ namespace keepass2android
private static readonly string[] _validScopes = { Strings.ScopeDatabaseActions, Strings.ScopeCurrentEntry }; private static readonly string[] _validScopes = { Strings.ScopeDatabaseActions, Strings.ScopeCurrentEntry };
/// <summary>
/// Sends a broadcast to all potential plugins prompting them to request access to our app.
/// </summary>
public static void TriggerRequests(Context ctx) public static void TriggerRequests(Context ctx)
{ {
Intent accessIntent = new Intent(Strings.ActionTriggerRequestAccess); Intent accessIntent = new Intent(Strings.ActionTriggerRequestAccess);
@ -33,27 +39,26 @@ namespace keepass2android
ApplicationInfo appInfo = ri.ActivityInfo.ApplicationInfo; ApplicationInfo appInfo = ri.ActivityInfo.ApplicationInfo;
String pkgName = appInfo.PackageName; String pkgName = appInfo.PackageName;
try TriggerRequest(ctx, pkgName, pluginDatabase);
{
Intent triggerIntent = new Intent(Strings.ActionTriggerRequestAccess);
triggerIntent.SetPackage(pkgName);
triggerIntent.PutExtra(Strings.ExtraSender, ctx.PackageName);
triggerIntent.PutExtra(Strings.ExtraRequestToken, pluginDatabase.GetRequestToken(pkgName));
ctx.SendBroadcast(triggerIntent);
}
catch (Exception e)
{
}
} }
} }
public static void TriggerRequest(Context ctx, string pkgName, PluginDatabase pluginDatabase)
{
try
{
Intent triggerIntent = new Intent(Strings.ActionTriggerRequestAccess);
triggerIntent.SetPackage(pkgName);
triggerIntent.PutExtra(Strings.ExtraSender, ctx.PackageName);
triggerIntent.PutExtra(Strings.ExtraRequestToken, pluginDatabase.GetRequestToken(pkgName));
ctx.SendBroadcast(triggerIntent);
}
catch (Exception e)
{
}
}
public override void OnReceive(Context context, Intent intent) public override void OnReceive(Context context, Intent intent)
@ -64,7 +69,7 @@ namespace keepass2android
var senderPackage = intent.GetStringExtra(Strings.ExtraSender); var senderPackage = intent.GetStringExtra(Strings.ExtraSender);
var requestToken = intent.GetStringExtra(Strings.ExtraRequestToken); var requestToken = intent.GetStringExtra(Strings.ExtraRequestToken);
var requestedScopes = intent.GetStringArrayListExtra(Strings.ExtraScopes); var requestedScopes = intent.GetStringArrayListExtra(Strings.ExtraScopes);
if (!AreScopesValid(requestedScopes)) if (!AreScopesValid(requestedScopes))
{ {
@ -79,7 +84,7 @@ namespace keepass2android
string currentAccessToken = pluginDb.GetAccessToken(senderPackage); string currentAccessToken = pluginDb.GetAccessToken(senderPackage);
if ((currentAccessToken != null) if ((currentAccessToken != null)
&& (AccessManager.IsSubset(requestedScopes, && (AccessManager.IsSubset(requestedScopes,
pluginDb.GetPluginScopes(senderPackage)))) pluginDb.GetPluginScopes(senderPackage))))
{ {
//permission already there. //permission already there.
var i = new Intent(Strings.ActionReceiveAccess); var i = new Intent(Strings.ActionReceiveAccess);
@ -113,7 +118,8 @@ namespace keepass2android
} }
} }
if (OnReceivedRequest != null)
OnReceivedRequest(this, new PluginHostEventArgs() { Package = senderPackage });
} }
} }
@ -131,6 +137,9 @@ namespace keepass2android
return true; return true;
} }
/// <summary>
/// adds the entry output data to the intent to be sent to a plugin
/// </summary>
public static void AddEntryToIntent(Intent intent, PwEntryOutput entry) public static void AddEntryToIntent(Intent intent, PwEntryOutput entry)
{ {
/*//add the entry XML /*//add the entry XML
@ -150,5 +159,11 @@ namespace keepass2android
intent.PutExtra(Strings.ExtraEntryId, entry.Uuid.ToHexString()); intent.PutExtra(Strings.ExtraEntryId, entry.Uuid.ToHexString());
} }
public class PluginHostEventArgs
{
public string Package { get; set; }
}
public static event EventHandler<PluginHostEventArgs> OnReceivedRequest;
} }
} }

View File

@ -5,11 +5,13 @@ using Android.Content;
using Android.Content.PM; using Android.Content.PM;
using Android.OS; using Android.OS;
using Android.Widget; using Android.Widget;
using Keepass2android.Pluginsdk;
using PluginHostTest; using PluginHostTest;
namespace keepass2android namespace keepass2android
{ {
[Activity(Label = "@string/app_name", Theme = "@style/Base")] [Activity(Label = "@string/plugins", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.KeyboardHidden)]
[IntentFilter(new[] { "keepass2android.PluginListActivity" }, Categories = new[] { Intent.CategoryDefault })]
public class PluginListActivity : ListActivity public class PluginListActivity : ListActivity
{ {
private PluginArrayAdapter _pluginArrayAdapter; private PluginArrayAdapter _pluginArrayAdapter;
@ -19,20 +21,26 @@ namespace keepass2android
{ {
base.OnCreate(bundle); base.OnCreate(bundle);
//TODO _design.ApplyTheme(); new ActivityDesign(this).ApplyTheme();
SetContentView(Resource.Layout.plugin_list); SetContentView(Resource.Layout.plugin_list);
PluginHost.TriggerRequests(this);
ListView listView = FindViewById<ListView>(Android.Resource.Id.List); ListView listView = FindViewById<ListView>(Android.Resource.Id.List);
listView.ItemClick += listView.ItemClick +=
(sender, args) => (sender, args) =>
{ {
Intent i = new Intent(this, typeof(PluginDetailsActivity)); Intent i = new Intent(this, typeof(PluginDetailsActivity));
i.PutExtra("PluginPackage", _items[args.Position].Package); i.PutExtra(Strings.ExtraPluginPackage, _items[args.Position].Package);
StartActivity(i); StartActivity(i);
}; };
FindViewById<Button>(Resource.Id.btnPluginsOnline).Click += delegate
{
Util.GotoUrl(this, "https://keepass2android.codeplex.com/wikipage?title=Available%20Plug-ins");
};
// Create your application here
} }
protected override void OnResume() protected override void OnResume()
{ {
@ -40,9 +48,9 @@ namespace keepass2android
PluginDatabase pluginDb = new PluginDatabase(this); PluginDatabase pluginDb = new PluginDatabase(this);
_items = (from pluginPackage in pluginDb.GetAllPluginPackages() _items = (from pluginPackage in pluginDb.GetAllPluginPackages()
let version = PackageManager.GetPackageInfo(pluginPackage, 0).VersionName let version = PackageManager.GetPackageInfo(pluginPackage, 0).VersionName
let enabledStatus = pluginDb.IsEnabled(pluginPackage) ? GetString(Resource.String.plugin_enabled) : GetString(Resource.String.plugin_disabled) let enabledStatus = pluginDb.IsEnabled(pluginPackage) ? GetString(Resource.String.plugin_enabled) : GetString(Resource.String.plugin_disabled)
select new PluginItem(pluginPackage, enabledStatus, this)).ToList(); select new PluginItem(pluginPackage, enabledStatus, this)).ToList();
/* /*
{ {
new PluginItem("PluginA", Resource.Drawable.Icon, "keepass2android.plugina", "connected"), new PluginItem("PluginA", Resource.Drawable.Icon, "keepass2android.plugina", "connected"),

View File

@ -49,7 +49,7 @@ namespace keepass2android
public const int NotifyKeyboard = 3; public const int NotifyKeyboard = 3;
public const int ClearClipboard = 4; public const int ClearClipboard = 4;
public void CopyValueToClipboardWithTimeout(Context ctx, string text) static public void CopyValueToClipboardWithTimeout(Context ctx, string text)
{ {
Intent i = new Intent(ctx, typeof(CopyToClipboardService)); Intent i = new Intent(ctx, typeof(CopyToClipboardService));
i.SetAction(Intents.CopyStringToClipboard); i.SetAction(Intents.CopyStringToClipboard);