+ pluginhost test project

This commit is contained in:
Philipp Crocoll 2014-04-22 06:27:13 +02:00
parent d1cc47057d
commit 4697dbf41c
204 changed files with 5037 additions and 0 deletions

View File

@ -0,0 +1,173 @@
using System;
using System.Diagnostics;
using System.Linq;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Keepass2android.Pluginsdk;
using keepass2android;
namespace PluginHostTest
{
[Activity(Label = "PluginHostTest", MainLauncher = true, Icon = "@drawable/icon")]
public class Activity1 : Activity
{
int count = 1;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
// Get our button from the layout resource,
// and attach an event to it
Button button = FindViewById<Button>(Resource.Id.MyButton);
button.Click += delegate { PluginHost.TriggerRequests(this); };
FindViewById<Button>(Resource.Id.managePluginsButton).Click += delegate(object sender, EventArgs args)
{
StartActivity(new Intent(this, typeof(PluginListActivity)));
};
FindViewById<Button>(Resource.Id.entryviewButton).Click += delegate
{
StartActivity(new Intent(this, typeof(EntryActivity)));
};
FindViewById<Button>(Resource.Id.testDbButton).Click += delegate
{
string message = "ok. ";
try
{
Stopwatch sw = new Stopwatch();
sw.Start();
PluginDatabase db = new PluginDatabase(this);
db.Clear();
if (db.GetAllPluginPackages().Count() != 0)
throw new Exception("db not empty!");
const string testPackageA = "test.package.a";
const string testPackageB = "test.package.b";
db.ClearPlugin(testPackageA);
db.ClearPlugin(testPackageB);
EnsurePackageDataIsEmpty(db, testPackageA);
EnsurePackageDataIsEmpty(db, testPackageB);
string[] requestedScopes = {
Strings.ScopeDatabaseActions
};
db.StorePlugin(testPackageA, null, requestedScopes);
EnsurePackageDataIsEmpty(db, testPackageB);
EnsurePackageDataIsEmpty(new PluginDatabase(this), testPackageB);
db.StorePlugin(testPackageB, null, requestedScopes);
EnsurePackageHasUnacceptedScope(db, testPackageA, Strings.ScopeDatabaseActions);
EnsurePackageHasUnacceptedScope(db, testPackageB, Strings.ScopeDatabaseActions);
EnsurePackageHasUnacceptedScope(new PluginDatabase(this), testPackageA, Strings.ScopeDatabaseActions);
if (db.GetAllPluginPackages().Count() != 2)
throw new Exception("wrong count of plugins");
if (db.GetPluginsWithAcceptedScope(Strings.ScopeDatabaseActions).Any())
{
throw new Exception("wrong count of accepted plugins");
}
if (new PluginDatabase(this).GetPluginsWithAcceptedScope(Strings.ScopeDatabaseActions).Any())
{
throw new Exception("wrong count of accepted plugins");
}
db.SetEnabled(testPackageA, true);
if (db.GetPluginsWithAcceptedScope(Strings.ScopeDatabaseActions).Single() != testPackageA)
{
throw new Exception("wrong plugin");
}
if (new PluginDatabase(this).GetPluginsWithAcceptedScope(Strings.ScopeDatabaseActions).Single() != testPackageA)
{
throw new Exception("wrong plugin");
}
if (db.GetPluginsWithAcceptedScope("somescope").Any())
{
throw new Exception("wrong count of accepted plugins");
}
var accessTokenA = db.GetAccessToken(testPackageA);
if (String.IsNullOrEmpty(accessTokenA))
throw new Exception("expected access token!");
if (!db.IsEnabled(testPackageA))
throw new Exception("plugin not enabled!");
if (db.IsEnabled(testPackageB))
throw new Exception("plugin enabled!");
if (!db.IsValidAccessToken(testPackageA, accessTokenA, Strings.ScopeDatabaseActions))
throw new Exception("invalid token!");
db.SetEnabled(testPackageA, false);
if (db.IsValidAccessToken(testPackageA, accessTokenA, Strings.ScopeDatabaseActions))
throw new Exception("valid token?!");
if (db.GetPluginsWithAcceptedScope(Strings.ScopeDatabaseActions).Any())
throw new Exception("unexpected!");
new PluginDatabase(this).SetEnabled(testPackageB, true);
if (!db.IsEnabled(testPackageB))
throw new Exception("plugin not enabled!");
db.SetEnabled(testPackageA, true);
accessTokenA = db.GetAccessToken(testPackageA);
message += sw.ElapsedMilliseconds + "ms";
Stopwatch swQuery = new Stopwatch();
swQuery.Start();
int n = 3;
for (int i = 0; i < n; i++)
{
if (db.GetPluginsWithAcceptedScope(Strings.ScopeDatabaseActions).Count() != 2)
{
throw new Exception("wrong plugin");
}
if (!db.IsValidAccessToken(testPackageA, accessTokenA, Strings.ScopeDatabaseActions))
throw new Exception("invalid token");
}
message += "/ " + swQuery.ElapsedMilliseconds/(double)n/2.0 + "ms for query";
}
catch (Exception exception)
{
message = exception.ToString();
}
Toast.MakeText(this, message, ToastLength.Long).Show();
};
}
private void EnsurePackageHasUnacceptedScope(PluginDatabase db, string plugin, string scope)
{
if (String.IsNullOrEmpty(db.GetRequestToken(plugin)))
throw new Exception("invalid request token");
if (db.GetAccessToken(plugin) != null)
throw new Exception("invalid access token!");
if (db.GetPluginScopes(plugin).Count != 1)
throw new Exception("Unexpected scopes!");
if (db.GetPluginScopes(plugin).First() != scope)
throw new Exception("Unexpected scope in db!");
}
private static void EnsurePackageDataIsEmpty(PluginDatabase db, string testPackageA)
{
if (String.IsNullOrEmpty(db.GetRequestToken(testPackageA)))
throw new Exception("invalid request token");
if (db.GetAccessToken(testPackageA) != null)
throw new Exception("invalid access token!");
if (db.GetPluginScopes(testPackageA).Count > 0)
throw new Exception("Unexpected scopes!");
}
}
}

View File

@ -0,0 +1,19 @@
Any raw assets you want to be deployed with your application can be placed in
this directory (and child directories) and given a Build Action of "AndroidAsset".
These files will be deployed with you package and will be accessible using Android's
AssetManager, like this:
public class ReadAsset : Activity
{
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
InputStream input = Assets.Open ("my_asset.txt");
}
}
Additionally, some Android functions will automatically load asset files:
Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,44 @@
/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
Keepass2Android is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Keepass2Android is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
namespace keepass2android.view
{
public abstract class ClickView : LinearLayout {
protected ClickView (IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
protected ClickView(Context context) :base(context)
{
}
abstract public void OnClick();
abstract public void OnCreateMenu(IContextMenu menu, IContextMenuContextMenuInfo menuInfo);
abstract public bool OnContextItemSelected(IMenuItem item);
}
}

View File

@ -0,0 +1,367 @@
/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
Keepass2Android is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Keepass2Android is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Views;
using Android.Widget;
using Android.Preferences;
using Android.Text.Method;
using System.Globalization;
using Android.Content.PM;
using Android.Webkit;
using Android.Graphics;
using Java.IO;
using PluginHostTest;
namespace keepass2android
{
[Activity (Label = "@string/app_name", ConfigurationChanges=ConfigChanges.Orientation|ConfigChanges.KeyboardHidden, Theme="@style/NoTitleBar")]
public class EntryActivity : Activity {
public const String KeyEntry = "entry";
public const String KeyRefreshPos = "refresh_pos";
public const String KeyCloseAfterCreate = "close_after_create";
private static Typeface _passwordFont;
private bool _showPassword;
private int _pos;
private List<TextView> _protectedTextViews;
protected void SetEntryView() {
SetContentView(Resource.Layout.entry_view);
}
protected void SetupEditButtons() {
View edit = FindViewById(Resource.Id.entry_edit);
if (true)
{
edit.Visibility = ViewStates.Visible;
edit.Click += (sender, e) =>
{
};
}
else
{
edit.Visibility = ViewStates.Gone;
}
}
protected override void OnCreate(Bundle savedInstanceState)
{
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this);
long usageCount = prefs.GetLong(GetString(Resource.String.UsageCount_key), 0);
ISharedPreferencesEditor edit = prefs.Edit();
edit.PutLong(GetString(Resource.String.UsageCount_key), usageCount+1);
edit.Commit();
_showPassword = ! prefs.GetBoolean(GetString(Resource.String.maskpass_key), Resources.GetBoolean(Resource.Boolean.maskpass_default));
base.OnCreate(savedInstanceState);
SetEntryView();
FillData(false);
SetupEditButtons();
}
public void CompleteOnCreate()
{
}
private String getDateTime(DateTime dt) {
return dt.ToString ("g", CultureInfo.CurrentUICulture);
}
String concatTags(List<string> tags)
{
StringBuilder sb = new StringBuilder();
foreach (string tag in tags)
{
sb.Append(tag);
sb.Append(", ");
}
if (tags.Count > 0)
sb.Remove(sb.Length-2,2);
return sb.ToString();
}
void PopulateExtraStrings(bool trimList)
{
ViewGroup extraGroup = (ViewGroup)FindViewById(Resource.Id.extra_strings);
if (trimList)
{
extraGroup.RemoveAllViews();
}
bool hasExtraFields = false;
foreach (var view in from pair in new Dictionary<string, string>() { { "Field header", "field value" }, { "another header", "_aiaeiae" } }
orderby pair.Key
select CreateEditSection(pair.Key, pair.Value, true))
{
extraGroup.AddView(view);
hasExtraFields = true;
}
FindViewById(Resource.Id.entry_extra_strings_label).Visibility = hasExtraFields ? ViewStates.Visible : ViewStates.Gone;
}
View CreateEditSection(string key, string value, bool isProtected)
{
LinearLayout layout = new LinearLayout(this, null) {Orientation = Orientation.Vertical};
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
layoutParams.SetMargins(10,0,0,0);
layout.LayoutParameters = layoutParams;
View viewInflated = LayoutInflater.Inflate(Resource.Layout.entry_extrastring_title,null);
TextView keyView = (TextView)viewInflated;
if (key != null)
keyView.Text = key;
layout.AddView(keyView);
TextView valueView = (TextView)LayoutInflater.Inflate(Resource.Layout.entry_extrastring_value, null);
if (value != null)
valueView.Text = value;
SetPasswordTypeface(valueView);
if (isProtected)
RegisterProtectedTextView(valueView);
if ((int)Build.VERSION.SdkInt >= 11)
valueView.SetTextIsSelectable(true);
layout.AddView(valueView);
return layout;
}
private void RegisterProtectedTextView(TextView protectedTextView)
{
_protectedTextViews.Add(protectedTextView);
}
void PopulateBinaries(bool trimList)
{
ViewGroup binariesGroup = (ViewGroup)FindViewById(Resource.Id.binaries);
if (trimList)
{
binariesGroup.RemoveAllViews();
}
foreach (KeyValuePair<string, string> pair in new Dictionary<string, string>() { {"abc",""}, {"test.png","uia"} })
{
String key = pair.Key;
Button binaryButton = new Button(this);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
binaryButton.Text = key;
binaryButton.SetCompoundDrawablesWithIntrinsicBounds( Resources.GetDrawable(Android.Resource.Drawable.IcMenuSave),null, null, null);
binaryButton.Click += (sender, e) =>
{
Button btnSender = (Button)(sender);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.SetTitle(GetString(Resource.String.SaveAttachmentDialog_title));
builder.SetMessage(GetString(Resource.String.SaveAttachmentDialog_text));
builder.SetPositiveButton(GetString(Resource.String.SaveAttachmentDialog_save), (dlgSender, dlgEvt) =>
{
});
builder.SetNegativeButton(GetString(Resource.String.SaveAttachmentDialog_open), (dlgSender, dlgEvt) =>
{
});
Dialog dialog = builder.Create();
dialog.Show();
};
binariesGroup.AddView(binaryButton,layoutParams);
}
FindViewById(Resource.Id.entry_binaries_label).Visibility = true ? ViewStates.Visible : ViewStates.Gone;
}
// url = file path or whatever suitable URL you want.
public static String GetMimeType(String url)
{
String type = null;
String extension = MimeTypeMap.GetFileExtensionFromUrl(url);
if (extension != null) {
MimeTypeMap mime = MimeTypeMap.Singleton;
type = mime.GetMimeTypeFromExtension(extension);
}
return type;
}
public override void OnBackPressed()
{
base.OnBackPressed();
}
protected void FillData(bool trimList)
{
_protectedTextViews = new List<TextView>();
ImageView iv = (ImageView)FindViewById(Resource.Id.entry_icon);
if (iv != null)
{
iv.SetImageDrawable(Resources.GetDrawable(Resource.Drawable.ic00));
}
ActionBar.Title = "Entry title";
ActionBar.SetDisplayHomeAsUpEnabled(true);
PopulateText(Resource.Id.entry_user_name, Resource.Id.entry_user_name_label, "user name");
PopulateText(Resource.Id.entry_url, Resource.Id.entry_url_label, "www.google.com");
PopulateText(Resource.Id.entry_password, Resource.Id.entry_password_label, "my password");
RegisterProtectedTextView(FindViewById<TextView>(Resource.Id.entry_password));
SetPasswordTypeface(FindViewById<TextView>(Resource.Id.entry_password));
PopulateText(Resource.Id.entry_created, Resource.Id.entry_created_label, getDateTime(DateTime.Now));
PopulateText(Resource.Id.entry_modified, Resource.Id.entry_modified_label, getDateTime(DateTime.Now));
if (true)
{
FindViewById(Resource.Id.entry_expires).Visibility = ViewStates.Visible;
FindViewById(Resource.Id.entry_expires_label).Visibility = ViewStates.Visible;
PopulateText(Resource.Id.entry_expires, Resource.Id.entry_expires_label, getDateTime(DateTime.Now));
}
else
{
FindViewById(Resource.Id.entry_expires).Visibility = ViewStates.Gone;
FindViewById(Resource.Id.entry_expires_label).Visibility = ViewStates.Gone;
}
PopulateText(Resource.Id.entry_comment, Resource.Id.entry_comment_label, "some text about this entry");
PopulateText(Resource.Id.entry_tags, Resource.Id.entry_tags_label, "bla; blubb; blablubb");
PopulateExtraStrings(trimList);
PopulateBinaries(trimList);
SetPasswordStyle();
}
private void SetPasswordTypeface(TextView textView)
{
}
private void PopulateText(int viewId, int headerViewId,int resId) {
View header = FindViewById(headerViewId);
TextView tv = (TextView)FindViewById(viewId);
header.Visibility = tv.Visibility = ViewStates.Visible;
tv.SetText (resId);
}
private void PopulateText(int viewId, int headerViewId, String text)
{
View header = FindViewById(headerViewId);
TextView tv = (TextView)FindViewById(viewId);
if (String.IsNullOrEmpty(text))
{
header.Visibility = tv.Visibility = ViewStates.Gone;
}
else
{
header.Visibility = tv.Visibility = ViewStates.Visible;
tv.Text = text;
}
}
void RequiresRefresh ()
{
Intent ret = new Intent ();
ret.PutExtra (KeyRefreshPos, _pos);
}
private void SetPasswordStyle() {
foreach (TextView password in _protectedTextViews)
{
if (_showPassword)
{
password.TransformationMethod = null;
}
else
{
password.TransformationMethod = PasswordTransformationMethod.Instance;
}
}
}
protected override void OnResume()
{
base.OnResume();
}
/// <summary>
/// brings up a dialog asking the user whether he wants to add the given URL to the entry for automatic finding
/// </summary>
public void AskAddUrlThenCompleteCreate(string url)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.SetTitle(GetString(Resource.String.AddUrlToEntryDialog_title));
builder.SetMessage(GetString(Resource.String.AddUrlToEntryDialog_text, new Java.Lang.Object[] { url } ));
builder.SetPositiveButton(GetString(Resource.String.yes), (dlgSender, dlgEvt) =>
{
});
builder.SetNegativeButton(GetString(Resource.String.no), (dlgSender, dlgEvt) =>
{
CompleteOnCreate();
});
Dialog dialog = builder.Create();
dialog.Show();
}
}
}

View File

@ -0,0 +1,64 @@
/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
Keepass2Android is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Keepass2Android is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using Android.Content;
using Android.Util;
using Android.Views;
using Android.Widget;
using System;
using Android.Runtime;
using PluginHostTest;
namespace keepass2android.view
{
public class EntryContentsView : LinearLayout {
public EntryContentsView (IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
public EntryContentsView(Context context):base(context, null) {
InflateView();
}
public EntryContentsView(Context context, IAttributeSet attrs): base(context, attrs) {
InflateView();
}
private void InflateView() {
LayoutInflater inflater = (LayoutInflater) Context.GetSystemService(Context.LayoutInflaterService);
inflater.Inflate(Resource.Layout.entry_view_contents, this);
}
/*
* doesn't compile with mono for android
*
protected override LayoutParams GenerateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.FillParent, LayoutParams.WrapContent);
}
*/
}
}

View File

@ -0,0 +1,75 @@
/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
Keepass2Android is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Keepass2Android is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Util;
using PluginHostTest;
namespace keepass2android.view
{
public class EntrySection : LinearLayout {
public EntrySection(Context context): base(context, null) {
InflateView (null, null);
}
public EntrySection(Context context, IAttributeSet attrs): base(context, attrs) {
InflateView (null, null);
}
public EntrySection(Context context, IAttributeSet attrs, String title, String value): base(context, attrs) {
InflateView(title, value);
}
public EntrySection (IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
private void InflateView(String title, String value) {
LayoutInflater inflater = (LayoutInflater) Context.GetSystemService(Context.LayoutInflaterService);
inflater.Inflate(Resource.Layout.entry_section, this);
SetText(Resource.Id.title, title);
FindViewById<TextView>(Resource.Id.value).Invalidate();
SetText(Resource.Id.value, value);
//TODO: this seems to cause a bug when rotating the device (and the activity gets destroyed)
//After recreating the activity, the value fields all have the same content.
if ((int)Android.OS.Build.VERSION.SdkInt >= 11)
FindViewById<TextView>(Resource.Id.value).SetTextIsSelectable(true);
}
private void SetText(int resId, String str) {
if (str != null) {
TextView tvTitle = (TextView) FindViewById(resId);
tvTitle.Text = str;
}
}
}
}

View File

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.Content.Res;
using Android.Graphics;
using Android.OS;
using Android.Runtime;
using Android.Text;
using Android.Text.Method;
using Android.Text.Style;
using Android.Text.Util;
using Android.Util;
using Android.Views;
using Android.Widget;
using PluginHostTest;
namespace keepass2android.views
{
public class Kp2aShortHelpView: TextView
{
private string _helpText;
private static Typeface _iconFont;
protected Kp2aShortHelpView(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
public Kp2aShortHelpView(Context context) : base(context)
{
}
public Kp2aShortHelpView(Context context, IAttributeSet attrs) : base(context, attrs)
{
Initialize(attrs);
}
public Kp2aShortHelpView(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs, defStyle)
{
Initialize(attrs);
}
public string HelpText
{
get { return _helpText; }
set { _helpText = value;
UpdateView();
}
}
private void UpdateView()
{
if (!String.IsNullOrEmpty(_helpText))
{
Text = "i";
Clickable = true;
MovementMethod = LinkMovementMethod.Instance;
Click += (sender, args) =>
{
new AlertDialog.Builder(Context)
.SetTitle("PluginHostTest")
.SetMessage(_helpText)
.SetPositiveButton(Android.Resource.String.Ok, (o, eventArgs) => { })
.Show();
};
Visibility = ViewStates.Visible;
}
else
{
Visibility = ViewStates.Gone;
}
}
void Initialize(IAttributeSet attrs)
{
TypedArray a = Context.ObtainStyledAttributes(
attrs,
Resource.Styleable.Kp2aShortHelpView);
HelpText = a.GetString(Resource.Styleable.Kp2aShortHelpView_help_text);
}
}
}

View File

@ -0,0 +1,113 @@
using Android.Widget;
using Android.Content;
using Android.Views;
using System.Collections.Generic;
using Android.App;
using PluginHostTest;
namespace keepass2android
{
public class PluginItem
{
private readonly string _package;
public PluginItem(string package, string _label, int _icon, string _version, string _enabledStatus)
{
_package = package;
Label = _label;
Icon = _icon;
Version = _version;
EnabledStatus = _enabledStatus;
}
public string Label
{
get;
set;
}
public string Version
{
get;
set;
}
public string EnabledStatus
{
get;
set;
}
public int Icon
{
get;
set;
}
public string Package
{
get { return _package; }
}
}
public class PluginArrayAdapter : ArrayAdapter<PluginItem>
{
class PluginViewHolder : Java.Lang.Object
{
public ImageView imgIcon;
public TextView txtTitle;
public TextView txtVersion;
public TextView txtEnabledStatus;
}
Context context;
int layoutResourceId;
IList<PluginItem> data = null;
public PluginArrayAdapter(Context context, int layoutResourceId, IList<PluginItem> data) :
base(context, layoutResourceId, data)
{
this.layoutResourceId = layoutResourceId;
this.context = context;
this.data = data;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View row = convertView;
PluginViewHolder holder = null;
if (row == null)
{
LayoutInflater inflater = ((Activity)context).LayoutInflater;
row = inflater.Inflate(layoutResourceId, parent, false);
holder = new PluginViewHolder();
holder.imgIcon = (ImageView)row.FindViewById(Resource.Id.imgIcon);
holder.txtTitle = (TextView)row.FindViewById(Resource.Id.txtLabel);
holder.txtVersion = (TextView)row.FindViewById(Resource.Id.txtVersion);
holder.txtEnabledStatus = (TextView)row.FindViewById(Resource.Id.txtStatus);
row.Tag = holder;
}
else
{
holder = (PluginViewHolder)row.Tag;
}
var item = data[position];
holder.txtTitle.Text = item.Label;
holder.txtVersion.Text = item.Version;
holder.txtEnabledStatus.Text = item.EnabledStatus;
holder.imgIcon.SetImageResource(item.Icon);
return row;
}
}
}

View File

@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Android.Content;
using Android.Util;
using Keepass2android.Pluginsdk;
namespace keepass2android
{
public class PluginDatabase
{
private const string _tag = "KP2A_PluginDatabase";
private readonly Context _ctx;
private const string _accessToken = "accessToken";
private const string _scopes = "scopes";
private const string _requesttoken = "requestToken";
private const string _pluginlist = "pluginList";
public PluginDatabase(Context ctx)
{
_ctx = ctx;
}
private ISharedPreferences GetPreferencesForPlugin(string packageName)
{
var prefs = _ctx.GetSharedPreferences("KP2A.Plugin." + packageName, FileCreationMode.Private);
if (prefs.GetString(_requesttoken, null) == null)
{
var editor = prefs.Edit();
editor.PutString(_requesttoken, Guid.NewGuid().ToString());
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();
}
}
return prefs;
}
/// <summary>
/// Returns the request token for the plugin. Request token is created of not yet available.
/// </summary>
/// <returns>Request token. Never null or empty.</returns>
public string GetRequestToken(string pkgName)
{
return GetPreferencesForPlugin(pkgName).GetString(_requesttoken, null);
}
public IList<string> GetPluginScopes(string pluginPackage)
{
var prefs = GetPreferencesForPlugin(pluginPackage);
return AccessManager.StringToStringArray(prefs.GetString(_scopes, ""));
}
public IEnumerable<String> GetAllPluginPackages()
{
var hostPrefs = GetHostPrefs();
return hostPrefs.GetStringSet(_pluginlist, new List<string>());
}
public bool IsEnabled(string pluginPackage)
{
return GetPreferencesForPlugin(pluginPackage).GetString(_accessToken, null) != null;
}
public void StorePlugin(string pluginPackage, string accessToken, IList<string> requestedScopes)
{
ISharedPreferences hostPrefs = GetHostPrefs();
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()
.PutString(_scopes, AccessManager.StringArrayToString(requestedScopes))
.PutString(_accessToken, accessToken)
.Commit();
}
private ISharedPreferences GetHostPrefs()
{
return _ctx.GetSharedPreferences("plugins", FileCreationMode.Private);
}
public void SetEnabled(string pluginPackage, bool enabled)
{
if (enabled)
{
string accessToken = Guid.NewGuid().ToString();
Intent i = new Intent(Strings.ActionReceiveAccess);
i.SetPackage(pluginPackage);
i.PutExtra(Strings.ExtraSender, _ctx.PackageName);
i.PutExtra(Strings.ExtraRequestToken, GetPreferencesForPlugin(pluginPackage).GetString(_requesttoken, null));
i.PutExtra(Strings.ExtraAccessToken, accessToken);
_ctx.SendBroadcast(i);
StorePlugin(pluginPackage, accessToken, GetPluginScopes( pluginPackage));
}
else
{
Intent i = new Intent(Strings.ActionRevokeAccess);
i.SetPackage(pluginPackage);
i.PutExtra(Strings.ExtraSender, _ctx.PackageName);
i.PutExtra(Strings.ExtraRequestToken, GetPreferencesForPlugin(pluginPackage).GetString(_requesttoken, null));
_ctx.SendBroadcast(i);
StorePlugin(pluginPackage, null, GetPluginScopes(pluginPackage));
}
}
public bool IsValidAccessToken(string pluginPackage, string accessToken, string scope)
{
var prefs = GetPreferencesForPlugin(pluginPackage);
if (prefs.GetString(_accessToken, null) != accessToken)
{
Log.Warn(_tag, "Invalid access token for " + pluginPackage);
return false;
}
if (!AccessManager.StringToStringArray(prefs.GetString(_scopes, "")).Contains(scope))
{
Log.Warn(_tag, "Scope " + scope + " not granted for " + pluginPackage);
return false;
}
return true;
}
public string GetAccessToken(string pluginPackage)
{
return GetPreferencesForPlugin(pluginPackage).GetString(_accessToken, null);
}
public void Clear()
{
foreach (string plugin in GetAllPluginPackages())
{
GetPreferencesForPlugin(plugin).Edit().Clear().Commit();
}
GetHostPrefs().Edit().Clear().Commit();
}
public IEnumerable<string> GetPluginsWithAcceptedScope(string scope)
{
return GetAllPluginPackages().Where(plugin =>
{
var prefs = GetPreferencesForPlugin(plugin);
return (prefs.GetString(_accessToken, null) != null)
&& AccessManager.StringToStringArray(prefs.GetString(_scopes, "")).Contains(scope);
});
}
public void ClearPlugin(string plugin)
{
var prefs = _ctx.GetSharedPreferences("KP2A.Plugin." + plugin, FileCreationMode.Private);
prefs.Edit().Clear().Commit();
}
}
}

View File

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Content.Res;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Widget;
using Java.Util;
using Keepass2android.Pluginsdk;
using keepass2android;
using keepass2android.views;
namespace PluginHostTest
{
[Activity(Label = "TODO Details")]
public class PluginDetailsActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
string pluginPackage = Intent.GetStringExtra("PluginPackage");
Intent mainIntent = new Intent(Intent.ActionMain, null);
mainIntent.AddCategory(Intent.CategoryLauncher);
IList<ResolveInfo> appList = PackageManager.QueryIntentActivities(mainIntent, 0);
//Collections.Sort(appList, new ResolveInfo.DisplayNameComparator(PackageManager));
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);
if (title != null)
FindViewById<TextView>(Resource.Id.txtLabel).Text = title;
FindViewById<TextView>(Resource.Id.txtVersion).Text = version;
SetTextOrHide(Resource.Id.txtAuthor, author);
SetTextOrHide(Resource.Id.txtShortDesc, shortDesc);
var checkbox = FindViewById<CheckBox>(Resource.Id.cb_enabled);
PluginDatabase pluginDb = new PluginDatabase(this);
checkbox.Checked = pluginDb.IsEnabled(pluginPackage);
checkbox.CheckedChange += delegate(object sender, CompoundButton.CheckedChangeEventArgs args)
{
pluginDb.SetEnabled(pluginPackage, checkbox.Checked);
};
Drawable d = PackageManager.GetApplicationIcon(pluginPackage);
FindViewById<ImageView>(Resource.Id.imgIcon).SetImageDrawable(d);
FindViewById<TextView>(Resource.Id.txtVersion).Text = version;
var scopesContainer = FindViewById<LinearLayout>(Resource.Id.scopes_list);
foreach (string scope in pluginDb.GetPluginScopes(pluginPackage))
{
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)));
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FillParent, ViewGroup.LayoutParams.WrapContent);
help.LayoutParameters = layoutParams;
scopesContainer.AddView(help);
}
}
private void SetTextOrHide(int resourceId, string text)
{
if (text != null)
{
FindViewById<TextView>(resourceId).Text = text;
FindViewById<TextView>(resourceId).Visibility = ViewStates.Visible;
}
else
FindViewById<TextView>(resourceId).Visibility = ViewStates.Gone;
}
private static string GetStringFromPlugin(Resources pluginRes, string pluginPackage, string stringId)
{
int titleId = pluginRes.GetIdentifier(pluginPackage + ":string/"+stringId, null, null);
string title = null;
if (titleId != 0)
title = pluginRes.GetString(titleId);
return title;
}
}
}

View File

@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Widget;
using Keepass2android;
using Keepass2android.Pluginsdk;
using PluginHostTest;
namespace keepass2android
{
[BroadcastReceiver()]
[IntentFilter(new[] { Strings.ActionRequestAccess})]
public class PluginHost: BroadcastReceiver
{
private const string _tag = "KP2A_PluginHost";
private static readonly string[] _validScopes = { Strings.ScopeDatabaseActions };
public static void TriggerRequests(Context ctx)
{
Intent accessIntent = new Intent(Strings.ActionTriggerRequestAccess);
PackageManager packageManager = ctx.PackageManager;
IList<ResolveInfo> dictPacks = packageManager.QueryBroadcastReceivers(
accessIntent, PackageInfoFlags.Receivers);
PluginDatabase pluginDatabase = new PluginDatabase(ctx);
foreach (ResolveInfo ri in dictPacks)
{
ApplicationInfo appInfo = ri.ActivityInfo.ApplicationInfo;
String pkgName = appInfo.PackageName;
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)
{
PluginDatabase pluginDb = new PluginDatabase(context);
if (intent.Action == Strings.ActionRequestAccess)
{
var senderPackage = intent.GetStringExtra(Strings.ExtraSender);
var requestToken = intent.GetStringExtra(Strings.ExtraRequestToken);
var requestedScopes = intent.GetStringArrayListExtra(Strings.ExtraScopes);
if (!AreScopesValid(requestedScopes))
{
return;
}
if (pluginDb.GetRequestToken(senderPackage) != requestToken)
{
Log.Warn(_tag, "Invalid requestToken!");
return;
}
string currentAccessToken = pluginDb.GetAccessToken(senderPackage);
if ((currentAccessToken != null)
&& (AccessManager.IsSubset(requestedScopes,
pluginDb.GetPluginScopes(senderPackage))))
{
//permission already there.
var i = new Intent(Strings.ActionReceiveAccess);
i.PutExtra(Strings.ExtraSender, context.PackageName);
i.PutExtra(Strings.ExtraAccessToken, currentAccessToken);
//TODO: Plugin should verify requestToken to make sure it doesn't receive accessTokens from malicious apps
i.PutExtra(Strings.ExtraRequestToken, requestToken);
i.SetPackage(senderPackage);
context.SendBroadcast(i);
Log.Debug(_tag, "Plugin " + senderPackage + " enabled.");
}
else
{
//store that scope was requested but not yet approved (=> accessToken = null)
pluginDb.StorePlugin(senderPackage, null, requestedScopes);
Log.Debug(_tag, "Plugin " + senderPackage + " not enabled.");
//see if the plugin has an access token
string accessToken = intent.GetStringExtra(Strings.ExtraAccessToken);
if (accessToken != null)
{
//notify plugin that access token is no longer valid or sufficient
Intent i = new Intent(Strings.ActionRevokeAccess);
i.PutExtra(Strings.ExtraSender, context.PackageName);
i.PutExtra(Strings.ExtraAccessToken, accessToken);
i.SetPackage(senderPackage);
context.SendBroadcast(i);
Log.Warn(_tag, "Access token of plugin " + senderPackage + " not (or no more) valid.");
}
}
}
}
private bool AreScopesValid(IList<string> requestedScopes)
{
foreach (string scope in requestedScopes)
{
if (!_validScopes.Contains(scope))
{
Log.Warn(_tag, "invalid scope: " + scope);
return false;
}
}
return true;
}
}
}

View File

@ -0,0 +1,578 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PluginHostTest</RootNamespace>
<AssemblyName>PluginHostTest</AssemblyName>
<FileAlignment>512</FileAlignment>
<AndroidApplication>true</AndroidApplication>
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<AndroidUseLatestPlatformSdk />
<TargetFrameworkVersion>v4.4</TargetFrameworkVersion>
<AndroidSupportedAbis>armeabi-v7a</AndroidSupportedAbis>
<AndroidStoreUncompressedFileExtensions />
<MandroidI18n />
<JavaMaximumHeapSize />
<JavaOptions />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
<AndroidLinkMode>None</AndroidLinkMode>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="Mono.Android.Support.v4" />
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Activity1.cs" />
<Compile Include="ClickView.cs" />
<Compile Include="EntryActivity.cs" />
<Compile Include="EntryContentsView.cs" />
<Compile Include="EntrySection.cs" />
<Compile Include="Kp2aShortHelpView.cs" />
<Compile Include="PluginDatabase.cs" />
<Compile Include="PluginDetailsActivity.cs" />
<Compile Include="PluginArrayAdapter.cs" />
<Compile Include="PluginListActivity.cs" />
<Compile Include="PluginHost.cs" />
<Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TextViewSelect.cs" />
<Compile Include="TextWithHelp.cs" />
</ItemGroup>
<ItemGroup>
<TransformFile Include="Assets\DejaVuSansMono.ttf" />
<TransformFile Include="Assets\fontawesome-webfont.ttf" />
<None Include="Resources\AboutResources.txt" />
<None Include="Assets\AboutAssets.txt" />
<AndroidResource Include="Resources\Layout\plugin_list.xml">
<SubType>AndroidResource</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\Main.axml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values\Strings2.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\Icon.png" />
</ItemGroup>
<ItemGroup>
<TransformFile Include="Properties\AndroidManifest.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PluginSdkBinding\PluginSdkBinding.csproj">
<Project>{3da3911e-36de-465e-8f15-f1991b6437e5}</Project>
<Name>PluginSdkBinding</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\ListViewPluginRow.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\plugin_details.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\text_with_help.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values\config2.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values\attrs.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values\colors.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values\config.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values\dimens.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values\styles.xml">
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values\strings.xml">
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\BlueButton.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values-v14\styles_light.xml">
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values-v14\colors.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values-v14\styles.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values-v14\styles_dark.xml">
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\btn_new_group.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\btn_new_group_dark.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\btn_new_group_holodark.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\collections_collection.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\collections_new_label.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\device_access_new_account.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\device_access_new_account_dark.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\device_access_not_secure.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\donate_cake.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\EntryFieldHeaderBackground.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\extra_string_header.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\GreenButton.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\HeaderButtonBackground.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_action_eye_open.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_action_search.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_action_search_holodark.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_keepass2android.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_keepass2android_nonet.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_launcher.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_launcher_folder_small.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_launcher_gray.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_launcher_gray_bday.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_launcher_offline.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_launcher_red.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_menu_add_field_holo_light.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_menu_add_field_holodark.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_menu_remove_field_holo_light.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_menu_remove_field_holodark.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_menu_view.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_storage_androidget.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_storage_androidsend.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_storage_dropbox.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_storage_dropboxKP2A.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_storage_file.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_storage_ftp.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_storage_gdrive.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_storage_http.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_storage_https.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_storage_sftp.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_storage_skydrive.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_unlocked_gray.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic00.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic01.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic02.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic03.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic04.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic05.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic06.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic07.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic08.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic09.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic10.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic11.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic12.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic13.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic14.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic15.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic16.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic17.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic18.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic19.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic20.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic21.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic22.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic23.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic24.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic25.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic26.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic27.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic28.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic29.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic30.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic31.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic32.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic33.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic34.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic35.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic36.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic37.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic38.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic39.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic40.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic41.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic42.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic43.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic44.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic45.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic46.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic47.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic48.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic49.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic50.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic51.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic52.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic53.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic54.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic55.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic56.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic57.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic58.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic59.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic60.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic61.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic62.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic63.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic64.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic65.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic66.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic67.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic68.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic99_blank.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\location_web_site.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\navigation_accept.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\navigation_accept_dark.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\navigation_cancel.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\navigation_cancel_holodark.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\navigation_previous_item.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\navigation_previous_item_dark.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\notify.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\notify_keyboard.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\oktoberfest.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\RedButton.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\section_header.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\transparent.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\YellowButton.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\collections_collection_holodark.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\collections_new_label_holodark.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\device_access_new_account_holodark.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\location_web_site_holodark.png" />
</ItemGroup>
<ItemGroup>
<TransformFile Include="Resources\Layout\entry_edit.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\entry_edit_section.xml">
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\entry_extrastring_title.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\entry_extrastring_value.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\entry_list_entry.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\entry_section.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\entry_view_test.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\entry_view_contents.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\entry_view.xml" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Linq;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Widget;
using PluginHostTest;
namespace keepass2android
{
//TODO theme?
[Activity (Label = "Plugins (TODO)", ConfigurationChanges=ConfigChanges.Orientation|ConfigChanges.KeyboardHidden )]
public class PluginListActivity : ListActivity
{
private PluginArrayAdapter _pluginArrayAdapter;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
//TODO _design.ApplyTheme();
SetContentView(Resource.Layout.plugin_list);
PluginDatabase pluginDb = new PluginDatabase(this);
List<PluginItem> items = (from pluginPackage in pluginDb.GetAllPluginPackages()
let version = PackageManager.GetPackageInfo(pluginPackage, 0).VersionName
let enabledStatus = pluginDb.IsEnabled(pluginPackage) ? GetString(Resource.String.plugin_enabled) : GetString(Resource.String.plugin_disabled)
select new PluginItem(pluginPackage, "the plugin", Resource.Drawable.Icon, version, enabledStatus)).ToList();
/*
{
new PluginItem("PluginA", Resource.Drawable.Icon, "keepass2android.plugina", "connected"),
new PluginItem("KeepassNFC", Resource.Drawable.Icon, "com.bla.blubb.plugina", "disconnected")
};
* */
_pluginArrayAdapter = new PluginArrayAdapter(this, Resource.Layout.ListViewPluginRow, items);
ListAdapter = _pluginArrayAdapter;
ListView listView = FindViewById<ListView>(Android.Resource.Id.List);
listView.ItemClick +=
(sender, args) =>
{
Intent i = new Intent(this, typeof(PluginDetailsActivity));
i.PutExtra("PluginPackage", items[args.Position].Package);
StartActivity(i);
};
// Create your application here
}
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-sdk android:minSdkVersion="12" android:targetSdkVersion="19" />
<application></application>
</manifest>

View File

@ -0,0 +1,34 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Android.App;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("PluginHostTest")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("PluginHostTest")]
[assembly: AssemblyCopyright("Copyright © 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
// Add some common permissions, these can be removed if not needed
[assembly: UsesPermission(Android.Manifest.Permission.Internet)]
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]

View File

@ -0,0 +1,50 @@
Images, layout descriptions, binary blobs and string dictionaries can be included
in your application as resource files. Various Android APIs are designed to
operate on the resource IDs instead of dealing with images, strings or binary blobs
directly.
For example, a sample Android app that contains a user interface layout (main.xml),
an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
would keep its resources in the "Resources" directory of the application:
Resources/
drawable-hdpi/
icon.png
drawable-ldpi/
icon.png
drawable-mdpi/
icon.png
layout/
main.xml
values/
strings.xml
In order to get the build system to recognize Android resources, set the build action to
"AndroidResource". The native Android APIs do not operate directly with filenames, but
instead operate on resource IDs. When you compile an Android application that uses resources,
the build system will package the resources for distribution and generate a class called
"Resource" that contains the tokens for each one of the resources included. For example,
for the above Resources layout, this is what the Resource class would expose:
public class Resource {
public class drawable {
public const int icon = 0x123;
}
public class layout {
public const int main = 0x456;
}
public class strings {
public const int first_string = 0xabc;
public const int second_string = 0xbcd;
}
}
You would then use R.drawable.icon to reference the drawable/icon.png file, or Resource.layout.main
to reference the layout/main.xml file, or Resource.strings.first_string to reference the first
string in the dictionary file values/strings.xml.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" >
<shape>
<solid
android:color="#449def" />
<stroke
android:width="1dp"
android:color="#2f6699" />
<corners
android:radius="3dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
</item>
<item>
<shape>
<gradient
android:startColor="#449def"
android:endColor="#2f6699"
android:angle="270" />
<stroke
android:width="1dp"
android:color="#2f6699" />
<corners
android:radius="4dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<gradient
android:angle="45"
android:startColor="#FF000030"
android:endColor="#FF000000"
android:type="linear" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" >
<shape>
<solid
android:color="#70c656" />
<stroke
android:width="1dp"
android:color="#53933f" />
<corners
android:radius="3dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
</item>
<item>
<shape>
<gradient
android:startColor="#70c656"
android:endColor="#53933f"
android:angle="270" />
<stroke
android:width="1dp"
android:color="#53933f" />
<corners
android:radius="4dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@color/group_header_button_pressed"/> <!-- pressed -->
<item android:state_focused="true" android:drawable="@color/group_header_button_pressed"/> <!-- focused -->
<item android:drawable="@android:color/transparent"/> <!-- default -->
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" >
<shape>
<solid
android:color="#ef4444" />
<stroke
android:width="1dp"
android:color="#992f2f" />
<corners
android:radius="3dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
</item>
<item>
<shape>
<gradient
android:startColor="#ef4444"
android:endColor="#992f2f"
android:angle="270" />
<stroke
android:width="1dp"
android:color="#992f2f" />
<corners
android:radius="3dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true">
<shape>
<solid
android:color="#f3ae1b" />
<stroke
android:width="1dp"
android:color="#bb6008" />
<corners
android:radius="3dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
</item>
<item>
<shape>
<gradient
android:startColor="#f3ae1b"
android:endColor="#bb6008"
android:angle="270" />
<stroke
android:width="1dp"
android:color="#bb6008" />
<corners
android:radius="4dp" />
<padding
android:left="10dp"
android:top="10dp"
android:right="10dp"
android:bottom="10dp" />
</shape>
</item>
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="2px" />
<solid
android:color="@color/emphasis2"/>
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Some files were not shown because too many files have changed in this diff Show More