Merge branch 'pluginhosttest'

Conflicts:
	.gitignore
This commit is contained in:
Philipp Crocoll 2014-05-16 17:17:43 +02:00
commit 7a40368ed3
483 changed files with 27953 additions and 7162 deletions

4
.gitignore vendored
View File

@ -238,3 +238,7 @@ Thumbs.db
/src/java/JavaFileStorageTest/gen/com/google/android/gms/R.java
/src/java/JavaFileStorageTest/gen/group/pals/android/lib/ui/filechooser/R.java
/src/java/JavaFileStorageTest/gen/keepass2android/javafilestorage/R.java
/src/java/PluginQR/bin
/src/PluginHostTest/bin/Debug
/src/java/Keepass2AndroidPluginSDK/bin

View File

@ -37,7 +37,8 @@ namespace ArtTestApp
FindViewById<Button>(Resource.Id.MyButton2).Click += (sender, args) => StartActivityForResult(typeof(Activity2),1);
FindViewById<Button>(Resource.Id.MyButton3).Click += (sender, args) => StartActivityForResult(typeof(PrefActivity), 1);
StartActivity(typeof(Activity2));
Finish();
}
}

View File

@ -17,14 +17,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TwofishCipher", "TwofishCip
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JavaFileStorageBindings", "JavaFileStorageBindings\JavaFileStorageBindings.csproj", "{48574278-4779-4B3A-A9E4-9CF1BC285D0B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppCompatV7Binding", "AppCompatV7Binding\AppCompatV7Binding.csproj", "{23233A28-D74F-4BF8-B4D8-834060840BD7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AndroidFileChooserBinding", "AndroidFileChooserBinding\AndroidFileChooserBinding.csproj", "{3C0F7FE5-639F-4422-A087-8B26CF862D1B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KP2AKdbLibraryBinding", "KP2AKdbLibraryBinding\KP2AKdbLibraryBinding.csproj", "{70D3844A-D9FA-4A64-B205-A84C6A822196}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArtTestApp", "ArtTestApp\ArtTestApp.csproj", "{1FF6C335-A627-43C9-AAA7-CBAC2E74CD18}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginHostTest", "PluginHostTest\PluginHostTest.csproj", "{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginSdkBinding", "PluginSdkBinding\PluginSdkBinding.csproj", "{3DA3911E-36DE-465E-8F15-F1991B6437E5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -206,24 +208,6 @@ Global
{48574278-4779-4B3A-A9E4-9CF1BC285D0B}.ReleaseNoNet|Mixed Platforms.Build.0 = ReleaseNoNet|Any CPU
{48574278-4779-4B3A-A9E4-9CF1BC285D0B}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{48574278-4779-4B3A-A9E4-9CF1BC285D0B}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.Debug|Win32.ActiveCfg = Debug|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.Debug|x64.ActiveCfg = Debug|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.Release|Any CPU.Build.0 = Release|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.Release|Win32.ActiveCfg = Release|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.Release|x64.ActiveCfg = Release|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.ReleaseNoNet|Mixed Platforms.ActiveCfg = ReleaseNoNet|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.ReleaseNoNet|Mixed Platforms.Build.0 = ReleaseNoNet|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{23233A28-D74F-4BF8-B4D8-834060840BD7}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
{3C0F7FE5-639F-4422-A087-8B26CF862D1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3C0F7FE5-639F-4422-A087-8B26CF862D1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3C0F7FE5-639F-4422-A087-8B26CF862D1B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@ -284,6 +268,48 @@ Global
{1FF6C335-A627-43C9-AAA7-CBAC2E74CD18}.ReleaseNoNet|Mixed Platforms.Deploy.0 = Release|Any CPU
{1FF6C335-A627-43C9-AAA7-CBAC2E74CD18}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{1FF6C335-A627-43C9-AAA7-CBAC2E74CD18}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Debug|Mixed Platforms.Deploy.0 = Debug|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Debug|Win32.ActiveCfg = Debug|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Debug|x64.ActiveCfg = Debug|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Release|Any CPU.Build.0 = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Release|Any CPU.Deploy.0 = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Release|Mixed Platforms.Deploy.0 = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Release|Win32.ActiveCfg = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.Release|x64.ActiveCfg = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.ReleaseNoNet|Any CPU.Deploy.0 = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.ReleaseNoNet|Mixed Platforms.Deploy.0 = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Debug|Win32.ActiveCfg = Debug|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Debug|x64.ActiveCfg = Debug|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Release|Any CPU.Build.0 = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Release|Win32.ActiveCfg = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.Release|x64.ActiveCfg = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{3DA3911E-36DE-465E-8F15-F1991B6437E5}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -20,7 +20,7 @@
<DebugType>full</DebugType>
<Optimize>False</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;EXCLUDE_TWOFISH;INCLUDE_KEYBOARD;INCLUDE_FILECHOOSER;INCLUDE_JAVAFILESTORAGE;INCLUDE_KEYTRANSFORM</DefineConstants>
<DefineConstants>DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;EXCLUDE_KEYTRANSFORM</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>False</ConsolePause>

View File

@ -2,7 +2,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// Dieser Code wurde von einem Tool generiert.
// Laufzeitversion:4.0.30319.34011
// Laufzeitversion:4.0.30319.34014
//
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
// der Code erneut generiert wird.

View File

@ -20,7 +20,7 @@
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;EXCLUDE_TWOFISH;INCLUDE_KEYBOARD;INCLUDE_FILECHOOSER;INCLUDE_JAVAFILESTORAGE;INCLUDE_KEYTRANSFORM</DefineConstants>
<DefineConstants>TRACE;DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;EXCLUDE_KEYTRANSFORM</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
@ -57,6 +57,7 @@
<Compile Include="database\edit\MoveElement.cs" />
<Compile Include="database\KdbDatabaseLoader.cs" />
<Compile Include="database\KdbxDatabaseLoader.cs" />
<Compile Include="database\PwEntryOutput.cs" />
<Compile Include="database\SynchronizeCachedDatabase.cs" />
<Compile Include="DataExchange\FileFormatProvider.cs" />
<Compile Include="DataExchange\Formats\KeePassCsv1x.cs" />

View File

@ -45,6 +45,11 @@ namespace keepass2android
}
}
/// <summary>
/// Information about the last opened entry. Includes the entry but also transformed fields.
/// </summary>
public PwEntryOutput LastOpenedEntry { get; set; }
/// <summary>
/// if an OTP key was used, this property tells the location of the OTP auxiliary file.
/// Must be set after loading.

View File

@ -0,0 +1,60 @@
using System;
using KeePass.Util.Spr;
using KeePassLib;
using KeePassLib.Collections;
using KeePassLib.Security;
namespace keepass2android
{
/// <summary>
/// Represents the strings which are output from a PwEntry.
/// </summary>
/// In contrast to the original PwEntry, this means that placeholders are replaced. Also, plugins may modify
/// or add fields.
public class PwEntryOutput
{
private readonly PwEntry _entry;
private readonly PwDatabase _db;
private readonly ProtectedStringDictionary _outputStrings = new ProtectedStringDictionary();
/// <summary>
/// Constructs the PwEntryOutput by replacing the placeholders
/// </summary>
public PwEntryOutput(PwEntry entry, PwDatabase db)
{
_entry = entry;
_db = db;
foreach (var pair in entry.Strings)
{
_outputStrings.Set(pair.Key, new ProtectedString(entry.Strings.Get(pair.Key).IsProtected, GetStringAndReplacePlaceholders(pair.Key)));
}
}
string GetStringAndReplacePlaceholders(string key)
{
String value = Entry.Strings.ReadSafe(key);
value = SprEngine.Compile(value, new SprContext(Entry, _db, SprCompileFlags.All));
return value;
}
/// <summary>
/// Returns the ID of the entry
/// </summary>
public PwUuid Uuid
{
get { return Entry.Uuid; }
}
/// <summary>
/// The output strings for the represented entry
/// </summary>
public ProtectedStringDictionary OutputStrings { get { return _outputStrings; } }
public PwEntry Entry
{
get { return _entry; }
}
}
}

View File

@ -67,6 +67,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="TestIntentsAndBundles.cs" />
<Compile Include="ProgressDialogStub.cs" />
<Compile Include="TestBase.cs" />
<Compile Include="TestCacheSupervisor.cs" />
@ -85,6 +86,7 @@
<Compile Include="TestSynchronizeCachedDatabase.cs" />
</ItemGroup>
<ItemGroup>
<None Include="ClassDiagram1.cd" />
<None Include="Resources\AboutResources.txt" />
<None Include="Assets\AboutAssets.txt" />
</ItemGroup>

View File

@ -18,7 +18,8 @@ namespace Kp2aUnitTests
{
TestRunner runner = new TestRunner();
// Run all tests from this assembly
runner.AddTests(Assembly.GetExecutingAssembly());
//runner.AddTests(Assembly.GetExecutingAssembly());
runner.AddTests(new List<Type> { typeof(TestIntentsAndBundles) });
//runner.AddTests(new List<Type> { typeof(TestSynchronizeCachedDatabase)});
//runner.AddTests(typeof(TestLoadDb).GetMethod("LoadErrorWithCertificateTrustFailure"));
//runner.AddTests(typeof(TestLoadDb).GetMethod("LoadWithAcceptedCertificateTrustFailure"));

View File

@ -2,7 +2,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// Dieser Code wurde von einem Tool generiert.
// Laufzeitversion:4.0.30319.34011
// Laufzeitversion:4.0.30319.34014
//
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
// der Code erneut generiert wird.

View File

@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Java.IO;
using KeePassLib;
using KeePassLib.Interfaces;
using KeePassLib.Keys;
using KeePassLib.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using keepass2android;
namespace Kp2aUnitTests
{
[TestClass]
internal class TestIntentsAndBundles
{
[TestMethod]
public void StringArray()
{
string[] dataIn = new string[] { "a","bcd"};
Intent i= new Intent();
i.PutExtra("key", dataIn);
Bundle extras = i.Extras;
var dataOut = extras.GetStringArray("key");
Assert.AreEqual(dataIn.Length, dataOut.Length);
Assert.AreEqual(dataIn[0], dataOut[0]);
Assert.AreEqual(dataIn[1], dataOut[1]);
}
}
}

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!");
}
}
}

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

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

View File

@ -0,0 +1,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,35 @@
using System;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Widget;
using KeePassLib.Security;
namespace keepass2android
{
[Service]
public class CopyToClipboardService: Service
{
public CopyToClipboardService()
{
}
public CopyToClipboardService(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
public static void CopyValueToClipboardWithTimeout(Context ctx, string text)
{
Toast.MakeText(ctx, text, ToastLength.Short).Show();
}
public override IBinder OnBind(Intent intent)
{
return null;
}
}
}

View File

@ -0,0 +1,905 @@
/*
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 System.Threading;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Text;
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 KeePassLib;
using KeePassLib.Security;
using Keepass2android.Pluginsdk;
using PluginHostTest;
using Uri = Android.Net.Uri;
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";
protected PwEntry Entry = new PwEntry(true, true);
private static Typeface _passwordFont;
internal bool _showPassword;
private int _pos;
private List<TextView> _protectedTextViews;
private readonly Dictionary<string, List<IPopupMenuItem>> _popupMenuItems =
new Dictionary<string, List<IPopupMenuItem>>();
private readonly Dictionary<string, IStringView> _stringViews = new Dictionary<string, IStringView>();
private readonly List<PluginMenuOption> _pendingMenuOptions = new List<PluginMenuOption>();
private IMenu _menu;
protected void SetEntryView()
{
SetContentView(Resource.Layout.entry_view);
}
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;
}
}
private class PluginActionReceiver : BroadcastReceiver
{
private readonly EntryActivity _activity;
public PluginActionReceiver(EntryActivity activity)
{
_activity = activity;
}
public override void OnReceive(Context context, Intent intent)
{
var pluginPackage = intent.GetStringExtra(Strings.ExtraSender);
if (new PluginDatabase(context).IsValidAccessToken(pluginPackage,
intent.GetStringExtra(Strings.ExtraAccessToken),
Strings.ScopeCurrentEntry))
{
if (intent.GetStringExtra(Strings.ExtraEntryId) != _activity.Entry.Uuid.ToHexString())
{
Kp2aLog.Log("received action for wrong entry " + intent.GetStringExtra(Strings.ExtraEntryId));
return;
}
_activity.AddPluginAction(pluginPackage,
intent.GetStringExtra(Strings.ExtraFieldId),
intent.GetStringExtra(Strings.ExtraActionId),
intent.GetStringExtra(Strings.ExtraActionDisplayText),
intent.GetIntExtra(Strings.ExtraActionIconResId, -1),
intent.GetBundleExtra(Strings.ExtraActionData));
}
else
{
Kp2aLog.Log("received invalid request. Plugin not authorized.");
}
}
}
private class PluginFieldReceiver : BroadcastReceiver
{
private readonly EntryActivity _activity;
public PluginFieldReceiver(EntryActivity activity)
{
_activity = activity;
}
public override void OnReceive(Context context, Intent intent)
{
if (intent.GetStringExtra(Strings.ExtraEntryId) != _activity.Entry.Uuid.ToHexString())
{
Kp2aLog.Log("received field for wrong entry " + intent.GetStringExtra(Strings.ExtraEntryId));
return;
}
if (!new PluginDatabase(context).IsValidAccessToken(intent.GetStringExtra(Strings.ExtraSender),
intent.GetStringExtra(Strings.ExtraAccessToken),
Strings.ScopeCurrentEntry))
{
Kp2aLog.Log("received field with invalid access token from " + intent.GetStringExtra(Strings.ExtraSender));
return;
}
string key = intent.GetStringExtra(Strings.ExtraFieldId);
string value = intent.GetStringExtra(Strings.ExtraFieldValue);
bool isProtected = intent.GetBooleanExtra(Strings.ExtraFieldProtected, false);
_activity.SetPluginField(key, value, isProtected);
}
}
private void SetPluginField(string key, string value, bool isProtected)
{
//update or add the string view:
IStringView existingField;
if (_stringViews.TryGetValue(key, out existingField))
{
existingField.Text = value;
}
else
{
ViewGroup extraGroup = (ViewGroup) FindViewById(Resource.Id.extra_strings);
var view = CreateExtraSection(key, value, isProtected);
extraGroup.AddView(view.View);
}
//update the Entry output in the App database and notify the CopyToClipboard service
App.Kp2A.GetDb().LastOpenedEntry.OutputStrings.Set(key, new ProtectedString(isProtected, value));
Intent updateKeyboardIntent = new Intent(this, typeof(CopyToClipboardService));
Intent.SetAction(Intents.UpdateKeyboard);
updateKeyboardIntent.PutExtra(KeyEntry, Entry.Uuid.ToHexString());
StartService(updateKeyboardIntent);
//notify plugins
NotifyPluginsOnModification(Strings.PrefixString+key);
}
private void AddPluginAction(string pluginPackage, string fieldId, string popupItemId, string displayText, int iconId, Bundle bundleExtra)
{
if (fieldId != null)
{
try
{
//create a new popup item for the plugin action:
var newPopup = new PluginPopupMenuItem(this, pluginPackage, fieldId, popupItemId, displayText, iconId, bundleExtra);
//see if we already have a popup item for this field with the same item id
var popupsForField = _popupMenuItems[fieldId];
var popupItemPos = popupsForField.FindIndex(0,
item =>
(item is PluginPopupMenuItem) &&
((PluginPopupMenuItem)item).PopupItemId == popupItemId);
//replace existing or add
if (popupItemPos >= 0)
{
popupsForField[popupItemPos] = newPopup;
}
else
{
popupsForField.Add(newPopup);
}
}
catch (Exception e)
{
Kp2aLog.Log(e.ToString());
}
}
else
{
//we need to add an option to the menu.
//As it is not sure that OnCreateOptionsMenu was called yet, we cannot access _menu without a check:
Intent i = new Intent(Strings.ActionEntryActionSelected);
i.SetPackage(pluginPackage);
i.PutExtra(Strings.ExtraActionData, bundleExtra);
i.PutExtra(Strings.ExtraSender, PackageName);
PluginHost.AddEntryToIntent(i, App.Kp2A.GetDb().LastOpenedEntry);
var menuOption = new PluginMenuOption()
{
DisplayText = displayText,
Icon = PackageManager.GetResourcesForApplication(pluginPackage).GetDrawable(iconId),
Intent = i
};
if (_menu != null)
{
AddMenuOption(menuOption);
}
else
{
lock (_pendingMenuOptions)
{
_pendingMenuOptions.Add(menuOption);
}
}
}
}
private void AddMenuOption(PluginMenuOption menuOption)
{
var menuItem = _menu.Add(menuOption.DisplayText);
menuItem.SetIcon(menuOption.Icon);
menuItem.SetIntent(menuOption.Intent);
}
public override bool OnCreateOptionsMenu(IMenu menu)
{
_menu = menu;
base.OnCreateOptionsMenu(menu);
MenuInflater inflater = MenuInflater;
inflater.Inflate(Resource.Menu.entry, menu);
lock (_pendingMenuOptions)
{
foreach (var option in _pendingMenuOptions)
AddMenuOption(option);
_pendingMenuOptions.Clear();
}
UpdateTogglePasswordMenu();
IMenuItem gotoUrl = menu.FindItem(Resource.Id.menu_goto_url);
//Disabled IMenuItem copyUser = menu.FindItem(Resource.Id.menu_copy_user);
//Disabled IMenuItem copyPass = menu.FindItem(Resource.Id.menu_copy_pass);
// In API >= 11 onCreateOptionsMenu may be called before onCreate completes
// so _entry may not be set
if (Entry == null)
{
gotoUrl.SetVisible(false);
//Disabled copyUser.SetVisible(false);
//Disabled copyPass.SetVisible(false);
}
else
{
String url = Entry.Strings.ReadSafe(PwDefs.UrlField);
if (String.IsNullOrEmpty(url))
{
// disable button if url is not available
gotoUrl.SetVisible(false);
}
if (String.IsNullOrEmpty(Entry.Strings.ReadSafe(PwDefs.UserNameField)))
{
// disable button if username is not available
//Disabled copyUser.SetVisible(false);
}
if (String.IsNullOrEmpty(Entry.Strings.ReadSafe(PwDefs.PasswordField)))
{
// disable button if password is not available
//Disabled copyPass.SetVisible(false);
}
}
return true;
}
public override bool OnOptionsItemSelected(IMenuItem item)
{
//check if this is a plugin action
if ((item.Intent != null) && (item.Intent.Action == Strings.ActionEntryActionSelected))
{
//yes. let the plugin handle the click:
SendBroadcast(item.Intent);
return true;
}
switch (item.ItemId)
{
case Resource.Id.menu_donate:
try
{
// Util.GotoDonateUrl(this);
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.error_failed_to_launch_link, ToastLength.Long).Show();
return false;
}
return true;
case Resource.Id.menu_toggle_pass:
if (_showPassword)
{
item.SetTitle(Resource.String.show_password);
_showPassword = false;
}
else
{
item.SetTitle(Resource.String.menu_hide_password);
_showPassword = true;
}
SetPasswordStyle();
return true;
case Resource.Id.menu_goto_url:
string url = _stringViews[PwDefs.UrlField].Text;
if (url == null) return false;
// Default http:// if no protocol specified
if (!url.Contains("://"))
{
url = "http://" + url;
}
try
{
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
}
return true;
/* TODO: required?
case Resource.Id.menu_copy_user:
timeoutCopyToClipboard(_entry.Strings.ReadSafe (PwDefs.UserNameField));
return true;
case Resource.Id.menu_copy_pass:
timeoutCopyToClipboard(_entry.Strings.ReadSafe (PwDefs.UserNameField));
return true;
*/
case Resource.Id.menu_rate:
try
{
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
}
return true;
case Resource.Id.menu_suggest_improvements:
try
{
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
}
return true;
case Resource.Id.menu_lock:
return true;
case Resource.Id.menu_translate:
try
{
}
catch (ActivityNotFoundException)
{
Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show();
}
return true;
case Android.Resource.Id.Home:
//Currently the action bar only displays the home button when we come from a previous activity.
//So we can simply Finish. See this page for information on how to do this in more general (future?) cases:
//http://developer.android.com/training/implementing-navigation/ancestral.html
Finish();
return true;
}
return base.OnOptionsItemSelected(item);
}
protected override void OnCreate(Bundle savedInstanceState)
{
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));
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);
SetEntryView();
FillData();
SetupEditButtons();
App.Kp2A.GetDb().LastOpenedEntry = new PwEntryOutput(Entry, App.Kp2A.GetDb().KpDatabase);
RegisterReceiver(new PluginActionReceiver(this), new IntentFilter(Strings.ActionAddEntryAction));
RegisterReceiver(new PluginFieldReceiver(this), new IntentFilter(Strings.ActionSetEntryField));
new Thread(NotifyPluginsOnOpen).Start();
}
private void NotifyPluginsOnOpen()
{
App.Kp2A.GetDb().SetEntry(Entry);
Intent i = new Intent(Strings.ActionOpenEntry);
i.PutExtra(Strings.ExtraSender, PackageName);
AddEntryToIntent(i);
foreach (var plugin in new PluginDatabase(this).GetPluginsWithAcceptedScope(Strings.ScopeCurrentEntry))
{
i.SetPackage(plugin);
SendBroadcast(i);
}
}
private void NotifyPluginsOnModification(string fieldId)
{
Intent i = new Intent(Strings.ActionEntryOutputModified);
i.PutExtra(Strings.ExtraSender, PackageName);
i.PutExtra(Strings.ExtraFieldId, fieldId);
AddEntryToIntent(i);
foreach (var plugin in new PluginDatabase(this).GetPluginsWithAcceptedScope(Strings.ScopeCurrentEntry))
{
i.SetPackage(plugin);
SendBroadcast(i);
}
}
public void CompleteOnCreate()
{
}
private String getDateTime(DateTime dt)
{
return dt.ToString("g", CultureInfo.CurrentUICulture);
}
private 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();
}
private void PopulateExtraStrings()
{
ViewGroup extraGroup = (ViewGroup) FindViewById(Resource.Id.extra_strings);
foreach (var pair in Entry.Strings.Where(pair => !PwDefs.IsStandardField(pair.Key)).OrderBy(pair => pair.Key))
{
var stringView = CreateExtraSection(pair.Key, pair.Value.ReadString(), pair.Value.IsProtected);
extraGroup.AddView(stringView.View);
}
}
private ExtraStringView CreateExtraSection(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);
layout.LayoutParameters = layoutParams;
View viewInflated = LayoutInflater.Inflate(Resource.Layout.entry_extrastring_title, null);
TextView keyView = viewInflated.FindViewById<TextView>(Resource.Id.entry_title);
if (key != null)
keyView.Text = key;
layout.AddView(viewInflated);
RelativeLayout valueViewContainer =
(RelativeLayout) LayoutInflater.Inflate(Resource.Layout.entry_extrastring_value, null);
var valueView = valueViewContainer.FindViewById<TextView>(Resource.Id.entry_extra);
if (value != null)
valueView.Text = value;
SetPasswordTypeface(valueView);
if (isProtected)
{
RegisterProtectedTextView(valueView);
valueView.TransformationMethod = PasswordTransformationMethod.Instance;
}
layout.AddView(valueViewContainer);
var stringView = new ExtraStringView(layout, valueView, keyView);
_stringViews.Add(key, stringView);
RegisterTextPopup(valueViewContainer, valueViewContainer.FindViewById(Resource.Id.extra_vdots), key, isProtected);
return stringView;
}
private List<IPopupMenuItem> RegisterPopup(string popupKey, View clickView, View anchorView)
{
clickView.Click += (sender, args) =>
{
ShowPopup(anchorView, popupKey);
};
_popupMenuItems[popupKey] = new List<IPopupMenuItem>();
return _popupMenuItems[popupKey];
}
private void RegisterProtectedTextView(TextView protectedTextView)
{
_protectedTextViews.Add(protectedTextView);
}
private void PopulateBinaries()
{
ViewGroup binariesGroup = (ViewGroup) FindViewById(Resource.Id.binaries);
foreach (KeyValuePair<string, string> pair in new Dictionary<string, string>())
{
String key = pair.Key;
RelativeLayout valueViewContainer =
(RelativeLayout) LayoutInflater.Inflate(Resource.Layout.entry_extrastring_value, null);
var valueView = valueViewContainer.FindViewById<TextView>(Resource.Id.entry_extra);
if (key != null)
valueView.Text = key;
string popupKey = Strings.PrefixBinary + key;
var itemList = RegisterPopup(popupKey, valueViewContainer, valueViewContainer.FindViewById(Resource.Id.extra_vdots));
itemList.Add(new WriteBinaryToFilePopupItem(key, this));
itemList.Add(new OpenBinaryPopupItem(key, this));
binariesGroup.AddView(valueViewContainer);
/*
Button binaryButton = new Button(this);
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()
{
_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);
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_password, Resource.Id.entryfield_container_password, PwDefs.PasswordField);
RegisterProtectedTextView(FindViewById<TextView>(Resource.Id.entry_password));
SetPasswordTypeface(FindViewById<TextView>(Resource.Id.entry_password));
RegisterTextPopup(FindViewById<RelativeLayout>(Resource.Id.username_container),
FindViewById(Resource.Id.username_vdots), PwDefs.UserNameField);
RegisterTextPopup(FindViewById<RelativeLayout>(Resource.Id.url_container),
FindViewById(Resource.Id.url_vdots), PwDefs.UrlField)
.Add(new GotoUrlMenuItem(this));
RegisterTextPopup(FindViewById<RelativeLayout>(Resource.Id.password_container),
FindViewById(Resource.Id.password_vdots), PwDefs.PasswordField);
PopulateText(Resource.Id.entry_created, Resource.Id.entryfield_container_created, getDateTime(Entry.CreationTime));
PopulateText(Resource.Id.entry_modified, Resource.Id.entryfield_container_modified, getDateTime(Entry.LastModificationTime));
if (Entry.Expires)
{
PopulateText(Resource.Id.entry_expires, Resource.Id.entryfield_container_expires, getDateTime(Entry.ExpiryTime));
}
else
{
PopulateText(Resource.Id.entry_expires, Resource.Id.entryfield_container_expires, null);
}
PopulateStandardText(Resource.Id.entry_comment, Resource.Id.entryfield_container_comment, PwDefs.NotesField);
PopulateText(Resource.Id.entry_tags, Resource.Id.entryfield_container_tags, concatTags(Entry.Tags));
PopulateText(Resource.Id.entry_override_url, Resource.Id.entryfield_container_overrideurl, Entry.OverrideUrl);
PopulateExtraStrings();
PopulateBinaries();
SetPasswordStyle();
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (resultCode == /*TODO*/ 0)
{
if (resultCode == /*TODO*/ 0)
{
RequiresRefresh();
}
Recreate();
}
}
protected override void OnDestroy()
{
NotifyPluginsOnClose();
base.OnDestroy();
}
private void NotifyPluginsOnClose()
{
Intent i = new Intent(Strings.ActionCloseEntryView);
i.PutExtra(Strings.ExtraSender, PackageName);
foreach (var plugin in new PluginDatabase(this).GetPluginsWithAcceptedScope(Strings.ScopeCurrentEntry))
{
i.SetPackage(plugin);
SendBroadcast(i);
}
}
private List<IPopupMenuItem> RegisterTextPopup(View container, View anchor, string fieldKey)
{
return RegisterTextPopup(container, anchor, fieldKey, Entry.Strings.GetSafe(fieldKey).IsProtected);
}
private List<IPopupMenuItem> RegisterTextPopup(View container, View anchor, string fieldKey, bool isProtected)
{
string popupKey = Strings.PrefixString + fieldKey;
var popupItems = RegisterPopup(
popupKey,
container,
anchor);
popupItems.Add(new CopyToClipboardPopupMenuIcon(this, _stringViews[fieldKey]));
if (isProtected)
popupItems.Add(new ToggleVisibilityPopupMenuItem(this));
return popupItems;
}
private void ShowPopup(View anchor, string popupKey)
{
//PopupMenu popupMenu = new PopupMenu(this, FindViewById(Resource.Id.entry_user_name));
PopupMenu popupMenu = new PopupMenu(this, anchor);
AccessManager.PreparePopup(popupMenu);
int itemId = 0;
foreach (IPopupMenuItem popupItem in _popupMenuItems[popupKey])
{
popupMenu.Menu.Add(0, itemId, 0, popupItem.Text)
.SetIcon(popupItem.Icon);
itemId++;
}
popupMenu.MenuItemClick += delegate(object sender, PopupMenu.MenuItemClickEventArgs args)
{
_popupMenuItems[popupKey][args.Item.ItemId].HandleClick();
};
popupMenu.Show();
}
private void ShowPopup(int resAnchor, string popupKey)
{
ShowPopup(FindViewById(resAnchor), popupKey);
}
private void SetPasswordTypeface(TextView textView)
{
}
private void PopulateText(int viewId, int containerViewId, int resId)
{
View header = FindViewById(containerViewId);
TextView tv = (TextView) FindViewById(viewId);
header.Visibility = tv.Visibility = ViewStates.Visible;
tv.SetText(resId);
}
private void PopulateText(int viewId, int containerViewId, String text)
{
View container = FindViewById(containerViewId);
TextView tv = (TextView) FindViewById(viewId);
if (String.IsNullOrEmpty(text))
{
container.Visibility = tv.Visibility = ViewStates.Gone;
}
else
{
container.Visibility = tv.Visibility = ViewStates.Visible;
tv.Text = text;
}
}
private void PopulateStandardText(int viewId, int containerViewId, String key)
{
PopulateText(viewId, containerViewId, Entry.Strings.ReadSafe(key));
_stringViews.Add(key, new StandardStringView(viewId, containerViewId, this));
}
private 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();
}
public void ToggleVisibility()
{
_showPassword = !_showPassword;
SetPasswordStyle();
UpdateTogglePasswordMenu();
}
public Android.Net.Uri WriteBinaryToFile(string key, bool writeToCacheDirectory)
{
return Android.Net.Uri.Empty;
//TODO
}
private void UpdateTogglePasswordMenu()
{
//todo use real method
}
public void GotoUrl()
{
//TODO
}
public void OpenBinaryFile(Uri newUri)
{
Toast.MakeText(this, "opening file TODO", ToastLength.Short).Show();
}
public void AddEntryToIntent(Intent intent)
{
PluginHost.AddEntryToIntent(intent, App.Kp2A.GetDb().LastOpenedEntry);
}
}
}

View File

@ -0,0 +1,41 @@
using Android.Content;
using Android.Graphics.Drawables;
using PluginHostTest;
namespace keepass2android
{
/// <summary>
/// Reperesents the popup menu item in EntryActivity to copy a string to clipboard
/// </summary>
class CopyToClipboardPopupMenuIcon : IPopupMenuItem
{
private readonly Context _context;
private readonly IStringView _stringView;
public CopyToClipboardPopupMenuIcon(Context context, IStringView stringView)
{
_context = context;
_stringView = stringView;
}
public Drawable Icon
{
get
{
return _context.Resources.GetDrawable(Resource.Drawable.ic_menu_copy_holo_light);
}
}
public string Text
{
//TODO localize
get { return "Copy to clipboard"; }
}
public void HandleClick()
{
CopyToClipboardService.CopyValueToClipboardWithTimeout(_context, _stringView.Text);
}
}
}

View File

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

View File

@ -0,0 +1,34 @@
using Android.Graphics.Drawables;
using PluginHostTest;
namespace keepass2android
{
/// <summary>
/// Reperesents the popup menu item in EntryActivity to go to the URL in the field
/// </summary>
class GotoUrlMenuItem : IPopupMenuItem
{
private readonly EntryActivity _ctx;
public GotoUrlMenuItem(EntryActivity ctx)
{
_ctx = ctx;
}
public Drawable Icon
{
get { return _ctx.Resources.GetDrawable(Android.Resource.Drawable.IcMenuUpload); }
}
public string Text
{
get { return _ctx.Resources.GetString(Resource.String.menu_url); }
}
public void HandleClick()
{
//TODO
_ctx.GotoUrl();
}
}
}

View File

@ -0,0 +1,17 @@
using System;
using Android.Graphics.Drawables;
using KeePassLib;
namespace keepass2android
{
/// <summary>
/// Interface for popup menu items in EntryActivity
/// </summary>
internal interface IPopupMenuItem
{
Drawable Icon { get; }
String Text { get; }
void HandleClick();
}
}

View File

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

View File

@ -0,0 +1,40 @@
using Android.Graphics.Drawables;
using PluginHostTest;
namespace keepass2android
{
/// <summary>
/// Represents the popup menu item in EntryActivity to open the associated attachment
/// </summary>
internal class OpenBinaryPopupItem : IPopupMenuItem
{
private readonly string _key;
private readonly EntryActivity _entryActivity;
public OpenBinaryPopupItem(string key, EntryActivity entryActivity)
{
_key = key;
_entryActivity = entryActivity;
}
public Drawable Icon
{
get { return _entryActivity.Resources.GetDrawable(Android.Resource.Drawable.IcMenuShare); }
}
public string Text
{
get { return _entryActivity.Resources.GetString(Resource.String.SaveAttachmentDialog_open); }
}
public void HandleClick()
{
Android.Net.Uri newUri = _entryActivity.WriteBinaryToFile(_key, true);
if (newUri != null)
{
_entryActivity.OpenBinaryFile(newUri);
}
}
}
}

View File

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

View File

@ -0,0 +1,59 @@
using Android.Content;
using Android.Graphics.Drawables;
using Android.OS;
using Keepass2android.Pluginsdk;
namespace keepass2android
{
/// <summary>
/// Represents a popup menu item in EntryActivity which was added by a plugin. The click will therefore broadcast to the plugin.
/// </summary>
class PluginPopupMenuItem : IPopupMenuItem
{
private readonly EntryActivity _activity;
private readonly string _pluginPackage;
private readonly string _fieldId;
private readonly string _popupItemId;
private readonly string _displayText;
private readonly int _iconId;
private readonly Bundle _bundleExtra;
public PluginPopupMenuItem(EntryActivity activity, string pluginPackage, string fieldId, string popupItemId, string displayText, int iconId, Bundle bundleExtra)
{
_activity = activity;
_pluginPackage = pluginPackage;
_fieldId = fieldId;
_popupItemId = popupItemId;
_displayText = displayText;
_iconId = iconId;
_bundleExtra = bundleExtra;
}
public Drawable Icon
{
get { return _activity.PackageManager.GetResourcesForApplication(_pluginPackage).GetDrawable(_iconId); }
}
public string Text
{
get { return _displayText; }
}
public string PopupItemId
{
get { return _popupItemId; }
}
public void HandleClick()
{
Intent i = new Intent(Strings.ActionEntryActionSelected);
i.SetPackage(_pluginPackage);
i.PutExtra(Strings.ExtraActionData, _bundleExtra);
i.PutExtra(Strings.ExtraFieldId, _fieldId);
i.PutExtra(Strings.ExtraSender, _activity.PackageName);
_activity.AddEntryToIntent(i);
_activity.SendBroadcast(i);
}
}
}

View File

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

View File

@ -0,0 +1,46 @@
using Android.Graphics.Drawables;
using PluginHostTest;
namespace keepass2android
{
/// <summary>
/// Reperesents the popup menu item in EntryActivity to toggle visibility of all protected strings (e.g. Password)
/// </summary>
class ToggleVisibilityPopupMenuItem : IPopupMenuItem
{
private readonly EntryActivity _activity;
public ToggleVisibilityPopupMenuItem(EntryActivity activity)
{
_activity = activity;
}
public Drawable Icon
{
get
{
//return new TextDrawable("\uF06E", _activity);
return _activity.Resources.GetDrawable(Resource.Drawable.ic_action_eye_open);
}
}
public string Text
{
get
{
return _activity.Resources.GetString(
_activity._showPassword ?
Resource.String.menu_hide_password
: Resource.String.show_password);
}
}
public void HandleClick()
{
_activity.ToggleVisibility();
}
}
}

View File

@ -0,0 +1,35 @@
using Android.Graphics.Drawables;
using PluginHostTest;
namespace keepass2android
{
/// <summary>
/// Represents the popup menu item in EntryActivity to store the binary attachment on SD card
/// </summary>
internal class WriteBinaryToFilePopupItem : IPopupMenuItem
{
private readonly string _key;
private readonly EntryActivity _activity;
public WriteBinaryToFilePopupItem(string key, EntryActivity activity)
{
_key = key;
_activity = activity;
}
public Drawable Icon
{
get { return _activity.Resources.GetDrawable(Android.Resource.Drawable.IcMenuSave); }
}
public string Text
{
get { return _activity.Resources.GetString(Resource.String.SaveAttachmentDialog_save); }
}
public void HandleClick()
{
_activity.WriteBinaryToFile(_key, false);
}
}
}

View File

@ -15,41 +15,49 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using Android.Content;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Widget;
using Android.Util;
using System;
using Android.Runtime;
using PluginHostTest;
namespace keepass2android.view
{
public class GroupHeaderView : RelativeLayout {
public class EntryContentsView : LinearLayout {
public GroupHeaderView (IntPtr javaReference, JniHandleOwnership transfer)
public EntryContentsView (IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
public GroupHeaderView(Context context) :base(context) {
public EntryContentsView(Context context):base(context, null) {
InflateView();
}
public GroupHeaderView(Context context, IAttributeSet attrs):base(context,attrs) {
public EntryContentsView(Context context, IAttributeSet attrs): base(context, attrs) {
InflateView();
}
private void InflateView() {
LayoutInflater inflater = (LayoutInflater) Context.GetSystemService(Context.LayoutInflaterService);
inflater.Inflate(Resource.Layout.group_header, this);
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,126 @@
using Android.Content.PM;
using Android.Content.Res;
using Android.Graphics.Drawables;
using Android.Widget;
using Android.Content;
using Android.Views;
using System.Collections.Generic;
using Android.App;
using PluginHostTest;
namespace keepass2android
{
/// <summary>
/// Represents information about a plugin for display in the plugin list activity
/// </summary>
public class PluginItem
{
private readonly string _package;
private readonly Context _ctx;
private readonly Resources _pluginRes;
public PluginItem(string package, string enabledStatus, Context ctx)
{
_package = package;
_ctx = ctx;
EnabledStatus = enabledStatus;
_pluginRes = _ctx.PackageManager.GetResourcesForApplication(_package);
}
public string Label
{
get
{
return PluginDetailsActivity.GetStringFromPlugin(_pluginRes, _package, "kp2aplugin_title");
}
}
public string Version
{
get
{
return _ctx.PackageManager.GetPackageInfo(_package, 0).VersionName;
}
}
public string EnabledStatus
{
get;
set;
}
public Drawable Icon
{
get
{
return _ctx.PackageManager.GetApplicationIcon(_package);
}
}
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.SetImageDrawable(item.Icon);
return row;
}
}
}

View File

@ -0,0 +1,190 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Android.Content;
using Android.Content.PM;
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>()).Where(IsPackageInstalled);
}
public bool IsPackageInstalled(string targetPackage)
{
try
{
PackageInfo info = _ctx.PackageManager.GetPackageInfo(targetPackage, PackageInfoFlags.MetaData);
}
catch (PackageManager.NameNotFoundException e)
{
return false;
}
return true;
}
public bool IsEnabled(string pluginPackage)
{
return GetPreferencesForPlugin(pluginPackage).GetString(_accessToken, null) != null;
}
public void StorePlugin(string pluginPackage, string accessToken, IList<string> requestedScopes)
{
ISharedPreferences pluginPrefs = GetPreferencesForPlugin(pluginPackage);
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)
{
if (pluginPackage == null)
{
Log.Warn(_tag, "No pluginPackage specified!");
return false;
}
if (accessToken == null)
{
Log.Warn(_tag, "No accessToken specified!");
return false;
}
var prefs = GetPreferencesForPlugin(pluginPackage);
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,152 @@
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")]
[IntentFilter(new[] { Strings.ActionEditPluginSettings},
Label = AppNames.AppName,
Categories = new[] { Intent.CategoryDefault })]
public class PluginDetailsActivity : Activity
{
private CheckBox _checkbox;
private string _pluginPackageName;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
_pluginPackageName = Intent.GetStringExtra(Strings.ExtraPluginPackage);
var pluginRes = PackageManager.GetResourcesForApplication(_pluginPackageName);
var title = GetStringFromPlugin(pluginRes, _pluginPackageName, "kp2aplugin_title");
var author = GetStringFromPlugin(pluginRes, _pluginPackageName, "kp2aplugin_author");
var shortDesc = GetStringFromPlugin(pluginRes, _pluginPackageName, "kp2aplugin_shortdesc");
var version = PackageManager.GetPackageInfo(_pluginPackageName, 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);
_checkbox = FindViewById<CheckBox>(Resource.Id.cb_enabled);
_checkbox.CheckedChange += delegate {
new PluginDatabase(this).SetEnabled(_pluginPackageName, _checkbox.Checked);
};
Drawable d = PackageManager.GetApplicationIcon(_pluginPackageName);
FindViewById<ImageView>(Resource.Id.imgIcon).SetImageDrawable(d);
FindViewById<TextView>(Resource.Id.txtVersion).Text = version;
//cannot be wrong to update the view when we received an update
PluginHost.OnReceivedRequest += OnPluginHostOnOnReceivedRequest;
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);
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;
}
public 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,174 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Util;
using KeePassLib.Utility;
using Keepass2android.Pluginsdk;
using Org.Json;
namespace keepass2android
{
/// <summary>
/// Class which manages plugins inside the app
/// </summary>
[BroadcastReceiver()]
[IntentFilter(new[] { Strings.ActionRequestAccess})]
public class PluginHost: BroadcastReceiver
{
private const string _tag = "KP2A_PluginHost";
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)
{
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;
TriggerRequest(ctx, pkgName, pluginDatabase);
}
}
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)
{
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.");
}
}
if (OnReceivedRequest != null)
OnReceivedRequest(this, new PluginHostEventArgs() { Package = senderPackage});
}
}
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;
}
/// <summary>
/// adds the entry output data to the intent to be sent to a plugin
/// </summary>
public static void AddEntryToIntent(Intent intent, PwEntryOutput entry)
{
/*//add the entry XML
not yet implemented. What to do with attachments?
MemoryStream memStream = new MemoryStream();
KdbxFile.WriteEntries(memStream, new[] {entry});
string entryData = StrUtil.Utf8.GetString(memStream.ToArray());
intent.PutExtra(Strings.ExtraEntryData, entryData);
*/
//add the output string array (placeholders replaced taking into account the db context)
Dictionary<string, string> outputFields = entry.OutputStrings.ToDictionary(pair => StrUtil.SafeXmlString(pair.Key), pair => pair.Value.ReadString());
//add field values as JSON ({ "key":"value", ... } form)
JSONObject json = new JSONObject(outputFields);
var jsonStr = json.ToString();
intent.PutExtra(Strings.ExtraEntryOutputData, jsonStr);
//add list of which fields are protected (StringArrayExtra)
string[] protectedFieldsList = entry.OutputStrings.Where(s=>s.Value.IsProtected).Select(s => s.Key).ToArray();
intent.PutExtra(Strings.ExtraProtectedFieldsList, protectedFieldsList);
intent.PutExtra(Strings.ExtraEntryId, entry.Uuid.ToHexString());
}
public class PluginHostEventArgs
{
public string Package { get; set; }
}
public static event EventHandler<PluginHostEventArgs> OnReceivedRequest;
}
}

View File

@ -0,0 +1,712 @@
<?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.2</TargetFrameworkVersion>
<AndroidSupportedAbis>armeabi,armeabi-v7a,x86</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="App.cs" />
<Compile Include="AppNames.cs" />
<Compile Include="ClickView.cs" />
<Compile Include="CopyToClipboardService.cs" />
<Compile Include="EntryActivity.cs" />
<Compile Include="EntryActivityClasses\CopyToClipboardPopupMenuIcon.cs" />
<Compile Include="EntryActivityClasses\GotoUrlMenuItem.cs" />
<Compile Include="EntryActivityClasses\ToggleVisibilityPopupMenuItem.cs" />
<Compile Include="EntryContentsView.cs" />
<Compile Include="EntrySection.cs" />
<Compile Include="EntryActivityClasses\ExtraStringView.cs" />
<Compile Include="EntryActivityClasses\IPopupMenuItem.cs" />
<Compile Include="EntryActivityClasses\IStringView.cs" />
<Compile Include="Intents.cs" />
<Compile Include="Kp2aShortHelpView.cs" />
<Compile Include="EntryActivityClasses\OpenBinaryPopupItem.cs" />
<Compile Include="PluginDatabase.cs" />
<Compile Include="PluginDetailsActivity.cs" />
<Compile Include="PluginArrayAdapter.cs" />
<Compile Include="PluginListActivity.cs" />
<Compile Include="PluginHost.cs" />
<Compile Include="EntryActivityClasses\PluginMenuOption.cs" />
<Compile Include="EntryActivityClasses\PluginPopupMenuItem.cs" />
<Compile Include="PwEntryOutput.cs" />
<Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SprCompileFlags.cs" />
<Compile Include="SprContext.cs" />
<Compile Include="SprEngine.cs" />
<Compile Include="EntryActivityClasses\StandardStringView.cs" />
<Compile Include="TextDrawable.cs" />
<Compile Include="TextViewSelect.cs" />
<Compile Include="TextWithHelp.cs" />
<Compile Include="EntryActivityClasses\WriteBinaryToFilePopupItem.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\DejaVuSansMono.ttf" />
<AndroidAsset 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>
<AndroidResource Include="Resources\Layout\sftp_credentials.axml" />
</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="..\KeePassLib2Android\KeePassLib2Android.csproj">
<Project>{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}</Project>
<Name>KeePassLib2Android</Name>
</ProjectReference>
<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">
<SubType>Designer</SubType>
</AndroidResource>
</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\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>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\vdots_holodark.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\vdots.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\ic_menu_copy_holo_light.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Menu\entry.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\about.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\create_database.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\database_settings.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\donate.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\donate_bday.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\donate_bdaymissed.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\edit_extra_string_dialog.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\file_row.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\file_selection.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\file_selection_buttons.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\file_selection_filename.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\file_selection_no_recent.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\file_storage_setup.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\filestorage_selection.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\filestorage_selection_listitem.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\filestorage_selection_listitem_kp2a.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\generate_password.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\group_add_entry.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\group_edit.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\group_header.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\group_list_entry.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\icon.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\icon_picker.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\InViewButton.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\password.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\QuickUnlock.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\QuickUnlock_Unused.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\SaveButton.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\search.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\searchurlresults.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\searchurlresults_empty.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\set_password.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\sftpcredentials.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\StartScreenButtons.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\url_credentials.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values\styles_dark.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values\styles_light.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,58 @@
using System.Collections.Generic;
using System.Linq;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Widget;
using Keepass2android.Pluginsdk;
using PluginHostTest;
namespace keepass2android
{
//TODO theme?
[Activity (Label = "Plugins (TODO)", ConfigurationChanges=ConfigChanges.Orientation|ConfigChanges.KeyboardHidden )]
public class PluginListActivity : ListActivity
{
private PluginArrayAdapter _pluginArrayAdapter;
private List<PluginItem> _items;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
//TODO _design.ApplyTheme();
SetContentView(Resource.Layout.plugin_list);
ListView listView = FindViewById<ListView>(Android.Resource.Id.List);
listView.ItemClick +=
(sender, args) =>
{
Intent i = new Intent(this, typeof(PluginDetailsActivity));
i.PutExtra(Strings.ExtraPluginPackage, _items[args.Position].Package);
StartActivity(i);
};
// Create your application here
}
protected override void OnResume()
{
base.OnResume();
PluginDatabase pluginDb = new PluginDatabase(this);
_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, enabledStatus, this)).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;
}
}
}

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="14" android:targetSdkVersion="17" />
<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>

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

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