Conflicts:
	src/java/JavaFileStorage/bin/javafilestorage.jar
This commit is contained in:
Philipp Crocoll 2014-12-09 06:22:27 +01:00
parent e77c8f9ce5
commit 82c8b124bd
92 changed files with 9186 additions and 4044 deletions

2
.gitignore vendored
View File

@ -282,3 +282,5 @@ Thumbs.db
/src/java/MasterKee /src/java/MasterKee
/src/PluginSdkBinding/obj/ReleaseNoNet /src/PluginSdkBinding/obj/ReleaseNoNet
/src/MasterKeeWinPlugin/bin/Release
/src/SamplePlugin

View File

@ -3,8 +3,6 @@
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{3C0F7FE5-639F-4422-A087-8B26CF862D1B}</ProjectGuid> <ProjectGuid>{3C0F7FE5-639F-4422-A087-8B26CF862D1B}</ProjectGuid>
<ProjectTypeGuids>{10368E6C-D01B-4462-8E8B-01FC667A7035};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{10368E6C-D01B-4462-8E8B-01FC667A7035};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
@ -22,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@ -30,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'">
<OutputPath>bin\ReleaseNoNet\</OutputPath> <OutputPath>bin\ReleaseNoNet\</OutputPath>
@ -39,30 +39,30 @@
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Mono.Android" /> <Reference Include="Mono.Android" />
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="Xamarin.Android.Support.v4">
<HintPath>..\packages\Xamarin.Android.Support.v4.20.0.0.4\lib\MonoAndroid10\Xamarin.Android.Support.v4.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<LibraryProjectZip Include="..\java\android-filechooser\code\project.zip">
<Link>project.zip</Link>
</LibraryProjectZip>
<None Include="Jars\AboutJars.txt" /> <None Include="Jars\AboutJars.txt" />
<None Include="Additions\AboutAdditions.txt" /> <None Include="Additions\AboutAdditions.txt" />
<None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<TransformFile Include="Transforms\Metadata.xml" /> <TransformFile Include="Transforms\Metadata.xml" />
<TransformFile Include="Transforms\EnumFields.xml" /> <TransformFile Include="Transforms\EnumFields.xml" />
<TransformFile Include="Transforms\EnumMethods.xml" /> <TransformFile Include="Transforms\EnumMethods.xml" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\android-support-v4.jar" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.Bindings.targets" /> <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.Bindings.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- 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. Other similar extension points exist, see Microsoft.Common.targets.
@ -71,4 +71,15 @@
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
<ItemGroup>
<LibraryProjectZip Include="..\java\android-filechooser\code\project.zip">
<Link>project.zip</Link>
</LibraryProjectZip>
</ItemGroup>
<ItemGroup>
<XamarinComponentReference Include="xamandroidsupportv4-18">
<Version>20.0.0.4</Version>
<Visible>False</Visible>
</XamarinComponentReference>
</ItemGroup>
</Project> </Project>

View File

@ -1,5 +1,8 @@
<metadata> <metadata>
<remove-node path="/api/package[@name='group.pals.android.lib.ui.filechooser.providers.localfile']/class[@name='FileObserverEx']" /> <remove-node path="/api/package[@name='group.pals.android.lib.ui.filechooser.providers.localfile']/class[@name='FileObserverEx']" />
<remove-node path="/api/package[@name='group.pals.android.lib.ui.filechooser']/class[@name='FragmentFiles']" />
<remove-node path="/api/package[@name='group.pals.android.lib.ui.filechooser.prefs']" />
<!-- <!--

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Xamarin.Android.Support.v4" version="20.0.0.4" targetFramework="MonoAndroid22" />
</packages>

View File

@ -3,8 +3,6 @@
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{48574278-4779-4B3A-A9E4-9CF1BC285D0B}</ProjectGuid> <ProjectGuid>{48574278-4779-4B3A-A9E4-9CF1BC285D0B}</ProjectGuid>
<ProjectTypeGuids>{10368E6C-D01B-4462-8E8B-01FC667A7035};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{10368E6C-D01B-4462-8E8B-01FC667A7035};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
@ -22,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@ -30,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'">
<OutputPath>bin\ReleaseNoNet\</OutputPath> <OutputPath>bin\ReleaseNoNet\</OutputPath>
@ -39,14 +39,25 @@
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="GooglePlayServicesFroyoLib">
<HintPath>..\Components\googleplayservicesfroyo-9.0\lib\android\GooglePlayServicesFroyoLib.dll</HintPath>
</Reference>
<Reference Include="Mono.Android" /> <Reference Include="Mono.Android" />
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="GooglePlayServicesLib">
<HintPath>..\..\..\Components\googleplayservices-19.0.0\lib\android\GooglePlayServicesLib.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Android.Support.v4">
<HintPath>..\..\..\Components\googleplayservices-19.0.0\lib\android\Xamarin.Android.Support.v4.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Android.Support.v7.AppCompat">
<HintPath>..\..\..\Components\googleplayservices-19.0.0\lib\android\Xamarin.Android.Support.v7.AppCompat.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Android.Support.v7.MediaRouter">
<HintPath>..\..\..\Components\googleplayservices-19.0.0\lib\android\Xamarin.Android.Support.v7.MediaRouter.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
@ -65,11 +76,6 @@
<Link>Jars\javafilestorage.jar</Link> <Link>Jars\javafilestorage.jar</Link>
</EmbeddedJar> </EmbeddedJar>
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="..\java\JavaFileStorage\libs\android-support-v4.jar">
<Link>Jars\android-support-v4.jar</Link>
</EmbeddedReferenceJar>
</ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedReferenceJar Include="..\java\JavaFileStorage\libs\dropbox-android-sdk-1.5.4.jar"> <EmbeddedReferenceJar Include="..\java\JavaFileStorage\libs\dropbox-android-sdk-1.5.4.jar">
<Link>Jars\dropbox-android-sdk-1.5.4.jar</Link> <Link>Jars\dropbox-android-sdk-1.5.4.jar</Link>
@ -158,4 +164,10 @@
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
<ItemGroup>
<XamarinComponentReference Include="googleplayservices">
<Version>19.0.0</Version>
<Visible>False</Visible>
</XamarinComponentReference>
</ItemGroup>
</Project> </Project>

View File

@ -3,8 +3,6 @@
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{70D3844A-D9FA-4A64-B205-A84C6A822196}</ProjectGuid> <ProjectGuid>{70D3844A-D9FA-4A64-B205-A84C6A822196}</ProjectGuid>
<ProjectTypeGuids>{10368E6C-D01B-4462-8E8B-01FC667A7035};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{10368E6C-D01B-4462-8E8B-01FC667A7035};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
@ -22,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@ -30,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'">
<OutputPath>bin\ReleaseNoNet\</OutputPath> <OutputPath>bin\ReleaseNoNet\</OutputPath>
@ -39,6 +39,8 @@
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Mono.Android" /> <Reference Include="Mono.Android" />

View File

@ -3,8 +3,6 @@
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}</ProjectGuid> <ProjectGuid>{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
@ -20,7 +18,7 @@
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>False</Optimize> <Optimize>False</Optimize>
<OutputPath>bin\Debug</OutputPath> <OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;EXCLUDE_TWOFISH;INCLUDE_KEYBOARD;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;EXCLUDE_KEYTRANSFORM</DefineConstants> <DefineConstants>DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;INCLUDE_FILECHOOSER;_EXCLUDE_JAVAFILESTORAGE;INCLUDE_KEYTRANSFORM</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<ConsolePause>False</ConsolePause> <ConsolePause>False</ConsolePause>
@ -34,7 +32,6 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime> <AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<ConsolePause>False</ConsolePause> <ConsolePause>False</ConsolePause>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU' ">
<DebugType>none</DebugType> <DebugType>none</DebugType>
@ -44,7 +41,6 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime> <AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<ConsolePause>False</ConsolePause> <ConsolePause>False</ConsolePause>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="protobuf-net"> <Reference Include="protobuf-net">
@ -156,7 +152,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\KP2AKdbLibraryBinding\KP2AKdbLibraryBinding.csproj"> <ProjectReference Include="..\KP2AKdbLibraryBinding\KP2AKdbLibraryBinding.csproj">
<Project>{70d3844a-d9fa-4a64-b205-a84c6a822196}</Project> <Project>{70D3844A-D9FA-4A64-B205-A84C6A822196}</Project>
<Name>KP2AKdbLibraryBinding</Name> <Name>KP2AKdbLibraryBinding</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>

View File

@ -41,15 +41,16 @@ namespace KeePassLib.Keys
/// </summary> /// </summary>
public sealed class KcpKeyFile : IUserKey public sealed class KcpKeyFile : IUserKey
{ {
private string m_strPath; private IOConnectionInfo m_ioc;
private ProtectedBinary m_pbKeyData; private ProtectedBinary m_pbKeyData;
private ProtectedBinary m_pbFileData;
/// <summary> /// <summary>
/// Path to the key file. /// Path to the key file.
/// </summary> /// </summary>
public string Path public string Path
{ {
get { return m_strPath; } get { return m_ioc.Path; }
} }
/// <summary> /// <summary>
@ -62,6 +63,16 @@ namespace KeePassLib.Keys
get { return m_pbKeyData; } get { return m_pbKeyData; }
} }
public IOConnectionInfo Ioc
{
get { return m_ioc; }
}
public ProtectedBinary RawFileData
{
get { return m_pbFileData; }
}
public KcpKeyFile(string strKeyFile) public KcpKeyFile(string strKeyFile)
{ {
Construct(IOConnectionInfo.FromPath(strKeyFile), false); Construct(IOConnectionInfo.FromPath(strKeyFile), false);
@ -82,10 +93,15 @@ namespace KeePassLib.Keys
Construct(iocKeyFile, bThrowIfDbFile); Construct(iocKeyFile, bThrowIfDbFile);
} }
private void Construct(IOConnectionInfo iocFile, bool bThrowIfDbFile) public KcpKeyFile(byte[] keyFileContents, IOConnectionInfo iocKeyFile, bool bThrowIfDbFile)
{
Construct(keyFileContents, iocKeyFile, bThrowIfDbFile);
}
private void Construct(byte[] pbFileData, IOConnectionInfo iocKeyFile, bool bThrowIfDbFile)
{ {
byte[] pbFileData = IOConnection.ReadFile(iocFile);
if (pbFileData == null) throw new Java.IO.FileNotFoundException(); if (pbFileData == null) throw new Java.IO.FileNotFoundException();
m_pbFileData = new ProtectedBinary(true, pbFileData);
if (bThrowIfDbFile && (pbFileData.Length >= 8)) if (bThrowIfDbFile && (pbFileData.Length >= 8))
{ {
@ -110,12 +126,18 @@ namespace KeePassLib.Keys
if (pbKey == null) throw new InvalidOperationException(); if (pbKey == null) throw new InvalidOperationException();
m_strPath = iocFile.Path; m_ioc = iocKeyFile;
m_pbKeyData = new ProtectedBinary(true, pbKey); m_pbKeyData = new ProtectedBinary(true, pbKey);
MemUtil.ZeroByteArray(pbKey); MemUtil.ZeroByteArray(pbKey);
} }
private void Construct(IOConnectionInfo iocFile, bool bThrowIfDbFile)
{
byte[] pbFileData = IOConnection.ReadFile(iocFile);
Construct(pbFileData, iocFile, bThrowIfDbFile);
}
// public void Clear() // public void Clear()
// { // {
// m_strPath = string.Empty; // m_strPath = string.Empty;

View File

@ -654,7 +654,10 @@ namespace KeePassLib.Serialization
return ms.ToArray(); return ms.ToArray();
} }
catch(Exception) { } catch (Exception e)
{
Kp2aLog.Log("error opening file: " + e);
}
finally finally
{ {
if(sIn != null) sIn.Close(); if(sIn != null) sIn.Close();

View File

@ -79,8 +79,17 @@ namespace keepass2android
Handler UiThreadHandler { get; } Handler UiThreadHandler { get; }
IProgressDialog CreateProgressDialog(Context ctx); IProgressDialog CreateProgressDialog(Context ctx);
/// <summary>
/// returns the file storage for the given ioc. might be a caching file storage
/// </summary>
IFileStorage GetFileStorage(IOConnectionInfo iocInfo); IFileStorage GetFileStorage(IOConnectionInfo iocInfo);
/// <summary>
/// returns the file storage for the given ioc. if allowCache=false, no cached file storage is returned
/// </summary>
IFileStorage GetFileStorage(IOConnectionInfo iocInfo, bool allowCache);
void TriggerReload(Context context); void TriggerReload(Context context);
/// <summary> /// <summary>
@ -90,5 +99,7 @@ namespace keepass2android
//bool OnServerCertificateError(int certificateProblem); //bool OnServerCertificateError(int certificateProblem);
RemoteCertificateValidationCallback CertificateValidationCallback { get; } RemoteCertificateValidationCallback CertificateValidationCallback { get; }
} }
} }

View File

@ -0,0 +1,200 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using KeePassLib.Serialization;
namespace keepass2android.Io
{
//TODOC,TOTEST, TODO: unimplemented methods?
public class AndroidContentStorage: IFileStorage
{
private readonly Context _ctx;
public AndroidContentStorage(Context ctx)
{
_ctx = ctx;
}
public IEnumerable<string> SupportedProtocols
{
get { yield return "content"; }
}
public void Delete(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion)
{
return false;
}
public string GetCurrentFileVersionFast(IOConnectionInfo ioc)
{
return null;
}
public Stream OpenFileForRead(IOConnectionInfo ioc)
{
return _ctx.ContentResolver.OpenInputStream(Android.Net.Uri.Parse(ioc.Path));
}
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
{
return new AndroidContentWriteTransaction(ioc.Path, _ctx);
}
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
{
return "";
}
public bool RequiresCredentials(IOConnectionInfo ioc)
{
return false;
}
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
{
throw new NotImplementedException();
}
public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public FileDescription GetFileDescription(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
{
}
public bool RequiresSetup(IOConnectionInfo ioConnection)
{
return false;
}
public string IocToPath(IOConnectionInfo ioc)
{
return ioc.Path;
}
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId)
{
Intent intent = new Intent();
activity.IocToIntent(intent, new IOConnectionInfo() { Path = protocolId + "://" });
activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileChooserPrepared, intent);
}
public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode,
bool alwaysReturnSuccess)
{
Intent intent = new Intent();
activity.IocToIntent(intent, ioc);
activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileUsagePrepared, intent);
}
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
{
throw new NotImplementedException();
}
public void OnResume(IFileStorageSetupActivity activity)
{
throw new NotImplementedException();
}
public void OnStart(IFileStorageSetupActivity activity)
{
throw new NotImplementedException();
}
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data)
{
throw new NotImplementedException();
}
public string GetDisplayName(IOConnectionInfo ioc)
{
return ioc.Path;
}
public string CreateFilePath(string parent, string newFilename)
{
throw new NotImplementedException();
}
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
{
throw new NotImplementedException();
}
public bool IsPermanentLocation(IOConnectionInfo ioc)
{
//on pre-Kitkat devices, content:// is always temporary:
return false;
}
public bool IsReadOnly(IOConnectionInfo ioc)
{
//on pre-Kitkat devices, we can't write content:// files
return true;
}
}
class AndroidContentWriteTransaction : IWriteTransaction
{
private readonly string _path;
private readonly Context _ctx;
private MemoryStream _memoryStream;
public AndroidContentWriteTransaction(string path, Context ctx)
{
_path = path;
_ctx = ctx;
}
public void Dispose()
{
_memoryStream.Dispose();
}
public Stream OpenFile()
{
_memoryStream = new MemoryStream();
return _memoryStream;
}
public void CommitWrite()
{
using (Stream outputStream = _ctx.ContentResolver.OpenOutputStream(Android.Net.Uri.Parse(_path)))
{
outputStream.Write(_memoryStream.ToArray(), 0, (int)_memoryStream.Length);
}
}
}
}

View File

@ -4,6 +4,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Net.Security; using System.Net.Security;
using System.Security;
using Android.Content; using Android.Content;
using Android.OS; using Android.OS;
using Java.Security.Cert; using Java.Security.Cert;
@ -295,5 +296,67 @@ namespace keepass2android.Io
res.Path += filename; res.Path += filename;
return res; return res;
} }
public bool IsPermanentLocation(IOConnectionInfo ioc)
{
return true;
}
public bool IsReadOnlyBecauseKitkatRestrictions(IOConnectionInfo ioc)
{
if (IsLocalFileFlaggedReadOnly(ioc))
return false; //it's not read-only because of the restrictions introduced in kitkat
try
{
//test if we can open
//http://www.doubleencore.com/2014/03/android-external-storage/#comment-1294469517
using (var writer = new Java.IO.FileOutputStream(ioc.Path, true))
{
writer.Close();
return false; //we can write
}
}
catch (Java.IO.IOException)
{
//seems like we can't write to that location even though it's not read-only
return true;
}
}
public bool IsReadOnly(IOConnectionInfo ioc)
{
if (ioc.IsLocalFile())
{
if (IsLocalFileFlaggedReadOnly(ioc))
return true;
if (IsReadOnlyBecauseKitkatRestrictions(ioc))
return true;
return false;
}
//for remote files assume they can be written: (think positive! :-) )
return false;
}
private bool IsLocalFileFlaggedReadOnly(IOConnectionInfo ioc)
{
try
{
return new FileInfo(ioc.Path).IsReadOnly;
}
catch (SecurityException)
{
return true;
}
catch (UnauthorizedAccessException)
{
return true;
}
catch (Exception)
{
return false;
}
}
} }
} }

View File

@ -488,6 +488,11 @@ namespace keepass2android.Io
_cachedStorage.PrepareFileUsage(activity, ioc, requestCode, alwaysReturnSuccess || IsCached(ioc)); _cachedStorage.PrepareFileUsage(activity, ioc, requestCode, alwaysReturnSuccess || IsCached(ioc));
} }
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
{
_cachedStorage.PrepareFileUsage(ctx, ioc);
}
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState) public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
{ {
_cachedStorage.OnCreate(activity, savedInstanceState); _cachedStorage.OnCreate(activity, savedInstanceState);
@ -542,6 +547,19 @@ namespace keepass2android.Io
} }
public bool IsPermanentLocation(IOConnectionInfo ioc)
{
//even though the cache would be permanent, it's not a good idea to cache a temporary file, so return false in that case:
return _cachedStorage.IsPermanentLocation(ioc);
}
public bool IsReadOnly(IOConnectionInfo ioc)
{
//even though the cache can always be written, the changes made in the cache could not be transferred to the cached file
//so we better treat the cache as read-only as well.
return _cachedStorage.IsReadOnly(ioc);
}
private void StoreFilePath(IOConnectionInfo folderPath, string filename, IOConnectionInfo res) private void StoreFilePath(IOConnectionInfo folderPath, string filename, IOConnectionInfo res)
{ {
File.WriteAllText(CachedFilePath(GetPseudoIoc(folderPath, filename)) + ".filepath", res.Path); File.WriteAllText(CachedFilePath(GetPseudoIoc(folderPath, filename)) + ".filepath", res.Path);

View File

@ -157,6 +157,17 @@ namespace keepass2android.Io
/// </summary> /// </summary>
/// The method may throw FileNotFoundException or not in case the file doesn't exist. /// The method may throw FileNotFoundException or not in case the file doesn't exist.
IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename); IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename);
/// <summary>
/// returns true if it can be expected that this location will be available permanently (in contrast to a cache copy or temporary URI permissions in Android)
/// </summary>
/// Does not require to exist forever!
bool IsPermanentLocation(IOConnectionInfo ioc);
/// <summary>
/// Should return true if the file cannot be written.
/// </summary>
bool IsReadOnly(IOConnectionInfo ioc);
} }
public interface IWriteTransaction: IDisposable public interface IWriteTransaction: IDisposable

View File

@ -283,6 +283,16 @@ namespace keepass2android.Io
} }
public bool IsPermanentLocation(IOConnectionInfo ioc)
{
return true;
}
public bool IsReadOnly(IOConnectionInfo ioc)
{
return false; //TODO implement. note, however, that we MAY return false even if it's read-only
}
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState) public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
{ {
_jfs.OnCreate(((IJavaFileStorageFileStorageSetupActivity)activity), savedInstanceState); _jfs.OnCreate(((IJavaFileStorageFileStorageSetupActivity)activity), savedInstanceState);

View File

@ -3,8 +3,6 @@
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}</ProjectGuid> <ProjectGuid>{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
@ -20,9 +18,10 @@
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath> <OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;EXCLUDE_TWOFISH;INCLUDE_KEYBOARD;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;EXCLUDE_KEYTRANSFORM</DefineConstants> <DefineConstants>DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;INCLUDE_FILECHOOSER;_EXCLUDE_JAVAFILESTORAGE;INCLUDE_KEYTRANSFORM</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@ -31,6 +30,7 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'">
<OutputPath>bin\ReleaseNoNet\</OutputPath> <OutputPath>bin\ReleaseNoNet\</OutputPath>
@ -38,9 +38,10 @@
<Optimize>true</Optimize> <Optimize>true</Optimize>
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Mono.Android" /> <Reference Include="Mono.Android" />
@ -64,6 +65,7 @@
<Compile Include="DataExchange\Formats\KeePassKdb2x.cs" /> <Compile Include="DataExchange\Formats\KeePassKdb2x.cs" />
<Compile Include="DataExchange\Formats\KeePassXml2x.cs" /> <Compile Include="DataExchange\Formats\KeePassXml2x.cs" />
<Compile Include="DataExchange\PwExportInfo.cs" /> <Compile Include="DataExchange\PwExportInfo.cs" />
<Compile Include="Io\AndroidContentStorage.cs" />
<Compile Include="Io\BuiltInFileStorage.cs" /> <Compile Include="Io\BuiltInFileStorage.cs" />
<Compile Include="Io\CachingFileStorage.cs" /> <Compile Include="Io\CachingFileStorage.cs" />
<Compile Include="Io\DropboxFileStorage.cs" /> <Compile Include="Io\DropboxFileStorage.cs" />
@ -79,6 +81,7 @@
<Compile Include="Io\SkyDriveFileStorage.cs" /> <Compile Include="Io\SkyDriveFileStorage.cs" />
<Compile Include="IProgressDialog.cs" /> <Compile Include="IProgressDialog.cs" />
<Compile Include="PreferenceKey.cs" /> <Compile Include="PreferenceKey.cs" />
<Compile Include="SelectStorageLocationActivityBase.cs" />
<Compile Include="UiStringKey.cs" /> <Compile Include="UiStringKey.cs" />
<Compile Include="database\Database.cs" /> <Compile Include="database\Database.cs" />
<Compile Include="database\edit\ActionOnFinish.cs" /> <Compile Include="database\edit\ActionOnFinish.cs" />
@ -113,19 +116,19 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj"> <ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj">
<Project>{48574278-4779-4b3a-a9e4-9cf1bc285d0b}</Project> <Project>{48574278-4779-4B3A-A9E4-9CF1BC285D0B}</Project>
<Name>JavaFileStorageBindings</Name> <Name>JavaFileStorageBindings</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj"> <ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj">
<Project>{545b4a6b-8bba-4fbe-92fc-4ac060122a54}</Project> <Project>{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}</Project>
<Name>KeePassLib2Android</Name> <Name>KeePassLib2Android</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\KP2AKdbLibraryBinding\KP2AKdbLibraryBinding.csproj"> <ProjectReference Include="..\KP2AKdbLibraryBinding\KP2AKdbLibraryBinding.csproj">
<Project>{70d3844a-d9fa-4a64-b205-a84c6a822196}</Project> <Project>{70D3844A-D9FA-4A64-B205-A84C6A822196}</Project>
<Name>KP2AKdbLibraryBinding</Name> <Name>KP2AKdbLibraryBinding</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\TwofishCipher\TwofishCipher.csproj"> <ProjectReference Include="..\TwofishCipher\TwofishCipher.csproj">
<Project>{5cf675a5-9bee-4720-bed9-d5bf14a2ebf9}</Project> <Project>{5CF675A5-9BEE-4720-BED9-D5BF14A2EBF9}</Project>
<Name>TwofishCipher</Name> <Name>TwofishCipher</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,318 @@
using System;
using Android.App;
using Android.Content;
using Android.Widget;
using Java.Net;
using KeePassLib.Serialization;
using keepass2android.Io;
namespace keepass2android
{
/// <summary>
/// base class for SelectStorageLocationActivity containing testable (non-UI) code
/// </summary>
public abstract class SelectStorageLocationActivityBase: Activity
{
public enum WritableRequirements
{
ReadOnly = 0,
WriteDesired = 1,
WriteDemanded = 2
}
protected const int RequestCodeFileStorageSelectionForPrimarySelect = 983713;
private const int RequestCodeFileStorageSelectionForCopyToWritableLocation = 983714;
private const int RequestCodeFileFileBrowseForWritableLocation = 983715;
private const int RequestCodeFileBrowseForOpen = 983716;
protected IOConnectionInfo _selectedIoc;
private IKp2aApp _app;
public SelectStorageLocationActivityBase(IKp2aApp app)
{
_app = app;
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if ((requestCode == RequestCodeFileStorageSelectionForPrimarySelect) || ((requestCode == RequestCodeFileStorageSelectionForCopyToWritableLocation)))
{
int browseRequestCode = RequestCodeFileBrowseForOpen;
if (requestCode == RequestCodeFileStorageSelectionForCopyToWritableLocation)
{
browseRequestCode = RequestCodeFileFileBrowseForWritableLocation;
}
if (resultCode == ExitFileStorageSelectionOk)
{
string protocolId = data.GetStringExtra("protocolId");
if (protocolId == "androidget")
{
ShowAndroidBrowseDialog(RequestCodeFileBrowseForOpen, false);
}
else
{
bool isForSave = (requestCode == RequestCodeFileStorageSelectionForPrimarySelect) ?
IsStorageSelectionForSave : true;
StartSelectFile(isForSave, browseRequestCode, protocolId);
}
}
else
{
ReturnCancel();
}
}
if ((requestCode == RequestCodeFileBrowseForOpen) || (requestCode == RequestCodeFileFileBrowseForWritableLocation))
{
if (resultCode == (Result)FileStorageResults.FileChooserPrepared)
{
IOConnectionInfo ioc = new IOConnectionInfo();
SetIoConnectionFromIntent(ioc, data);
bool isForSave = (requestCode == RequestCodeFileFileBrowseForWritableLocation) ?
true : IsStorageSelectionForSave;
StartFileChooser(ioc.Path, requestCode, isForSave);
return;
}
if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE")))
{
ShowToast(data.GetStringExtra("EXTRA_ERROR_MESSAGE"));
}
if (resultCode == Result.Ok)
{
string filename = IntentToFilename(data);
if (filename != null)
{
if (filename.StartsWith("file://"))
{
filename = filename.Substring(7);
filename = URLDecoder.Decode(filename);
}
IOConnectionInfo ioc = new IOConnectionInfo
{
Path = filename
};
IocSelected(ioc, requestCode);
}
else
{
if (data.Data.Scheme == "content")
{
IocSelected(IOConnectionInfo.FromPath(data.DataString), requestCode);
}
else
{
ShowInvalidSchemeMessage(data.DataString);
ReturnCancel();
}
}
}
else
{
ReturnCancel();
}
}
}
protected abstract void ShowToast(string text);
protected abstract void ShowInvalidSchemeMessage(string dataString);
protected abstract string IntentToFilename(Intent data);
protected abstract void SetIoConnectionFromIntent(IOConnectionInfo ioc, Intent data);
protected abstract Result ExitFileStorageSelectionOk { get; }
/// <summary>
/// Starts the appropriate file selection process (either manual file select or prepare filechooser + filechooser)
/// </summary>
/// <param name="isForSave"></param>
/// <param name="browseRequestCode"></param>
/// <param name="protocolId"></param>
protected abstract void StartSelectFile(bool isForSave, int browseRequestCode, string protocolId);
protected abstract void ShowAndroidBrowseDialog(int requestCode, bool isForSave);
protected abstract bool IsStorageSelectionForSave { get; }
private void IocSelected(IOConnectionInfo ioc, int requestCode)
{
if (requestCode == RequestCodeFileFileBrowseForWritableLocation)
{
IocForCopySelected(ioc);
}
else if (requestCode == RequestCodeFileBrowseForOpen)
{
PrimaryIocSelected(ioc);
}
else
{
#if DEBUG
throw new Exception("invalid request code!");
#endif
}
}
private void IocForCopySelected(IOConnectionInfo targetIoc)
{
PerformCopy(() =>
{
IOConnectionInfo sourceIoc = _selectedIoc;
try
{
CopyFile(targetIoc, sourceIoc);
}
catch (Exception e)
{
return () =>
{
ShowToast(_app.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message);
ReturnCancel();
};
}
return () => { ReturnOk(targetIoc); };
});
}
protected abstract void PerformCopy(Func<Action> copyAndReturnPostExecute);
private void MoveToWritableLocation(IOConnectionInfo ioc)
{
_selectedIoc = ioc;
StartFileStorageSelection(RequestCodeFileStorageSelectionForCopyToWritableLocation, false, false);
}
protected abstract void StartFileStorageSelection(int requestCode,
bool allowThirdPartyGet, bool allowThirdPartySend);
protected bool OnReceivedSftpData(string filename, int requestCode, bool isForSave)
{
IOConnectionInfo ioc = new IOConnectionInfo { Path = filename };
#if !EXCLUDE_FILECHOOSER
StartFileChooser(ioc.Path, requestCode, isForSave);
#else
IocSelected(ioc, requestCode);
#endif
return true;
}
protected abstract void StartFileChooser(string path, int requestCode, bool isForSave);
protected bool OnOpenButton(String fileName, int requestCode)
{
IOConnectionInfo ioc = new IOConnectionInfo
{
Path = fileName
};
IocSelected(ioc, requestCode);
return true;
}
protected virtual void CopyFile(IOConnectionInfo targetIoc, IOConnectionInfo sourceIoc)
{
IFileStorage sourceStorage = _app.GetFileStorage(sourceIoc, false); //don't cache source. file won't be used ever again
IFileStorage targetStorage = _app.GetFileStorage(targetIoc);
using (
var writeTransaction = targetStorage.OpenWriteTransaction(targetIoc,
_app.GetBooleanPreference(
PreferenceKey.UseFileTransactions)))
{
using (var writeStream = writeTransaction.OpenFile())
{
sourceStorage.OpenFileForRead(sourceIoc).CopyTo(writeStream);
}
writeTransaction.CommitWrite();
}
}
private void PrimaryIocSelected(IOConnectionInfo ioc)
{
var filestorage = _app.GetFileStorage(ioc, false);
if (!filestorage.IsPermanentLocation(ioc))
{
string message = _app.GetResourceString(UiStringKey.FileIsTemporarilyAvailable) + " " + _app.GetResourceString(UiStringKey.CopyFileRequired) + " " + _app.GetResourceString(UiStringKey.ClickOkToSelectLocation);
EventHandler<DialogClickEventArgs> onOk = (sender, args) => { MoveToWritableLocation(ioc); };
EventHandler<DialogClickEventArgs> onCancel = (sender, args) => { ReturnCancel(); };
ShowAlertDialog(message, onOk, onCancel);
return;
}
if ((RequestedWritableRequirements != WritableRequirements.ReadOnly) && (filestorage.IsReadOnly(ioc)))
{
string readOnlyExplanation = _app.GetResourceString(UiStringKey.FileIsReadOnly);
BuiltInFileStorage builtInFileStorage = filestorage as BuiltInFileStorage;
if (builtInFileStorage != null)
{
if (builtInFileStorage.IsReadOnlyBecauseKitkatRestrictions(ioc))
readOnlyExplanation = _app.GetResourceString(UiStringKey.FileIsReadOnlyOnKitkat);
}
EventHandler<DialogClickEventArgs> onOk = (sender, args) => { MoveToWritableLocation(ioc); };
EventHandler<DialogClickEventArgs> onCancel = (sender, args) =>
{
if (RequestedWritableRequirements == WritableRequirements.WriteDemanded)
ReturnCancel();
else
ReturnOk(ioc);
};
ShowAlertDialog(readOnlyExplanation + " "
+ (RequestedWritableRequirements == WritableRequirements.WriteDemanded ?
_app.GetResourceString(UiStringKey.CopyFileRequired)
: _app.GetResourceString(UiStringKey.CopyFileRequiredForEditing))
+ " "
+ _app.GetResourceString(UiStringKey.ClickOkToSelectLocation), onOk, onCancel);
return;
}
ReturnOk(ioc);
}
protected abstract void ShowAlertDialog(string message, EventHandler<DialogClickEventArgs> onOk, EventHandler<DialogClickEventArgs> onCancel);
protected abstract WritableRequirements RequestedWritableRequirements { get; }
protected abstract void ReturnOk(IOConnectionInfo ioc);
protected abstract void ReturnCancel();
}
}

View File

@ -48,6 +48,12 @@ namespace keepass2android
SynchronizingOtpAuxFile, SynchronizingOtpAuxFile,
SavingOtpAuxFile, SavingOtpAuxFile,
CertificateFailure, CertificateFailure,
exporting_database exporting_database,
FileIsTemporarilyAvailable,
CopyFileRequired,
ClickOkToSelectLocation,
FileIsReadOnly,
FileIsReadOnlyOnKitkat,
CopyFileRequiredForEditing
} }
} }

View File

@ -119,7 +119,7 @@ namespace keepass2android
KpDatabase = pwDatabase; KpDatabase = pwDatabase;
SearchHelper = new SearchDbHelper(app); SearchHelper = new SearchDbHelper(app);
CanWrite = databaseLoader.CanWrite; CanWrite = databaseLoader.CanWrite && !fileStorage.IsReadOnly(iocInfo);
} }
/// <summary> /// <summary>

View File

@ -36,25 +36,21 @@ namespace keepass2android
} }
KcpKeyFile passwordKeyfile = (KcpKeyFile)key.GetUserKey(typeof(KcpKeyFile)); KcpKeyFile passwordKeyfile = (KcpKeyFile)key.GetUserKey(typeof(KcpKeyFile));
string keyfile = ""; MemoryStream keyfileStream = null;
if (passwordKeyfile != null) if (passwordKeyfile != null)
{ {
keyfile = passwordKeyfile.Path; keyfileStream = new MemoryStream(passwordKeyfile.RawFileData.ReadData());
} }
try try
{ {
var dbv3 = importer.OpenDatabase(hashingStream, password, keyfile); var dbv3 = importer.OpenDatabase(hashingStream, password, keyfileStream);
db.Name = dbv3.Name; db.Name = dbv3.Name;
db.RootGroup = ConvertGroup(dbv3.RootGroup); db.RootGroup = ConvertGroup(dbv3.RootGroup);
} }
catch (InvalidPasswordException e) {
return;
}
catch (Java.IO.FileNotFoundException e) catch (Java.IO.FileNotFoundException e)
{ {
throw new FileNotFoundException( throw new FileNotFoundException(

View File

@ -3,8 +3,6 @@
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}</ProjectGuid> <ProjectGuid>{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{10368E6C-D01B-4462-8E8B-01FC667A7035};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{10368E6C-D01B-4462-8E8B-01FC667A7035};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
@ -32,7 +30,6 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime> <AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<ConsolePause>False</ConsolePause> <ConsolePause>False</ConsolePause>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'">
<OutputPath>bin\ReleaseNoNet\</OutputPath> <OutputPath>bin\ReleaseNoNet\</OutputPath>
@ -40,6 +37,8 @@
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />

View File

@ -3,8 +3,6 @@
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}</ProjectGuid> <ProjectGuid>{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
@ -16,7 +14,6 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile> <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies> <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest> <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<AndroidUseLatestPlatformSdk />
<TargetFrameworkVersion>v4.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.2</TargetFrameworkVersion>
<AndroidSupportedAbis>armeabi,armeabi-v7a,x86</AndroidSupportedAbis> <AndroidSupportedAbis>armeabi,armeabi-v7a,x86</AndroidSupportedAbis>
<AndroidStoreUncompressedFileExtensions /> <AndroidStoreUncompressedFileExtensions />
@ -32,7 +29,6 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
<AndroidLinkMode>None</AndroidLinkMode> <AndroidLinkMode>None</AndroidLinkMode>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
@ -43,10 +39,8 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime> <AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
<AndroidLinkSkip>System.Core%3b</AndroidLinkSkip> <AndroidLinkSkip>System.Core%3b</AndroidLinkSkip>
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk> <EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
<BundleAssemblies>False</BundleAssemblies>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'">
<OutputPath>bin\ReleaseNoNet\</OutputPath> <OutputPath>bin\ReleaseNoNet\</OutputPath>
@ -54,9 +48,10 @@
<Optimize>true</Optimize> <Optimize>true</Optimize>
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Mono.Android" /> <Reference Include="Mono.Android" />
@ -67,6 +62,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="TestAndroidContentFileStorage.cs" />
<Compile Include="TestIntentsAndBundles.cs" /> <Compile Include="TestIntentsAndBundles.cs" />
<Compile Include="ProgressDialogStub.cs" /> <Compile Include="ProgressDialogStub.cs" />
<Compile Include="TestBase.cs" /> <Compile Include="TestBase.cs" />
@ -83,10 +79,10 @@
<Compile Include="TestCachingFileStorage.cs" /> <Compile Include="TestCachingFileStorage.cs" />
<Compile Include="TestSaveDb.cs" /> <Compile Include="TestSaveDb.cs" />
<Compile Include="TestSaveDbCached.cs" /> <Compile Include="TestSaveDbCached.cs" />
<Compile Include="TestSelectStorageLocation.cs" />
<Compile Include="TestSynchronizeCachedDatabase.cs" /> <Compile Include="TestSynchronizeCachedDatabase.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="ClassDiagram1.cd" />
<None Include="Resources\AboutResources.txt" /> <None Include="Resources\AboutResources.txt" />
<None Include="Assets\AboutAssets.txt" /> <None Include="Assets\AboutAssets.txt" />
</ItemGroup> </ItemGroup>
@ -109,7 +105,7 @@
<Name>Kp2aBusinessLogic</Name> <Name>Kp2aBusinessLogic</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\KP2AKdbLibraryBinding\KP2AKdbLibraryBinding.csproj"> <ProjectReference Include="..\KP2AKdbLibraryBinding\KP2AKdbLibraryBinding.csproj">
<Project>{70d3844a-d9fa-4a64-b205-a84c6a822196}</Project> <Project>{70D3844A-D9FA-4A64-B205-A84C6A822196}</Project>
<Name>KP2AKdbLibraryBinding</Name> <Name>KP2AKdbLibraryBinding</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\monodroid-unittesting\MonoDroidUnitTesting\MonoDroidUnitTesting.csproj"> <ProjectReference Include="..\monodroid-unittesting\MonoDroidUnitTesting\MonoDroidUnitTesting.csproj">

View File

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

View File

@ -0,0 +1,33 @@
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;
using keepass2android.Io;
namespace Kp2aUnitTests
{
[TestClass]
internal class TestBuiltInFileStorage
{
[TestMethod]
public void ReadOnlyKitKat()
{
var storage = new BuiltInFileStorage(new TestKp2aApp());
var extFile = "/storage/sdcard1/file.txt";
Assert.IsTrue(storage.IsReadOnly(IOConnectionInfo.FromPath(extFile)));
Assert.IsTrue(storage.IsReadOnly(IOConnectionInfo.FromPath(extFile)));
Assert.IsFalse(storage.IsReadOnly(IOConnectionInfo.FromPath(Application.Context.GetExternalFilesDir(null).AbsolutePath+ "/file.txt")));
}
}
}

View File

@ -183,5 +183,15 @@ namespace Kp2aUnitTests
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public bool IsPermanentLocation(IOConnectionInfo ioc)
{
return true;
}
public bool IsReadOnly(IOConnectionInfo ioc)
{
return false;
}
} }
} }

View File

@ -134,11 +134,18 @@ namespace Kp2aUnitTests
return new ProgressDialogStub(); return new ProgressDialogStub();
} }
public IFileStorage GetFileStorage(IOConnectionInfo iocInfo) public virtual IFileStorage GetFileStorage(IOConnectionInfo iocInfo)
{ {
return FileStorage; return FileStorage;
} }
public virtual IFileStorage GetFileStorage(IOConnectionInfo iocInfo, bool allowCache)
{
if (FileStorage is CachingFileStorage)
throw new Exception("bad test class");
return FileStorage;
}
public bool TriggerReloadCalled; public bool TriggerReloadCalled;
private TestFileStorage _testFileStorage; private TestFileStorage _testFileStorage;

View File

@ -2,11 +2,15 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Android.App; using Android.App;
using Com.Keepassdroid.Database.Load;
using Java.IO;
using KeePassLib; using KeePassLib;
using KeePassLib.Keys;
using KeePassLib.Serialization; using KeePassLib.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using keepass2android; using keepass2android;
using keepass2android.Io; using keepass2android.Io;
using FileNotFoundException = System.IO.FileNotFoundException;
namespace Kp2aUnitTests namespace Kp2aUnitTests
{ {
@ -72,6 +76,56 @@ namespace Kp2aUnitTests
Assert.IsFalse(e.Binaries.Any()); Assert.IsFalse(e.Binaries.Any());
} }
[TestMethod]
public void TestLoadKdb1WithKeyfileByDirectCall()
{
ImporterV3 importer = new ImporterV3();
try
{
FileStream dbStream = new FileStream(TestDbDirectory+"withkeyfile_nopwd.kdb", FileMode.Open);
FileStream keyfileStream = new FileStream(TestDbDirectory + "withkeyfile.key", FileMode.Open);
/*
for (int i = 0; i < 10; i++)
{
int b = keyfileStream.ReadByte();
Kp2aLog.Log(i+": " + b);
}
keyfileStream.Close();
Kp2aLog.Log("stream 2");
var keyfileStream2 = new MemoryStream(new KcpKeyFile(TestDbDirectory + "withkeyfile.key").RawFileData.ReadData());
for (int i = 0; i < 10; i++)
{
int b = keyfileStream2.ReadByte();
Kp2aLog.Log(i + ": " + b);
}*/
importer.OpenDatabase(dbStream, "", keyfileStream);
}
catch (Exception e)
{
Kp2aLog.Log(e.ToString());
Assert.Fail("exception occured: " + e);
}
}
[TestMethod]
public void TestLoadKdb1WithKeyfile()
{
var app = PerformLoad("withkeyfile.kdb", "test", TestDbDirectory + "withkeyfile.key");
}
[TestMethod]
public void TestLoadKdb1WithKeyfileOnly()
{
var app = PerformLoad("withkeyfile_nopwd.kdb", "", TestDbDirectory + "withkeyfile.key");
}
[TestMethod] [TestMethod]
public void TestLoadWithKeyfileOnly() public void TestLoadWithKeyfileOnly()
{ {

View File

@ -0,0 +1,790 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using KeePassLib.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using keepass2android;
using keepass2android.Io;
namespace Kp2aUnitTests
{
class TemporaryFileStorage: IFileStorage
{
public IEnumerable<string> SupportedProtocols
{
get {
yield return "content";
yield return "readonly";
}
}
public void Delete(IOConnectionInfo ioc)
{
}
public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion)
{
return false;
}
public string GetCurrentFileVersionFast(IOConnectionInfo ioc)
{
return null;
}
public Stream OpenFileForRead(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
{
throw new NotImplementedException();
}
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public bool RequiresCredentials(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
{
throw new NotImplementedException();
}
public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public FileDescription GetFileDescription(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public bool RequiresSetup(IOConnectionInfo ioConnection)
{
return false;
}
public string IocToPath(IOConnectionInfo ioc)
{
return ioc.Path;
}
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId)
{
throw new NotImplementedException();
}
public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode,
bool alwaysReturnSuccess)
{
throw new NotImplementedException();
}
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
{
throw new NotImplementedException();
}
public void OnResume(IFileStorageSetupActivity activity)
{
throw new NotImplementedException();
}
public void OnStart(IFileStorageSetupActivity activity)
{
throw new NotImplementedException();
}
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data)
{
throw new NotImplementedException();
}
public string GetDisplayName(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public string CreateFilePath(string parent, string newFilename)
{
throw new NotImplementedException();
}
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
{
throw new NotImplementedException();
}
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
{
throw new NotImplementedException();
}
public bool IsPermanentLocation(IOConnectionInfo ioc)
{
return ioc.Path.StartsWith("content") == false;
}
public bool IsReadOnly(IOConnectionInfo ioc)
{
return true;
}
}
class TestKp2aAppForSelectStorageLocation: TestKp2aApp
{
public override IFileStorage GetFileStorage(IOConnectionInfo iocInfo, bool allowCache)
{
if ((iocInfo.Path.StartsWith("content://")) || (iocInfo.Path.StartsWith("readonly://")))
{
return new TemporaryFileStorage();
}
return base.GetFileStorage(iocInfo);
}
}
sealed class TestControllableSelectStorageLocationActivity: SelectStorageLocationActivityBase
{
public List<string> toasts = new List<string>();
public WritableRequirements requestedWritableRequirements;
public bool? _result;
public IOConnectionInfo _resultIoc;
public object _userAction;
private IKp2aApp _app;
public TestControllableSelectStorageLocationActivity(IKp2aApp app) : base(app)
{
_app = app;
StartFileStorageSelection(RequestCodeFileStorageSelectionForPrimarySelect, true, false);
}
protected override void ShowToast(string text)
{
toasts.Add(text);
}
protected override void CopyFile(IOConnectionInfo targetIoc, IOConnectionInfo sourceIoc)
{
if (CopyFileShouldFail)
{
throw new Exception("CopyFile failed in test.");
}
}
public bool CopyFileShouldFail { get; set; }
protected override void ShowInvalidSchemeMessage(string dataString)
{
toasts.Add("invalid scheme: " + dataString);
}
protected override string IntentToFilename(Intent data)
{
return data.GetStringExtra("path");
}
protected override void SetIoConnectionFromIntent(IOConnectionInfo ioc, Intent data)
{
ioc.Path = data.GetStringExtra("path");
}
protected override Result ExitFileStorageSelectionOk
{
get { return Result.FirstUser + 825; }
}
protected override void StartSelectFile(bool isForSave, int browseRequestCode, string protocolId)
{
_userAction = new SelectFileAction(isForSave, browseRequestCode, protocolId, this);
}
public void HandleActivityResult(int requestCode, Result resultCode, Intent data)
{
OnActivityResult(requestCode, resultCode, data);
}
internal class SelectFileAction
{
private readonly bool _isForSave;
private readonly int _browseRequestCode;
private readonly string _protocolId;
private readonly TestControllableSelectStorageLocationActivity _testControllableSelectStorageLocationActivity;
public SelectFileAction(bool isForSave, int browseRequestCode, string protocolId, TestControllableSelectStorageLocationActivity testControllableSelectStorageLocationActivity)
{
_isForSave = isForSave;
_browseRequestCode = browseRequestCode;
_protocolId = protocolId;
_testControllableSelectStorageLocationActivity = testControllableSelectStorageLocationActivity;
}
public bool IsForSave {
get { return _isForSave; }
}
public int BrowseRequestCode
{
get { return _browseRequestCode; }
}
public string ProtocolId
{
get { return _protocolId; }
}
public void PerformManualFileSelect(string path)
{
_testControllableSelectStorageLocationActivity.PressOpenButton(path, _browseRequestCode);
}
public void Cancel()
{
_testControllableSelectStorageLocationActivity.ReturnCancel();
}
public void PrepareFileChooser(string protocolId)
{
Intent data = new Intent();
data.PutExtra("path", protocolId+"://");
_testControllableSelectStorageLocationActivity.HandleActivityResult(_browseRequestCode, (Result) FileStorageResults.FileChooserPrepared, data);
}
}
private void PressOpenButton(string path, int browseRequestCode)
{
OnOpenButton(path, browseRequestCode);
}
internal class FileStorageSelectionAction
{
private readonly int _requestCode;
private readonly bool _allowThirdPartyGet;
private readonly bool _allowThirdPartySend;
private readonly TestControllableSelectStorageLocationActivity _testControllableSelectStorageLocationActivity;
public FileStorageSelectionAction(int requestCode, bool allowThirdPartyGet, bool allowThirdPartySend, TestControllableSelectStorageLocationActivity testControllableSelectStorageLocationActivity)
{
_requestCode = requestCode;
_allowThirdPartyGet = allowThirdPartyGet;
_allowThirdPartySend = allowThirdPartySend;
_testControllableSelectStorageLocationActivity = testControllableSelectStorageLocationActivity;
}
public int RequestCode
{
get { return _requestCode; }
}
public bool AllowThirdPartyGet
{
get { return _allowThirdPartyGet; }
}
public bool AllowThirdPartySend
{
get { return _allowThirdPartySend; }
}
public void ReturnProtocol(string protocolId)
{
Intent intent = new Intent();
intent.PutExtra("protocolId", protocolId);
_testControllableSelectStorageLocationActivity.HandleActivityResult(_requestCode, Result.FirstUser + 825 /*fs select ok*/, intent);
}
public void Cancel()
{
_testControllableSelectStorageLocationActivity.HandleActivityResult(_requestCode, Result.Canceled, null);
}
}
protected override void ShowAndroidBrowseDialog(int requestCode, bool isForSave)
{
_userAction = new AndroidBrowseDialogAction(requestCode, isForSave, this);
}
internal class AndroidBrowseDialogAction
{
private readonly int _requestCode;
private readonly bool _isForSave;
private readonly TestControllableSelectStorageLocationActivity _activity;
public AndroidBrowseDialogAction(int requestCode, bool isForSave, TestControllableSelectStorageLocationActivity activity)
{
_requestCode = requestCode;
_isForSave = isForSave;
_activity = activity;
}
public int RequestCode
{
get { return _requestCode; }
}
public void ReturnSelectedFile(string selectedUri)
{
Intent data = new Intent();
data.PutExtra("path", selectedUri);
_activity.HandleActivityResult(_requestCode, Result.Ok, data);
}
public void Cancel()
{
_activity.HandleActivityResult(_requestCode, Result.Canceled, null);
}
}
protected override bool IsStorageSelectionForSave { get { return SelectLocationForSave; } }
private bool SelectLocationForSave { get; set; }
protected override void PerformCopy(Func<Action> copyAndReturnPostExecute)
{
Action postExec = copyAndReturnPostExecute();
postExec();
}
protected override void StartFileStorageSelection(int requestCode, bool allowThirdPartyGet, bool allowThirdPartySend)
{
_userAction = new FileStorageSelectionAction(requestCode, allowThirdPartyGet, allowThirdPartySend, this);
}
protected override void StartFileChooser(string path, int requestCode, bool isForSave)
{
_userAction = new FileChooserAction(path, requestCode, isForSave, this);
}
internal class FileChooserAction
{
private readonly string _path;
private readonly int _requestCode;
private readonly bool _isForSave;
private readonly TestControllableSelectStorageLocationActivity _activity;
public FileChooserAction(string path, int requestCode, bool isForSave, TestControllableSelectStorageLocationActivity activity)
{
_path = path;
_requestCode = requestCode;
_isForSave = isForSave;
_activity = activity;
}
public string Path
{
get { return _path; }
}
public int RequestCode
{
get { return _requestCode; }
}
public bool IsForSave
{
get { return _isForSave; }
}
public void ReturnChosenFile(string path)
{
Intent data = new Intent();
data.PutExtra("path", path);
_activity.HandleActivityResult(_requestCode, Result.Ok, data);
}
public void Cancel()
{
_activity.HandleActivityResult(_requestCode, Result.Canceled, null);
}
}
protected override void ShowAlertDialog(string message, EventHandler<DialogClickEventArgs> onOk, EventHandler<DialogClickEventArgs> onCancel)
{
_userAction = new ShowAlertDialogAction(message, onOk, onCancel);
}
internal class ShowAlertDialogAction
{
public string Message { get; set; }
public EventHandler<DialogClickEventArgs> OnOk { get; set; }
public EventHandler<DialogClickEventArgs> OnCancel { get; set; }
public ShowAlertDialogAction(string message, EventHandler<DialogClickEventArgs> onOk, EventHandler<DialogClickEventArgs> onCancel)
{
Message = message;
OnOk = onOk;
OnCancel = onCancel;
}
public void Cancel()
{
OnCancel(this, null);
}
public void Ok()
{
OnOk(this, null);
}
}
protected override WritableRequirements RequestedWritableRequirements
{
get { return requestedWritableRequirements; }
}
public IKp2aApp App
{
get { return _app; }
}
protected override void ReturnOk(IOConnectionInfo ioc)
{
_result = true;
_resultIoc = ioc;
}
protected override void ReturnCancel()
{
_result = false;
}
}
[TestClass]
class TestSelectStorageLocation
{
[TestInitialize]
public void Init()
{
try
{
Looper.Prepare();
}
catch (Exception)
{
}
}
[TestMethod]
public void TestCancelFileStorageSelection()
{
var testee = CreateTestee();
var action = (TestControllableSelectStorageLocationActivity.FileStorageSelectionAction)testee._userAction;
action.Cancel();
Assert.IsFalse((bool) testee._result);
}
[TestMethod]
public void TestSimpleManualSelect()
{
var testee = CreateTestee();
var action = (TestControllableSelectStorageLocationActivity.FileStorageSelectionAction) testee._userAction;
action.ReturnProtocol("ftp");
Assert.IsNull(testee._result); //no result yet
var action2 = (TestControllableSelectStorageLocationActivity.SelectFileAction)testee._userAction;
string path = "ftp://crocoll.net/test.kdbx";
action2.PerformManualFileSelect(path);
Assert.IsTrue((bool) testee._result);
Assert.AreEqual(testee._resultIoc.Path, path);
}
[TestMethod]
public void TestCancelManualSelect()
{
var testee = CreateTestee();
var action = (TestControllableSelectStorageLocationActivity.FileStorageSelectionAction)testee._userAction;
action.ReturnProtocol("ftp");
Assert.IsNull(testee._result); //no result yet
var action2 = (TestControllableSelectStorageLocationActivity.SelectFileAction)testee._userAction;
action2.Cancel();
Assert.IsFalse((bool)testee._result);
}
[TestMethod]
public void TestCancelAndroidBrowseDialog()
{
var testee = CreateTestee();
var action = (TestControllableSelectStorageLocationActivity.FileStorageSelectionAction)testee._userAction;
action.ReturnProtocol("androidget");
Assert.IsNull(testee._result); //no result yet
var action2 = (TestControllableSelectStorageLocationActivity.AndroidBrowseDialogAction)testee._userAction;
action2.Cancel();
Assert.IsFalse((bool)testee._result);
}
[TestMethod]
public void TestCancelCopyTemporaryLocation()
{
var testee = CreateTestee();
var action = (TestControllableSelectStorageLocationActivity.FileStorageSelectionAction)testee._userAction;
action.ReturnProtocol("androidget");
var action2 = (TestControllableSelectStorageLocationActivity.AndroidBrowseDialogAction)testee._userAction;
action2.ReturnSelectedFile("content://abc.kdbx");
var action3 = (TestControllableSelectStorageLocationActivity.ShowAlertDialogAction)testee._userAction;
Assert.IsTrue(action3.Message.StartsWith(testee.App.GetResourceString(UiStringKey.FileIsTemporarilyAvailable)));
Assert.IsNull(testee._result); //no result yet
action3.Cancel();
Assert.IsFalse((bool)testee._result);
}
[TestMethod]
public void TestCopyTemporaryLocation()
{
var testee = CreateTestee();
var action = (TestControllableSelectStorageLocationActivity.FileStorageSelectionAction)testee._userAction;
action.ReturnProtocol("androidget");
var action2 = (TestControllableSelectStorageLocationActivity.AndroidBrowseDialogAction)testee._userAction;
action2.ReturnSelectedFile("content://abc.kdbx");
var action3 = (TestControllableSelectStorageLocationActivity.ShowAlertDialogAction)testee._userAction;
Assert.IsTrue(action3.Message.StartsWith(testee.App.GetResourceString(UiStringKey.FileIsTemporarilyAvailable)));
Assert.IsNull(testee._result); //no result yet
action3.Ok();
var action4 = (TestControllableSelectStorageLocationActivity.FileStorageSelectionAction)testee._userAction;
action4.ReturnProtocol("ftp");
Assert.IsNull(testee._result);
var action5 = (TestControllableSelectStorageLocationActivity.SelectFileAction)testee._userAction;
Assert.IsTrue(action5.IsForSave);
string path = "ftp://crocoll.net/testtarget.kdbx";
action5.PerformManualFileSelect(path);
Assert.IsTrue((bool)testee._result);
Assert.AreEqual(path, testee._resultIoc.Path);
}
[TestMethod]
public void TestCopyTemporaryLocationWithFileBrowser()
{
var testee = CreateTestee();
var action = (TestControllableSelectStorageLocationActivity.FileStorageSelectionAction)testee._userAction;
action.ReturnProtocol("androidget");
var action2 = (TestControllableSelectStorageLocationActivity.AndroidBrowseDialogAction)testee._userAction;
action2.ReturnSelectedFile("content://abc.kdbx");
var action3 = (TestControllableSelectStorageLocationActivity.ShowAlertDialogAction)testee._userAction;
Assert.IsTrue(action3.Message.StartsWith(testee.App.GetResourceString(UiStringKey.FileIsTemporarilyAvailable)));
Assert.IsNull(testee._result); //no result yet
action3.Ok();
var action4 = (TestControllableSelectStorageLocationActivity.FileStorageSelectionAction)testee._userAction;
action4.ReturnProtocol("file");
var action5 = (TestControllableSelectStorageLocationActivity.SelectFileAction)testee._userAction;
Assert.IsTrue(action5.IsForSave);
action5.PrepareFileChooser("file");
Assert.IsNull(testee._result);
var action6 = (TestControllableSelectStorageLocationActivity.FileChooserAction)testee._userAction;
Assert.IsTrue(action5.IsForSave);
string path = "file:///mnt/sdcard/testtarget.kdbx";
action6.ReturnChosenFile(path);
string expectedpath = "/mnt/sdcard/testtarget.kdbx";
Assert.IsTrue((bool)testee._result);
Assert.AreEqual(expectedpath, testee._resultIoc.Path);
}
[TestMethod]
public void TestCopyTemporaryLocationWithCancelFileBrowser()
{
var testee = CreateTestee();
var action = (TestControllableSelectStorageLocationActivity.FileStorageSelectionAction)testee._userAction;
action.ReturnProtocol("androidget");
var action2 = (TestControllableSelectStorageLocationActivity.AndroidBrowseDialogAction)testee._userAction;
action2.ReturnSelectedFile("content://abc.kdbx");
var action3 = (TestControllableSelectStorageLocationActivity.ShowAlertDialogAction)testee._userAction;
Assert.IsTrue(action3.Message.StartsWith(testee.App.GetResourceString(UiStringKey.FileIsTemporarilyAvailable)));
Assert.IsNull(testee._result); //no result yet
action3.Ok();
var action4 = (TestControllableSelectStorageLocationActivity.FileStorageSelectionAction)testee._userAction;
action4.ReturnProtocol("file");
var action5 = (TestControllableSelectStorageLocationActivity.SelectFileAction)testee._userAction;
Assert.IsTrue(action5.IsForSave);
action5.PrepareFileChooser("file");
Assert.IsNull(testee._result);
var action6 = (TestControllableSelectStorageLocationActivity.FileChooserAction)testee._userAction;
Assert.IsTrue(action5.IsForSave);
string path = "file:///mnt/sdcard/testtarget.kdbx";
action6.Cancel();
Assert.IsFalse((bool)testee._result);
}
[TestMethod]
public void TestCancelCopyReadOnlyLocation()
{
SelectStorageLocationActivityBase.WritableRequirements requestedWritableRequirements = SelectStorageLocationActivityBase.WritableRequirements.WriteDesired;
string path;
var testee = PrepareTesteeForCancelCopyReadOnly(requestedWritableRequirements, out path);
Assert.IsTrue((bool)testee._result);
Assert.AreEqual(path, testee._resultIoc.Path);
}
[TestMethod]
public void TestCancelCopyReadOnlyLocationWriteRequired()
{
SelectStorageLocationActivityBase.WritableRequirements requestedWritableRequirements = SelectStorageLocationActivityBase.WritableRequirements.WriteDemanded;
string path;
var testee = PrepareTesteeForCancelCopyReadOnly(requestedWritableRequirements, out path);
Assert.IsFalse((bool)testee._result);
}
private static TestControllableSelectStorageLocationActivity PrepareTesteeForCancelCopyReadOnly(
SelectStorageLocationActivityBase.WritableRequirements requestedWritableRequirements, out string path)
{
var testee = CreateTestee();
testee.requestedWritableRequirements = requestedWritableRequirements;
var action = (TestControllableSelectStorageLocationActivity.FileStorageSelectionAction) testee._userAction;
action.ReturnProtocol("androidget");
var action2 = (TestControllableSelectStorageLocationActivity.AndroidBrowseDialogAction) testee._userAction;
path = "readonly://abc.kdbx";
action2.ReturnSelectedFile(path);
var action3 = (TestControllableSelectStorageLocationActivity.ShowAlertDialogAction) testee._userAction;
Assert.IsTrue(action3.Message.StartsWith(testee.App.GetResourceString(UiStringKey.FileIsReadOnly)));
Assert.IsNull(testee._result); //no result yet
action3.Cancel();
return testee;
}
[TestMethod]
public void TestOpenReadOnly()
{
var testee = CreateTestee();
testee.requestedWritableRequirements = SelectStorageLocationActivityBase.WritableRequirements.ReadOnly;
var action = (TestControllableSelectStorageLocationActivity.FileStorageSelectionAction) testee._userAction;
action.ReturnProtocol("androidget");
var action2 = (TestControllableSelectStorageLocationActivity.AndroidBrowseDialogAction) testee._userAction;
var path = "readonly://abc.kdbx";
action2.ReturnSelectedFile(path);
Assert.IsTrue((bool)testee._result);
Assert.AreEqual(path, testee._resultIoc.Path);
}
[TestMethod]
public void TestCopyTemporaryLocationFails()
{
var testee = CreateTestee();
var action = (TestControllableSelectStorageLocationActivity.FileStorageSelectionAction)testee._userAction;
action.ReturnProtocol("androidget");
var action2 = (TestControllableSelectStorageLocationActivity.AndroidBrowseDialogAction)testee._userAction;
action2.ReturnSelectedFile("content://abc.kdbx");
var action3 = (TestControllableSelectStorageLocationActivity.ShowAlertDialogAction)testee._userAction;
Assert.IsTrue(action3.Message.StartsWith(testee.App.GetResourceString(UiStringKey.FileIsTemporarilyAvailable)));
Assert.IsNull(testee._result); //no result yet
action3.Ok();
var action4 = (TestControllableSelectStorageLocationActivity.FileStorageSelectionAction)testee._userAction;
action4.ReturnProtocol("ftp");
Assert.IsNull(testee._result);
var action5 = (TestControllableSelectStorageLocationActivity.SelectFileAction)testee._userAction;
Assert.IsTrue(action5.IsForSave);
string path = "ftp://crocoll.net/testtarget.kdbx";
testee.CopyFileShouldFail = true;
action5.PerformManualFileSelect(path);
Assert.IsFalse((bool)testee._result);
}
private static TestControllableSelectStorageLocationActivity CreateTestee()
{
return new TestControllableSelectStorageLocationActivity(new TestKp2aAppForSelectStorageLocation());
}
}
}

View File

@ -35,10 +35,6 @@
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
@ -46,6 +42,9 @@
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework">
<HintPath>..\..\..\..\Program Files %28x86%29\Microsoft Visual Studio 11.0\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Main.cs" /> <Compile Include="Main.cs" />
@ -57,7 +56,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MasterPassword\MasterPassword.csproj"> <ProjectReference Include="..\MasterPassword\MasterPassword.csproj">
<Project>{2f7cb5b4-ac2a-4790-b0f3-42e6c9a060d5}</Project> <Project>{2F7CB5B4-AC2A-4790-B0F3-42E6C9A060D5}</Project>
<Name>MasterPassword</Name> <Name>MasterPassword</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>

View File

@ -3,8 +3,6 @@
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{9A4C5BAA-1A8A-49B4-BBC3-60D4871FB36C}</ProjectGuid> <ProjectGuid>{9A4C5BAA-1A8A-49B4-BBC3-60D4871FB36C}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
@ -26,7 +24,6 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
<AndroidLinkMode>None</AndroidLinkMode> <AndroidLinkMode>None</AndroidLinkMode>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
@ -37,7 +34,6 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime> <AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Mono.Android" /> <Reference Include="Mono.Android" />
@ -86,11 +82,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MasterPassword\MasterPassword.csproj"> <ProjectReference Include="..\MasterPassword\MasterPassword.csproj">
<Project>{2f7cb5b4-ac2a-4790-b0f3-42e6c9a060d5}</Project> <Project>{2F7CB5B4-AC2A-4790-B0F3-42E6C9A060D5}</Project>
<Name>MasterPassword</Name> <Name>MasterPassword</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\PluginSdkBinding\PluginSdkBinding.csproj"> <ProjectReference Include="..\PluginSdkBinding\PluginSdkBinding.csproj">
<Project>{3da3911e-36de-465e-8f15-f1991b6437e5}</Project> <Project>{3DA3911E-36DE-465E-8F15-F1991B6437E5}</Project>
<Name>PluginSdkBinding</Name> <Name>PluginSdkBinding</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>

View File

@ -3,8 +3,6 @@
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}</ProjectGuid> <ProjectGuid>{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
@ -16,7 +14,6 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile> <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies> <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest> <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<AndroidUseLatestPlatformSdk />
<TargetFrameworkVersion>v4.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.2</TargetFrameworkVersion>
<AndroidSupportedAbis>armeabi,armeabi-v7a,x86</AndroidSupportedAbis> <AndroidSupportedAbis>armeabi,armeabi-v7a,x86</AndroidSupportedAbis>
<AndroidStoreUncompressedFileExtensions /> <AndroidStoreUncompressedFileExtensions />
@ -32,7 +29,6 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
<AndroidLinkMode>None</AndroidLinkMode> <AndroidLinkMode>None</AndroidLinkMode>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
@ -43,7 +39,6 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime> <AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Mono.Android" /> <Reference Include="Mono.Android" />
@ -122,7 +117,7 @@
<Name>KeePassLib2Android</Name> <Name>KeePassLib2Android</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\PluginSdkBinding\PluginSdkBinding.csproj"> <ProjectReference Include="..\PluginSdkBinding\PluginSdkBinding.csproj">
<Project>{3da3911e-36de-465e-8f15-f1991b6437e5}</Project> <Project>{3DA3911E-36DE-465E-8F15-F1991B6437E5}</Project>
<Name>PluginSdkBinding</Name> <Name>PluginSdkBinding</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>

View File

@ -3,8 +3,6 @@
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{3DA3911E-36DE-465E-8F15-F1991B6437E5}</ProjectGuid> <ProjectGuid>{3DA3911E-36DE-465E-8F15-F1991B6437E5}</ProjectGuid>
<ProjectTypeGuids>{10368E6C-D01B-4462-8E8B-01FC667A7035};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{10368E6C-D01B-4462-8E8B-01FC667A7035};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
@ -22,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@ -30,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'">
<OutputPath>bin\ReleaseNoNet\</OutputPath> <OutputPath>bin\ReleaseNoNet\</OutputPath>
@ -39,6 +39,8 @@
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Mono.Android" /> <Reference Include="Mono.Android" />

View File

@ -3,8 +3,6 @@
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{5CF675A5-9BEE-4720-BED9-D5BF14A2EBF9}</ProjectGuid> <ProjectGuid>{5CF675A5-9BEE-4720-BED9-D5BF14A2EBF9}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
@ -23,6 +21,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@ -31,6 +30,7 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'">
<OutputPath>bin\ReleaseNoNet\</OutputPath> <OutputPath>bin\ReleaseNoNet\</OutputPath>
@ -38,9 +38,10 @@
<Optimize>true</Optimize> <Optimize>true</Optimize>
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Mono.Android" /> <Reference Include="Mono.Android" />
@ -66,7 +67,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj"> <ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj">
<Project>{545b4a6b-8bba-4fbe-92fc-4ac060122a54}</Project> <Project>{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}</Project>
<Name>KeePassLib2Android</Name> <Name>KeePassLib2Android</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.inputstick.api" package="com.inputstick.api"
android:versionCode="1" android:versionCode="1"

View File

@ -14,7 +14,10 @@ public final class R {
public static int ic_launcher=0x7f020000; public static int ic_launcher=0x7f020000;
} }
public static final class string { public static final class string {
public static int action_settings=0x7f030002;
public static int app_name=0x7f030000; public static int app_name=0x7f030000;
public static int hello_world=0x7f030003;
public static int title_activity_install_utility=0x7f030001;
} }
public static final class style { public static final class style {
/** /**

View File

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">InputStickAPI</string> <string name="app_name">InputStickAPI</string>
<string name="title_activity_install_utility">Download InputStickUtility</string>
<string name="action_settings">Settings</string>
<string name="hello_world">Hello world!</string>
</resources> </resources>

View File

@ -10,8 +10,10 @@ public class AES {
private Cipher mCipherEncr; private Cipher mCipherEncr;
private Cipher mCipherDecr; private Cipher mCipherDecr;
private SecretKeySpec mKey; private SecretKeySpec mKey;
private boolean ready;
public AES() { public AES() {
ready = false;
} }
public static byte[] getMD5(String s) { public static byte[] getMD5(String s) {
@ -31,10 +33,10 @@ public class AES {
mCipherEncr = Cipher.getInstance("AES/CBC/NoPadding"); mCipherEncr = Cipher.getInstance("AES/CBC/NoPadding");
mCipherEncr.init(Cipher.ENCRYPT_MODE, mKey); mCipherEncr.init(Cipher.ENCRYPT_MODE, mKey);
iv = mCipherEncr.getIV(); iv = mCipherEncr.getIV();
//System.out.println("AES IV: "); Util.printHex(iv, "AES IV: ");
Util.printHex(iv);
mCipherDecr = Cipher.getInstance("AES/CBC/NoPadding"); mCipherDecr = Cipher.getInstance("AES/CBC/NoPadding");
mCipherDecr.init(Cipher.DECRYPT_MODE, mKey, new IvParameterSpec(iv)); mCipherDecr.init(Cipher.DECRYPT_MODE, mKey, new IvParameterSpec(iv));
ready = true;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -49,4 +51,7 @@ public class AES {
return mCipherDecr.update(data); return mCipherDecr.update(data);
} }
public boolean isReady() {
return ready;
}
} }

View File

@ -21,7 +21,6 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
private Application mApp; private Application mApp;
private BTService mBTService; private BTService mBTService;
private PacketManager mPacketManager; private PacketManager mPacketManager;
//private PacketQueue mPacketQueue;
private final BTHandler mBTHandler = new BTHandler(this); private final BTHandler mBTHandler = new BTHandler(this);
@ -46,30 +45,11 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
case BTService.EVENT_CANCELLED: case BTService.EVENT_CANCELLED:
manager.onDisconnected(); manager.onDisconnected();
break; break;
case BTService.EVENT_CONNECTION_FAILED: case BTService.EVENT_ERROR:
manager.onFailure(1); manager.onFailure(msg.arg1);
break;
case BTService.EVENT_CONNECTION_LOST:
manager.onFailure(1);
break;
case BTService.EVENT_NO_BT_HW:
manager.onFailure(1);
break;
case BTService.EVENT_INVALID_MAC:
manager.onFailure(1);
break;
case BTService.EVENT_CMD_TIMEOUT:
manager.onFailure(1);
break;
case BTService.EVENT_INTERVAL_TIMEOUT:
manager.onFailure(1);
break;
case BTService.EVENT_TURN_ON_TIMEOUT:
manager.onFailure(1);
break;
case BTService.EVENT_OTHER_ERROR:
manager.onFailure(1);
break; break;
default:
manager.onFailure(InputStickError.ERROR_BLUETOOTH);
} }
} }
} }
@ -80,6 +60,7 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
private void onConnected() { private void onConnected() {
stateNotify(ConnectionManager.STATE_CONNECTED); stateNotify(ConnectionManager.STATE_CONNECTED);
//mInitManager.startTimeoutCountdown(InitManager.DEFAULT_INIT_TIMEOUT);
mInitManager.onConnected(); mInitManager.onConnected();
} }
@ -90,6 +71,7 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
private void onFailure(int code) { private void onFailure(int code) {
mErrorCode = code; mErrorCode = code;
stateNotify(ConnectionManager.STATE_FAILURE); stateNotify(ConnectionManager.STATE_FAILURE);
disconnect();
} }
private void onData(byte[] rawData) { private void onData(byte[] rawData) {
@ -97,13 +79,12 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
data = mPacketManager.bytesToPacket(rawData); data = mPacketManager.bytesToPacket(rawData);
if (data == null) { if (data == null) {
//TODO //TODO failure?
return; return;
} }
mInitManager.onData(data); mInitManager.onData(data);
//sendNext(); TODO
for (InputStickDataListener listener : mDataListeners) { for (InputStickDataListener listener : mDataListeners) {
listener.onInputStickData(data); listener.onInputStickData(data);
} }
@ -124,7 +105,7 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
public void connect(boolean reflection, int timeout) { public void connect(boolean reflection, int timeout) {
mErrorCode = ConnectionManager.ERROR_NONE; mErrorCode = InputStickError.ERROR_NONE;
if (mBTService == null) { if (mBTService == null) {
mBTService = new BTService(mApp, mBTHandler); mBTService = new BTService(mApp, mBTHandler);
mPacketManager = new PacketManager(mBTService, mKey); mPacketManager = new PacketManager(mBTService, mKey);
@ -143,10 +124,14 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
} }
} }
public void disconnect(int failureCode) {
onFailure(failureCode);
}
@Override @Override
public void sendPacket(Packet p) { public void sendPacket(Packet p) {
mPacketManager.sendPacket(p); //TODO tmp; zalozmy z beda same NO_RESP ??? mPacketManager.sendPacket(p);
} }

View File

@ -11,13 +11,6 @@ public abstract class ConnectionManager {
public static final int STATE_READY = 4; public static final int STATE_READY = 4;
public static final int ERROR_NONE = 0;
public static final int ERROR_UNSUPPORTED_FIRMWARE = 10;
public static final int ERROR_PASSWORD_PROTECTED = 11;
public static final int ERROR_INVALID_KEY = 12;
protected Vector<InputStickStateListener> mStateListeners = new Vector<InputStickStateListener>(); protected Vector<InputStickStateListener> mStateListeners = new Vector<InputStickStateListener>();
protected Vector<InputStickDataListener> mDataListeners = new Vector<InputStickDataListener>(); protected Vector<InputStickDataListener> mDataListeners = new Vector<InputStickDataListener>();
@ -47,9 +40,11 @@ public abstract class ConnectionManager {
public void addStateListener(InputStickStateListener listener) { public void addStateListener(InputStickStateListener listener) {
if (listener != null) { if (listener != null) {
if ( !mStateListeners.contains(listener)) {
mStateListeners.add(listener); mStateListeners.add(listener);
} }
} }
}
public void removeStateListener(InputStickStateListener listener) { public void removeStateListener(InputStickStateListener listener) {
if (listener != null) { if (listener != null) {
@ -59,9 +54,11 @@ public abstract class ConnectionManager {
public void addDataListener(InputStickDataListener listener) { public void addDataListener(InputStickDataListener listener) {
if (listener != null) { if (listener != null) {
if ( !mDataListeners.contains(listener)) {
mDataListeners.add(listener); mDataListeners.add(listener);
} }
} }
}
public void removeDataListener(InputStickDataListener listener) { public void removeDataListener(InputStickDataListener listener) {
if (listener != null) { if (listener != null) {

View File

@ -15,10 +15,16 @@ public class HIDInfo {
private boolean mouseReady; private boolean mouseReady;
private boolean consumerReady; private boolean consumerReady;
// >= 0.93
private boolean sentToHostInfo;
private int keyboardReportsSentToHost;
private int mouseReportsSentToHost;
private int consumerReportsSentToHost;
public HIDInfo() { public HIDInfo() {
keyboardReportProtocol = true; keyboardReportProtocol = true;
mouseReportProtocol = true; mouseReportProtocol = true;
sentToHostInfo = false;
} }
public void update(byte[] data) { public void update(byte[] data) {
@ -70,6 +76,14 @@ public class HIDInfo {
} else { } else {
consumerReady = true; consumerReady = true;
} }
if (data.length >= 12) {
if (data[11] == (byte)0xFF) {
sentToHostInfo = true;
keyboardReportsSentToHost = data[8] & 0xFF;
mouseReportsSentToHost = data[9] & 0xFF;
consumerReportsSentToHost = data[10] & 0xFF;
}
}
} }
public void setKeyboardBusy() { public void setKeyboardBusy() {
@ -112,4 +126,24 @@ public class HIDInfo {
return consumerReady; return consumerReady;
} }
// > v0.93 firmware only
public boolean isSentToHostInfoAvailable() {
return sentToHostInfo;
}
public int getKeyboardReportsSentToHost() {
return keyboardReportsSentToHost;
}
public int getMouseReportsSentToHost() {
return mouseReportsSentToHost;
}
public int getConsumerReportsSentToHost() {
return consumerReportsSentToHost;
}
} }

View File

@ -52,7 +52,6 @@ public class IPCConnectionManager extends ConnectionManager {
} }
break; break;
case SERVICE_CMD_STATE: case SERVICE_CMD_STATE:
//System.out.println("CMD STATE: "+msg.arg1);
manager.stateNotify(msg.arg1); manager.stateNotify(msg.arg1);
break; break;
} }
@ -61,7 +60,6 @@ public class IPCConnectionManager extends ConnectionManager {
private ServiceConnection mConnection = new ServiceConnection() { private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) { public void onServiceConnected(ComponentName className, IBinder service) {
//System.out.println("onServiceConnected!");
mService = new Messenger(service); mService = new Messenger(service);
mBound = true; mBound = true;
sendMessage(SERVICE_CMD_CONNECT, 0, 0); sendMessage(SERVICE_CMD_CONNECT, 0, 0);
@ -69,9 +67,10 @@ public class IPCConnectionManager extends ConnectionManager {
public void onServiceDisconnected(ComponentName className) { public void onServiceDisconnected(ComponentName className) {
// unexpectedly disconnected from service // unexpectedly disconnected from service
//System.out.println("onService DISCONNECTED!");
mService = null; mService = null;
mBound = false; mBound = false;
mErrorCode = InputStickError.ERROR_ANDROID_SERVICE_DISCONNECTED;
stateNotify(STATE_FAILURE);
stateNotify(STATE_DISCONNECTED); stateNotify(STATE_DISCONNECTED);
} }
}; };
@ -130,34 +129,32 @@ public class IPCConnectionManager extends ConnectionManager {
} }
if (exists) { if (exists) {
mErrorCode = ConnectionManager.ERROR_NONE; mErrorCode = InputStickError.ERROR_NONE;
Intent intent = new Intent(); Intent intent = new Intent();
intent.setComponent(new ComponentName("com.inputstick.apps.inputstickutility","com.inputstick.apps.inputstickutility.service.InputStickService")); intent.setComponent(new ComponentName("com.inputstick.apps.inputstickutility","com.inputstick.apps.inputstickutility.service.InputStickService"));
mCtx.startService(intent); mCtx.startService(intent);
mCtx.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); mCtx.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
if (mBound) { if (mBound) {
//already bound? //already bound
//System.out.println("Service already Connected");
sendMessage(SERVICE_CMD_CONNECT, 0, 0); sendMessage(SERVICE_CMD_CONNECT, 0, 0);
} }
} else { } else {
mErrorCode = 1; //TODO mErrorCode = InputStickError.ERROR_ANDROID_NO_UTILITY_APP;
stateNotify(STATE_FAILURE); stateNotify(STATE_FAILURE);
stateNotify(STATE_DISCONNECTED);
} }
} }
@Override @Override
public void disconnect() { public void disconnect() {
if (mBound) { if (mBound) {
//System.out.println("UNBIND");
sendMessage(SERVICE_CMD_DISCONNECT, 0, 0); sendMessage(SERVICE_CMD_DISCONNECT, 0, 0);
Intent intent = new Intent(); Intent intent = new Intent();
intent.setComponent(new ComponentName("com.inputstick.apps.inputstickutility","com.inputstick.apps.inputstickutility.service.InputStickService")); intent.setComponent(new ComponentName("com.inputstick.apps.inputstickutility","com.inputstick.apps.inputstickutility.service.InputStickService"));
mCtx.unbindService(mConnection); mCtx.unbindService(mConnection);
mCtx.stopService(intent); mCtx.stopService(intent);
mBound = false; mBound = false;
//TODO stateNotify //service will pass notification message (disconnected)
//service will pass notification message
} else { } else {
//just set state, there is nothing else to do //just set state, there is nothing else to do
stateNotify(STATE_DISCONNECTED); stateNotify(STATE_DISCONNECTED);
@ -176,5 +173,4 @@ public class IPCConnectionManager extends ConnectionManager {
} }
} }

View File

@ -22,6 +22,12 @@ public class Packet {
public static final byte CMD_FW_INFO = 0x10; public static final byte CMD_FW_INFO = 0x10;
public static final byte CMD_INIT = 0x11; public static final byte CMD_INIT = 0x11;
public static final byte CMD_INIT_AUTH = 0x12;
public static final byte CMD_INIT_CON = 0x13;
//public static final byte CMD_SET_KEY = 0x14;
public static final byte CMD_SET_VALUE = 0x14;
public static final byte CMD_RESTORE_DEFAULTS = 0x15;
public static final byte CMD_RESTORE_STATUS = 0x16;
public static final byte CMD_HID_STATUS_REPORT = 0x20; public static final byte CMD_HID_STATUS_REPORT = 0x20;
@ -37,6 +43,7 @@ public class Packet {
public static final byte RESP_OK = 0x01; public static final byte RESP_OK = 0x01;
public static final byte RESP_UNKNOWN_CMD = (byte)0xFF;
public static final byte[] RAW_OLD_BOOTLOADER = new byte[] {START_TAG, (byte)0x00, (byte)0x02, (byte)0x83, (byte)0x00, (byte)0xDA}; public static final byte[] RAW_OLD_BOOTLOADER = new byte[] {START_TAG, (byte)0x00, (byte)0x02, (byte)0x83, (byte)0x00, (byte)0xDA};
@ -119,4 +126,8 @@ public class Packet {
return mRespond; return mRespond;
} }
public void print() {
Util.printHex(mData, "PACKET DATA:");
}
} }

View File

@ -39,9 +39,13 @@ public class PacketManager {
} }
} }
public boolean isEncrypted() {
return mEncryption;
}
public Packet encPacket(boolean enable) { public Packet encPacket(boolean enable) {
Random r = new Random(); Random r = new Random();
Packet p = new Packet(true, Packet.CMD_INIT); Packet p = new Packet(true, Packet.CMD_INIT_AUTH);
if (enable) { if (enable) {
p.addByte((byte)1); p.addByte((byte)1);
} else { } else {
@ -68,13 +72,13 @@ public class PacketManager {
initData = mAes.encrypt(initData); initData = mAes.encrypt(initData);
p.addBytes(initData); p.addBytes(initData);
//Util.printHex(initData, "InitData: "); Util.printHex(initData, "InitData: ");
cmpData = new byte[16]; cmpData = new byte[16];
r.nextBytes(cmpData); r.nextBytes(cmpData);
p.addBytes(cmpData); p.addBytes(cmpData);
//Util.printHex(cmpData, "CmpData: "); Util.printHex(cmpData, "CmpData: ");
return p; return p;
} }
@ -87,11 +91,15 @@ public class PacketManager {
payload = Arrays.copyOfRange(data, 2, data.length); //remove TAG, info payload = Arrays.copyOfRange(data, 2, data.length); //remove TAG, info
if ((data[1] & Packet.FLAG_ENCRYPTED) != 0) { if ((data[1] & Packet.FLAG_ENCRYPTED) != 0) {
Util.log("DECRYPT"); //Util.log("DECRYPT");
if (mAes.isReady()) {
payload = mAes.decrypt(payload); payload = mAes.decrypt(payload);
} else {
return null;
}
} }
Util.printHex(payload, "DATA IN: "); //Util.printHex(payload, "DATA IN: ");
//check CRC //check CRC
crcCompare = Util.getLong(payload[0], payload[1], payload[2], payload[3]); crcCompare = Util.getLong(payload[0], payload[1], payload[2], payload[3]);
@ -114,8 +122,10 @@ public class PacketManager {
} }
public void sendPacket(Packet p) { public void sendPacket(Packet p) {
if (p != null) {
sendPacket(p, mEncryption); sendPacket(p, mEncryption);
} }
}
public void sendPacket(Packet p, boolean encrypt) { public void sendPacket(Packet p, boolean encrypt) {
byte[] result, header, data; byte[] result, header, data;
@ -137,7 +147,7 @@ public class PacketManager {
mCrc.reset(); mCrc.reset();
mCrc.update(result, CRC_OFFSET, result.length - CRC_OFFSET); mCrc.update(result, CRC_OFFSET, result.length - CRC_OFFSET);
crcValue = mCrc.getValue(); crcValue = mCrc.getValue();
Util.log("CRC: "+crcValue); //Util.log("CRC: "+crcValue);
result[3] = (byte)crcValue; result[3] = (byte)crcValue;
crcValue >>= 8; crcValue >>= 8;
result[2] = (byte)crcValue; result[2] = (byte)crcValue;

View File

@ -1,13 +1,25 @@
package com.inputstick.api; package com.inputstick.api;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public abstract class Util { public abstract class Util {
private static final boolean debug = false; public static boolean debug = false;
public static void log(String msg) { public static void log(String msg) {
log(msg, false);
}
public static void log(String msg, boolean displayTime) {
if (debug) { if (debug) {
System.out.println("LOG: " + msg); System.out.print("LOG: " + msg);
if (displayTime) {
System.out.print(" @ " + System.currentTimeMillis());
}
System.out.println();
} }
} }
@ -21,12 +33,14 @@ public abstract class Util {
public static void printHex(byte[] toPrint) { public static void printHex(byte[] toPrint) {
if (debug) { if (debug) {
if (toPrint != null) {
int cnt = 0; int cnt = 0;
String s; String s;
byte b; byte b;
for (int i = 0; i < toPrint.length; i++) { for (int i = 0; i < toPrint.length; i++) {
b = toPrint[i]; b = toPrint[i];
if ((b < 10) && (b >= 0)) { //0x0..0xF = 0x00..0x0F
if ((b < 0x10) && (b >= 0)) {
s = Integer.toHexString((int)b); s = Integer.toHexString((int)b);
s = "0" + s; s = "0" + s;
} else { } else {
@ -43,6 +57,10 @@ public abstract class Util {
cnt = 0; cnt = 0;
} }
} }
} else {
System.out.println("null");
}
System.out.println("\n#####"); System.out.println("\n#####");
} }
} }
@ -80,4 +98,17 @@ public abstract class Util {
} }
public static byte[] getPasswordBytes(String plainText) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
return md.digest(plainText.getBytes("UTF-8"));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
} }

View File

@ -1,35 +1,54 @@
package com.inputstick.api.basic; package com.inputstick.api.basic;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector; import java.util.Vector;
import android.app.AlertDialog;
import android.app.Application; import android.app.Application;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import com.inputstick.api.BTConnectionManager; import com.inputstick.api.BTConnectionManager;
import com.inputstick.api.ConnectionManager; import com.inputstick.api.ConnectionManager;
import com.inputstick.api.HIDInfo; import com.inputstick.api.HIDInfo;
import com.inputstick.api.IPCConnectionManager; import com.inputstick.api.IPCConnectionManager;
import com.inputstick.api.InputStickDataListener; import com.inputstick.api.InputStickDataListener;
import com.inputstick.api.InputStickError;
import com.inputstick.api.InputStickStateListener; import com.inputstick.api.InputStickStateListener;
import com.inputstick.api.OnEmptyBufferListener;
import com.inputstick.api.Packet; import com.inputstick.api.Packet;
import com.inputstick.api.Util;
import com.inputstick.api.hid.HIDTransaction; import com.inputstick.api.hid.HIDTransaction;
import com.inputstick.api.hid.HIDTransactionQueue; import com.inputstick.api.hid.HIDTransactionQueue;
import com.inputstick.init.InitManager; import com.inputstick.init.InitManager;
public class InputStickHID implements InputStickStateListener, InputStickDataListener { public class InputStickHID implements InputStickStateListener, InputStickDataListener {
//private static final String mTag = "InputStickBasic"; public static final int INTERFACE_KEYBOARD = 0;
public static final int INTERFACE_CONSUMER = 1;
public static final int INTERFACE_MOUSE = 2;
//private static final String mTag = "InputStickBasic";
private static ConnectionManager mConnectionManager; private static ConnectionManager mConnectionManager;
private static Vector<InputStickStateListener> mStateListeners = new Vector<InputStickStateListener>(); private static Vector<InputStickStateListener> mStateListeners = new Vector<InputStickStateListener>();
private static InputStickHID instance = new InputStickHID(); private static InputStickHID instance = new InputStickHID();
private static HIDInfo mHIDInfo = new HIDInfo(); private static HIDInfo mHIDInfo;
private static HIDTransactionQueue keyboardQueue; private static HIDTransactionQueue keyboardQueue;
private static HIDTransactionQueue mouseQueue; private static HIDTransactionQueue mouseQueue;
private static HIDTransactionQueue consumerQueue; private static HIDTransactionQueue consumerQueue;
// >= FW 0.93
private static Timer t1;
private static boolean constantUpdateMode;
private InputStickHID() { private InputStickHID() {
} }
@ -38,9 +57,11 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
} }
private static void init() { private static void init() {
keyboardQueue = new HIDTransactionQueue(HIDTransactionQueue.KEYBOARD, mConnectionManager); mHIDInfo = new HIDInfo();
mouseQueue = new HIDTransactionQueue(HIDTransactionQueue.MOUSE, mConnectionManager); constantUpdateMode = false;
consumerQueue = new HIDTransactionQueue(HIDTransactionQueue.CONSUMER, mConnectionManager); keyboardQueue = new HIDTransactionQueue(INTERFACE_KEYBOARD, mConnectionManager);
mouseQueue = new HIDTransactionQueue(INTERFACE_MOUSE, mConnectionManager);
consumerQueue = new HIDTransactionQueue(INTERFACE_CONSUMER, mConnectionManager);
mConnectionManager.addStateListener(instance); mConnectionManager.addStateListener(instance);
mConnectionManager.addDataListener(instance); mConnectionManager.addDataListener(instance);
@ -55,8 +76,6 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
//direct Bluetooth connection //direct Bluetooth connection
public static void connect(Application app, String mac, byte[] key) { public static void connect(Application app, String mac, byte[] key) {
//mConnectionManager = new BTConnectionManager(new BasicInitManager(key), app, mac, reflections, key);
//mConnectionManager = new BTConnectionManager(new BasicInitManager(key), app, mac, key);
mConnectionManager = new BTConnectionManager(new InitManager(key), app, mac, key); mConnectionManager = new BTConnectionManager(new InitManager(key), app, mac, key);
init(); init();
} }
@ -80,6 +99,16 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
} }
} }
public static int getErrorCode() {
if (mConnectionManager != null) {
return mConnectionManager.getErrorCode();
} else {
return InputStickError.ERROR_UNKNOWN;
}
}
public static boolean isReady() { public static boolean isReady() {
if (getState() == ConnectionManager.STATE_READY) { if (getState() == ConnectionManager.STATE_READY) {
return true; return true;
@ -90,9 +119,11 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
public static void addStateListener(InputStickStateListener listener) { public static void addStateListener(InputStickStateListener listener) {
if (listener != null) { if (listener != null) {
if ( !mStateListeners.contains(listener)) {
mStateListeners.add(listener); mStateListeners.add(listener);
} }
} }
}
public static void removeStateListener(InputStickStateListener listener) { public static void removeStateListener(InputStickStateListener listener) {
if (listener != null) { if (listener != null) {
@ -100,6 +131,22 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
} }
} }
public static void addBufferEmptyListener(OnEmptyBufferListener listener) {
if (listener != null) {
keyboardQueue.addBufferEmptyListener(listener);
mouseQueue.addBufferEmptyListener(listener);
consumerQueue.addBufferEmptyListener(listener);
}
}
public static void removeBufferEmptyListener(OnEmptyBufferListener listener) {
if (listener != null) {
keyboardQueue.removeBufferEmptyListener(listener);
mouseQueue.removeBufferEmptyListener(listener);
consumerQueue.removeBufferEmptyListener(listener);
}
}
public static void addKeyboardTransaction(HIDTransaction transaction) { public static void addKeyboardTransaction(HIDTransaction transaction) {
keyboardQueue.addTransaction(transaction); keyboardQueue.addTransaction(transaction);
} }
@ -112,6 +159,18 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
consumerQueue.addTransaction(transaction); consumerQueue.addTransaction(transaction);
} }
public static void clearKeyboardBuffer() {
keyboardQueue.clearBuffer();
}
public static void clearMouseBuffer() {
mouseQueue.clearBuffer();
}
public static void clearConsumerBuffer() {
consumerQueue.clearBuffer();
}
public static boolean sendPacket(Packet p) { public static boolean sendPacket(Packet p) {
if (mConnectionManager != null) { if (mConnectionManager != null) {
mConnectionManager.sendPacket(p); mConnectionManager.sendPacket(p);
@ -123,24 +182,70 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
@Override @Override
public void onStateChanged(int state) { public void onStateChanged(int state) {
if ((state == ConnectionManager.STATE_DISCONNECTED) && (t1 != null)) {
t1.cancel();
t1 = null;
}
for (InputStickStateListener listener : mStateListeners) { for (InputStickStateListener listener : mStateListeners) {
listener.onStateChanged(state); listener.onStateChanged(state);
} }
} }
public static boolean isKeyboardLocalBufferEmpty() {
return keyboardQueue.isLocalBufferEmpty();
}
public static boolean isMouseLocalBufferEmpty() {
return mouseQueue.isLocalBufferEmpty();
}
public static boolean isConsumerLocalBufferEmpty() {
return consumerQueue.isLocalBufferEmpty();
}
public static boolean isKeyboardRemoteBufferEmpty() {
return keyboardQueue.isRemoteBufferEmpty();
}
public static boolean isMouseRemoteBufferEmpty() {
return mouseQueue.isRemoteBufferEmpty();
}
public static boolean isConsumerRemoteBufferEmpty() {
return consumerQueue.isRemoteBufferEmpty();
}
@Override @Override
public void onInputStickData(byte[] data) { public void onInputStickData(byte[] data) {
if (data[0] == Packet.CMD_HID_STATUS) { if (data[0] == Packet.CMD_HID_STATUS) {
mHIDInfo.update(data); mHIDInfo.update(data);
if (mHIDInfo.isSentToHostInfoAvailable()) {
// >= FW 0.93
keyboardQueue.deviceReady(mHIDInfo, mHIDInfo.getKeyboardReportsSentToHost());
mouseQueue.deviceReady(mHIDInfo, mHIDInfo.getMouseReportsSentToHost());
consumerQueue.deviceReady(mHIDInfo, mHIDInfo.getConsumerReportsSentToHost());
if ( !constantUpdateMode) {
Util.log("Constatnt update mode enabled");
constantUpdateMode = true;
t1 = new Timer();
t1.schedule(new TimerTask() {
@Override
public void run() {
keyboardQueue.sendToBuffer(false);
mouseQueue.sendToBuffer(false);
consumerQueue.sendToBuffer(false);
}
}, 5,5);
}
} else {
//previous FW versions
if (mHIDInfo.isKeyboardReady()) { if (mHIDInfo.isKeyboardReady()) {
keyboardQueue.deviceReady(); keyboardQueue.deviceReady(null, 0);
} }
if (mHIDInfo.isMouseReady()) { if (mHIDInfo.isMouseReady()) {
mouseQueue.deviceReady(); mouseQueue.deviceReady(null, 0);
} }
if (mHIDInfo.isConsumerReady()) { if (mHIDInfo.isConsumerReady()) {
consumerQueue.deviceReady(); consumerQueue.deviceReady(null, 0);
}
} }
InputStickKeyboard.setLEDs(mHIDInfo.getNumLock(), mHIDInfo.getCapsLock(), mHIDInfo.getScrollLock()); InputStickKeyboard.setLEDs(mHIDInfo.getNumLock(), mHIDInfo.getCapsLock(), mHIDInfo.getScrollLock());
@ -148,5 +253,40 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
} }
public static AlertDialog getDownloadDialog(final Context ctx) {
if (mConnectionManager.getErrorCode() == InputStickError.ERROR_ANDROID_NO_UTILITY_APP) {
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(ctx);
downloadDialog.setTitle("No InputStickUtility app installed");
downloadDialog.setMessage("InputStickUtility is required to run this application. Download now?");
downloadDialog.setPositiveButton("Yes",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
final String appPackageName = "com.inputstick.apps.inputstickutility";
try {
ctx.startActivity(new Intent(
Intent.ACTION_VIEW, Uri
.parse("market://details?id="
+ appPackageName)));
} catch (android.content.ActivityNotFoundException anfe) {
ctx.startActivity(new Intent(
Intent.ACTION_VIEW,
Uri.parse("http://play.google.com/store/apps/details?id="
+ appPackageName)));
}
}
});
downloadDialog.setNegativeButton("No",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
});
return downloadDialog.show();
} else {
return null;
}
}
} }

View File

@ -2,6 +2,8 @@ package com.inputstick.api.basic;
import java.util.Vector; import java.util.Vector;
import android.util.SparseArray;
import com.inputstick.api.InputStickKeyboardListener; import com.inputstick.api.InputStickKeyboardListener;
import com.inputstick.api.hid.HIDKeycodes; import com.inputstick.api.hid.HIDKeycodes;
import com.inputstick.api.hid.HIDTransaction; import com.inputstick.api.hid.HIDTransaction;
@ -13,6 +15,10 @@ public class InputStickKeyboard {
private static final byte NONE = (byte)0; private static final byte NONE = (byte)0;
private static final byte LED_NUM_LOCK = 1;
private static final byte LED_CAPS_LOCK = 2;
private static final byte LED_SCROLL_LOCK = 4;
private static boolean mReportProtocol; private static boolean mReportProtocol;
private static boolean mNumLock; private static boolean mNumLock;
private static boolean mCapsLock; private static boolean mCapsLock;
@ -20,14 +26,25 @@ public class InputStickKeyboard {
private static Vector<InputStickKeyboardListener> mKeyboardListeners = new Vector<InputStickKeyboardListener>(); private static Vector<InputStickKeyboardListener> mKeyboardListeners = new Vector<InputStickKeyboardListener>();
private static final SparseArray<String> ledsMap;
static
{
ledsMap = new SparseArray<String>();
ledsMap.put(LED_NUM_LOCK, "NumLock");
ledsMap.put(LED_CAPS_LOCK, "CapsLock");
ledsMap.put(LED_SCROLL_LOCK, "ScrollLock");
}
private InputStickKeyboard() { private InputStickKeyboard() {
} }
public static void addKeyboardListener(InputStickKeyboardListener listener) { public static void addKeyboardListener(InputStickKeyboardListener listener) {
if (listener != null) { if (listener != null) {
if ( !mKeyboardListeners.contains(listener)) {
mKeyboardListeners.add(listener); mKeyboardListeners.add(listener);
} }
} }
}
public static void removeKeyboardListener(InputStickKeyboardListener listener) { public static void removeKeyboardListener(InputStickKeyboardListener listener) {
if (listener != null) { if (listener != null) {
@ -115,4 +132,24 @@ public class InputStickKeyboard {
InputStickHID.addKeyboardTransaction(t); InputStickHID.addKeyboardTransaction(t);
}*/ }*/
public static String ledsToString(byte leds) {
String result = "None";
boolean first = true;
byte mod;
for (int i = 0; i < 8; i++) {
mod = (byte)(LED_NUM_LOCK << i);
if ((leds & mod) != 0) {
if ( !first) {
result += ", ";
} else {
result = "";
}
first = false;
result += ledsMap.get(mod);
}
}
return result;
}
} }

View File

@ -1,5 +1,7 @@
package com.inputstick.api.basic; package com.inputstick.api.basic;
import android.util.SparseArray;
import com.inputstick.api.hid.HIDTransaction; import com.inputstick.api.hid.HIDTransaction;
import com.inputstick.api.hid.MouseReport; import com.inputstick.api.hid.MouseReport;
@ -12,6 +14,15 @@ public class InputStickMouse {
public static final byte BUTTON_RIGHT = 0x02; public static final byte BUTTON_RIGHT = 0x02;
public static final byte BUTTON_MIDDLE = 0x04; public static final byte BUTTON_MIDDLE = 0x04;
private static final SparseArray<String> buttonsMap;
static
{
buttonsMap = new SparseArray<String>();
buttonsMap.put(BUTTON_LEFT, "Left");
buttonsMap.put(BUTTON_RIGHT, "Right");
buttonsMap.put(BUTTON_MIDDLE, "Middle");
}
private static boolean mReportProtocol; private static boolean mReportProtocol;
private InputStickMouse() { private InputStickMouse() {
@ -54,4 +65,24 @@ public class InputStickMouse {
InputStickHID.addMouseTransaction(t); InputStickHID.addMouseTransaction(t);
} }
public static String buttonsToString(byte buttons) {
String result = "None";
boolean first = true;
byte mod;
for (int i = 0; i < 8; i++) {
mod = (byte)(BUTTON_LEFT << i);
if ((buttons & mod) != 0) {
if ( !first) {
result += ", ";
} else {
result = "";
}
first = false;
result += buttonsMap.get(mod);
}
}
return result;
}
} }

View File

@ -19,6 +19,7 @@ import android.os.Message;
import com.inputstick.api.Packet; import com.inputstick.api.Packet;
import com.inputstick.api.Util; import com.inputstick.api.Util;
import com.inputstick.api.InputStickError;
public class BTService { public class BTService {
@ -28,16 +29,7 @@ public class BTService {
public static final int EVENT_DATA = 1; public static final int EVENT_DATA = 1;
public static final int EVENT_CONNECTED = 2; public static final int EVENT_CONNECTED = 2;
public static final int EVENT_CANCELLED = 3; public static final int EVENT_CANCELLED = 3;
public static final int EVENT_CONNECTION_FAILED = 4; public static final int EVENT_ERROR = 4;
public static final int EVENT_CONNECTION_LOST = 5;
public static final int EVENT_NO_BT_HW = 6;
public static final int EVENT_INVALID_MAC = 7;
//TODO:
public static final int EVENT_CMD_TIMEOUT = 8;
public static final int EVENT_INTERVAL_TIMEOUT = 9;
public static final int EVENT_TURN_ON_TIMEOUT = 10;
public static final int EVENT_OTHER_ERROR = 11;
private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); //SPP private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); //SPP
@ -72,7 +64,6 @@ public class BTService {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
final String action = intent.getAction(); final String action = intent.getAction();
System.out.println("ACTION: "+action);
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
if ((state == BluetoothAdapter.STATE_ON) && (turnBluetoothOn)) { if ((state == BluetoothAdapter.STATE_ON) && (turnBluetoothOn)) {
@ -103,11 +94,11 @@ public class BTService {
mUseReflection = enabled; mUseReflection = enabled;
} }
private synchronized void event(int event) {
private synchronized void event(int event, int arg1) {
Util.log("event() " + mLastEvent + " -> " + event); Util.log("event() " + mLastEvent + " -> " + event);
mLastEvent = event; mLastEvent = event;
Message msg = Message.obtain(null, mLastEvent, arg1, 0);
Message msg = Message.obtain(null, mLastEvent, 0, 0);
mHandler.sendMessage(msg); mHandler.sendMessage(msg);
} }
@ -163,7 +154,7 @@ public class BTService {
if (BluetoothAdapter.checkBluetoothAddress(mac)) { if (BluetoothAdapter.checkBluetoothAddress(mac)) {
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) { if (mBluetoothAdapter == null) {
event(EVENT_NO_BT_HW); event(EVENT_ERROR, InputStickError.ERROR_BLUETOOTH_NOT_SUPPORTED);
} else { } else {
if (mBluetoothAdapter.isEnabled()) { if (mBluetoothAdapter.isEnabled()) {
doConnect(false); doConnect(false);
@ -172,7 +163,7 @@ public class BTService {
} }
} }
} else { } else {
event(EVENT_INVALID_MAC); event(EVENT_ERROR, InputStickError.ERROR_BLUETOOTH_INVALID_MAC);
} }
} }
@ -180,7 +171,7 @@ public class BTService {
Util.log("disconnect"); Util.log("disconnect");
disconnecting = true; disconnecting = true;
cancelThreads(); cancelThreads();
event(EVENT_CANCELLED); event(EVENT_CANCELLED, 0);
} }
@ -226,7 +217,7 @@ public class BTService {
mConnectedThread.start(); mConnectedThread.start();
connected = true; connected = true;
event(EVENT_CONNECTED); event(EVENT_CONNECTED, 0);
} }
@ -239,7 +230,7 @@ public class BTService {
Util.log("RETRY: "+retryCnt + " time left: " + (timeout - System.currentTimeMillis())); Util.log("RETRY: "+retryCnt + " time left: " + (timeout - System.currentTimeMillis()));
doConnect(true); doConnect(true);
} else { } else {
event(EVENT_CONNECTION_FAILED); event(EVENT_ERROR, InputStickError.ERROR_BLUETOOTH_CONNECTION_FAILED);
} }
} }
} }
@ -249,7 +240,7 @@ public class BTService {
if (disconnecting) { if (disconnecting) {
disconnecting = false; disconnecting = false;
} else { } else {
event(EVENT_CONNECTION_LOST); event(EVENT_ERROR, InputStickError.ERROR_BLUETOOTH_CONNECTION_LOST);
} }
} }
@ -357,10 +348,12 @@ public class BTService {
int rxTmp; int rxTmp;
int lengthByte; int lengthByte;
int length; int length;
int wdgCnt = 0;
while (true) { while (true) {
try { try {
rxTmp = mmInStream.read(); rxTmp = mmInStream.read();
if (rxTmp == Packet.START_TAG) { if (rxTmp == Packet.START_TAG) {
wdgCnt = 0;
lengthByte = mmInStream.read(); lengthByte = mmInStream.read();
length = lengthByte; length = lengthByte;
length &= 0x3F; length &= 0x3F;
@ -373,8 +366,13 @@ public class BTService {
} }
mHandler.obtainMessage(EVENT_DATA, 0, 0, buffer).sendToTarget(); mHandler.obtainMessage(EVENT_DATA, 0, 0, buffer).sendToTarget();
} else { } else {
System.out.println("RX: " + rxTmp); Util.log("Unexpected RX byte" + rxTmp);
//possible WDG reset if (rxTmp == 0xAF) {
wdgCnt++;
}
if (wdgCnt > 1024) {
//TODO
}
} }
} catch (IOException e) { } catch (IOException e) {
connectionLost(); connectionLost();
@ -388,7 +386,7 @@ public class BTService {
mmOutStream.write(buffer); mmOutStream.write(buffer);
mmOutStream.flush(); mmOutStream.flush();
} catch (IOException e) { } catch (IOException e) {
Util.log("Exception during write"); Util.log("write() exception");
} }
} }
@ -396,7 +394,7 @@ public class BTService {
try { try {
mmSocket.close(); mmSocket.close();
} catch (IOException e) { } catch (IOException e) {
Util.log("close() of connect socket failed"); Util.log("socket close() exception");
} }
} }
} }

View File

@ -1,5 +1,7 @@
package com.inputstick.api.hid; package com.inputstick.api.hid;
import android.util.SparseArray;
public class HIDKeycodes { public class HIDKeycodes {
public static final byte NONE = 0x00; public static final byte NONE = 0x00;
@ -13,6 +15,7 @@ public class HIDKeycodes {
public static final byte ALT_RIGHT = 0x40; public static final byte ALT_RIGHT = 0x40;
public static final byte GUI_RIGHT = (byte)0x80; public static final byte GUI_RIGHT = (byte)0x80;
public static final byte KEY_ENTER = 0x28; public static final byte KEY_ENTER = 0x28;
public static final byte KEY_ESCAPE = 0x29; public static final byte KEY_ESCAPE = 0x29;
public static final byte KEY_BACKSPACE = 0x2A; public static final byte KEY_BACKSPACE = 0x2A;
@ -79,6 +82,8 @@ public class HIDKeycodes {
public static final byte KEY_NUM_0 = 0x62; public static final byte KEY_NUM_0 = 0x62;
public static final byte KEY_NUM_DOT = 0x63; public static final byte KEY_NUM_DOT = 0x63;
public static final byte KEY_BACKSLASH_NON_US = 0x64;
public static final byte KEY_A = 0x04; public static final byte KEY_A = 0x04;
public static final byte KEY_B = 0x05; public static final byte KEY_B = 0x05;
public static final byte KEY_C = 0x06; public static final byte KEY_C = 0x06;
@ -126,6 +131,133 @@ public class HIDKeycodes {
public static final SparseArray<String> modifiersMap;
static
{
modifiersMap = new SparseArray<String>();
modifiersMap.put(CTRL_LEFT, "Left Ctrl");
modifiersMap.put(SHIFT_LEFT, "Left Shift");
modifiersMap.put(ALT_LEFT, "Left Alt");
modifiersMap.put(GUI_LEFT, "Left GUI");
modifiersMap.put(CTRL_RIGHT, "Right Ctrl");
modifiersMap.put(SHIFT_RIGHT, "Right Shift");
modifiersMap.put(ALT_RIGHT, "Right Alt");
modifiersMap.put(GUI_RIGHT, "Right GUI");
}
public static final SparseArray<String> keyMap;
static
{
keyMap = new SparseArray<String>();
keyMap.put(0, "None");
keyMap.put(KEY_ENTER, "Enter");
keyMap.put(KEY_ESCAPE , "Esc");
keyMap.put(KEY_BACKSPACE , "Backspace");
keyMap.put(KEY_TAB , "Tab");
keyMap.put(KEY_SPACEBAR , "Space");
keyMap.put(KEY_CAPS_LOCK , "CapsLock");
keyMap.put(KEY_1 , "1");
keyMap.put(KEY_2 , "2");
keyMap.put(KEY_3 , "3");
keyMap.put(KEY_4 , "4");
keyMap.put(KEY_5 , "5");
keyMap.put(KEY_6 , "6");
keyMap.put(KEY_7 , "7");
keyMap.put(KEY_8 , "8");
keyMap.put(KEY_9 , "9");
keyMap.put(KEY_0 , "0");
keyMap.put(KEY_F1 , "F1");
keyMap.put(KEY_F2 , "F2");
keyMap.put(KEY_F3 , "F3");
keyMap.put(KEY_F4 , "F4");
keyMap.put(KEY_F5 , "F5");
keyMap.put(KEY_F6 , "F6");
keyMap.put(KEY_F7 , "F7");
keyMap.put(KEY_F8 , "F8");
keyMap.put(KEY_F9 , "F9");
keyMap.put(KEY_F10 , "F10");
keyMap.put(KEY_F11 , "F11");
keyMap.put(KEY_F12 , "F12");
keyMap.put(KEY_PRINT_SCREEN , "Print Scrn");
keyMap.put(KEY_SCROLL_LOCK , "ScrollLock");
keyMap.put(KEY_PASUE , "Pause Break");
keyMap.put(KEY_INSERT , "Insert");
keyMap.put(KEY_HOME , "Home");
keyMap.put(KEY_PAGE_UP , "PageUp");
keyMap.put(KEY_DELETE , "Delete");
keyMap.put(KEY_END , "End");
keyMap.put(KEY_PAGE_DOWN , "PageDown");
keyMap.put(KEY_ARROW_RIGHT , "Right Arrow");
keyMap.put(KEY_ARROW_LEFT , "Left Arrow");
keyMap.put(KEY_ARROW_DOWN , "Down Arrow");
keyMap.put(KEY_ARROW_UP , "Up Arrow");
keyMap.put(KEY_NUM_LOCK , "NumLock");
keyMap.put(KEY_NUM_BACKSLASH , "Num /");
keyMap.put(KEY_NUM_STAR , "Num *");
keyMap.put(KEY_NUM_MINUS , "Num -");
keyMap.put(KEY_NUM_PLUS , "Num +");
keyMap.put(KEY_NUM_ENTER , "Num Enter");
keyMap.put(KEY_NUM_1 , "Num 1");
keyMap.put(KEY_NUM_2 , "Num 2");
keyMap.put(KEY_NUM_3 , "Num 3");
keyMap.put(KEY_NUM_4 , "Num 4");
keyMap.put(KEY_NUM_5 , "Num 5");
keyMap.put(KEY_NUM_6 , "Num 6");
keyMap.put(KEY_NUM_7 , "Num 7");
keyMap.put(KEY_NUM_8 , "Num 8");
keyMap.put(KEY_NUM_9 , "Num 9");
keyMap.put(KEY_NUM_0 , "Num 0");
keyMap.put(KEY_NUM_DOT , "Num .");
keyMap.put(KEY_A , "A");
keyMap.put(KEY_B , "B");
keyMap.put(KEY_C , "C");
keyMap.put(KEY_D , "D");
keyMap.put(KEY_E , "E");
keyMap.put(KEY_F , "F");
keyMap.put(KEY_G , "G");
keyMap.put(KEY_H , "H");
keyMap.put(KEY_I , "I");
keyMap.put(KEY_J , "J");
keyMap.put(KEY_K , "K");
keyMap.put(KEY_L , "L");
keyMap.put(KEY_M , "M");
keyMap.put(KEY_N , "N");
keyMap.put(KEY_O , "O");
keyMap.put(KEY_P , "P");
keyMap.put(KEY_Q , "Q");
keyMap.put(KEY_R , "R");
keyMap.put(KEY_S , "S");
keyMap.put(KEY_T , "T");
keyMap.put(KEY_U , "U");
keyMap.put(KEY_V , "V");
keyMap.put(KEY_W , "W");
keyMap.put(KEY_X , "X");
keyMap.put(KEY_Y , "Y");
keyMap.put(KEY_Z , "Z");
keyMap.put(KEY_MINUS , "-");
keyMap.put(KEY_EQUALS , "=");
keyMap.put(KEY_LEFT_BRACKET , "[");
keyMap.put(KEY_RIGHT_BRACKET , "]");
keyMap.put(KEY_BACKSLASH , "\\");
//keyMap.put(KEY_GRAVE , "`");
keyMap.put(KEY_SEMICOLON , ";");
keyMap.put(KEY_APOSTROPHE , "'");
keyMap.put(KEY_GRAVE , "`");
keyMap.put(KEY_COMA , ",");
keyMap.put(KEY_DOT , ".");
keyMap.put(KEY_SLASH , "/");
keyMap.put(KEY_APPLICATION , "Application");
}
public static final int[] ASCIItoHID = { public static final int[] ASCIItoHID = {
0, //000 0, //000
0, //001 0, //001
@ -257,6 +389,14 @@ public class HIDKeycodes {
0 //127 just in case... 0 //127 just in case...
}; };
public static char getChar(byte keyCode) {
for (int i = 0; i < ASCIItoHID.length; i++) {
if (ASCIItoHID[i] == keyCode) {
return (char)i;
}
}
return 0;
}
public static byte getKeyCode(char c) { public static byte getKeyCode(char c) {
return (byte)ASCIItoHID[c]; //TODO range return (byte)ASCIItoHID[c]; //TODO range
@ -266,7 +406,31 @@ public class HIDKeycodes {
return ASCIItoHID[c]; //TODO range return ASCIItoHID[c]; //TODO range
} }
public static String modifiersToString(byte modifiers) {
String result = "None";
boolean first = true;
byte mod;
for (int i = 0; i < 8; i++) {
mod = (byte)(CTRL_LEFT << i);
if ((modifiers & mod) != 0) {
if ( !first) {
result += ", ";
} else {
result = "";
}
first = false;
result += modifiersMap.get(mod);
}
}
return result;
}
public static String keyToString(byte key) {
String result = keyMap.get(key);
if (result == null) {
result = "Unknown";
}
return result;
}
} }

View File

@ -1,39 +1,71 @@
package com.inputstick.api.hid; package com.inputstick.api.hid;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector; import java.util.Vector;
import com.inputstick.api.ConnectionManager; import com.inputstick.api.ConnectionManager;
import com.inputstick.api.HIDInfo;
import com.inputstick.api.OnEmptyBufferListener;
import com.inputstick.api.Packet; import com.inputstick.api.Packet;
import com.inputstick.api.basic.InputStickHID;
public class HIDTransactionQueue { public class HIDTransactionQueue {
public static final int KEYBOARD = 1;
public static final int MOUSE = 2;
public static final int CONSUMER = 3;
private static final int BUFFER_SIZE = 32; private static final int BUFFER_SIZE = 32;
private static final int BT_DELAY = 50; //additional delay for BT overhead
private static final int MAX_PACKETS_PER_UPDATE = 10;
private static final int MAX_IMMEDIATE_PACKETS = 3;
private final Vector<HIDTransaction> queue; private final Vector<HIDTransaction> queue;
private final ConnectionManager mConnectionManager; private final ConnectionManager mConnectionManager;
private final byte cmd; private final byte cmd;
private boolean ready; private boolean ready;
private int mInterfaceType;
private boolean mustNotify;
private Vector<OnEmptyBufferListener> mBufferEmptyListeners = new Vector<OnEmptyBufferListener>();
private Timer t;
private boolean timerCancelled;
private boolean sentAhead;
private long lastTime; private long lastTime;
private long minNextTime;
private int lastReports; private int lastReports;
public HIDTransactionQueue(int type, ConnectionManager connectionManager) { // >= FW 0.93
private boolean bufferInitDone;
private boolean constantUpdateMode;
private int bufferFreeSpace;
private int immediatePacketsLeft;
//private int reportsSentSinceLastUpdate;
private int packetsSentSinceLastUpdate;
public HIDTransactionQueue(int interfaceType, ConnectionManager connectionManager) {
constantUpdateMode = false;
bufferFreeSpace = BUFFER_SIZE;
queue = new Vector<HIDTransaction>(); queue = new Vector<HIDTransaction>();
mConnectionManager = connectionManager; mConnectionManager = connectionManager;
ready = false; ready = false;
switch (type) { sentAhead = false;
case KEYBOARD: minNextTime = 0;
mustNotify = false;
mInterfaceType = interfaceType;
switch (interfaceType) {
case InputStickHID.INTERFACE_KEYBOARD:
cmd = Packet.CMD_HID_DATA_KEYB; cmd = Packet.CMD_HID_DATA_KEYB;
break; break;
case MOUSE: case InputStickHID.INTERFACE_MOUSE:
cmd = Packet.CMD_HID_DATA_MOUSE; cmd = Packet.CMD_HID_DATA_MOUSE;
break; break;
case CONSUMER: case InputStickHID.INTERFACE_CONSUMER:
cmd = Packet.CMD_HID_DATA_CONSUMER; cmd = Packet.CMD_HID_DATA_CONSUMER;
break; break;
default: default:
@ -41,26 +73,32 @@ public class HIDTransactionQueue {
} }
} }
private void sendNext() { private int sendNext(int maxReports) {
HIDTransaction transaction; HIDTransaction transaction;
byte reports = 0;
ready = false;
Packet p = new Packet(false, cmd, reports);
//assume there is at least 1 element in queue //assume there is at least 1 element in queue
transaction = queue.firstElement(); transaction = queue.firstElement();
if (transaction.getReportsCount() > BUFFER_SIZE) { if (transaction.getReportsCount() > maxReports) {
//transaction too big! split // v0.92
if (maxReports < BUFFER_SIZE) {
//don't split transactions until there is no other way left!
return 0;
}
//transaction too big to fit single packet! split
transaction = transaction.split(BUFFER_SIZE); transaction = transaction.split(BUFFER_SIZE);
} else { } else {
queue.removeElementAt(0); queue.removeElementAt(0);
} }
byte reports = 0;
ready = false;
Packet p = new Packet(false, cmd, reports);
while (transaction.hasNext()) { while (transaction.hasNext()) {
p.addBytes(transaction.getNextReport()); p.addBytes(transaction.getNextReport());
reports++; reports++;
} }
//TODO add next transactions if possible
while(true) { while(true) {
if (queue.isEmpty()) { if (queue.isEmpty()) {
@ -68,7 +106,7 @@ public class HIDTransactionQueue {
} }
transaction = queue.firstElement(); transaction = queue.firstElement();
if (reports + transaction.getReportsCount() < BUFFER_SIZE) { if (reports + transaction.getReportsCount() < maxReports) {
queue.removeElementAt(0); queue.removeElementAt(0);
while (transaction.hasNext()) { while (transaction.hasNext()) {
p.addBytes(transaction.getNextReport()); p.addBytes(transaction.getNextReport());
@ -79,32 +117,182 @@ public class HIDTransactionQueue {
} }
} }
//!! total number of reports must be < 32 ! (max packet limitation)
p.modifyByte(1, reports); //set reports count p.modifyByte(1, reports); //set reports count
mConnectionManager.sendPacket(p); mConnectionManager.sendPacket(p);
lastReports = reports; lastReports = reports;
lastTime = System.currentTimeMillis(); lastTime = System.currentTimeMillis();
minNextTime = lastTime + (lastReports * 4) + BT_DELAY;
if (queue.isEmpty()) {
notifyOnLocalBufferEmpty();
} }
public void addTransaction(HIDTransaction transaction) { return reports;
if (queue.isEmpty()) {
if (System.currentTimeMillis() > lastTime + (lastReports * 8 * 2/*just to be safe*/)) {
ready = true;
} }
public void addBufferEmptyListener(OnEmptyBufferListener listener) {
if (listener != null) {
if ( !mBufferEmptyListeners.contains(listener)) {
mBufferEmptyListeners.add(listener);
}
}
}
public void removeBufferEmptyListener(OnEmptyBufferListener listener) {
if (listener != null) {
mBufferEmptyListeners.remove(listener);
}
}
private void notifyOnRemoteBufferEmpty() {
for (OnEmptyBufferListener listener : mBufferEmptyListeners) {
listener.onRemoteBufferEmpty(mInterfaceType);
}
}
private void notifyOnLocalBufferEmpty() {
for (OnEmptyBufferListener listener : mBufferEmptyListeners) {
listener.onRemoteBufferEmpty(mInterfaceType);
}
}
public synchronized boolean isLocalBufferEmpty() {
return queue.isEmpty();
}
public synchronized boolean isRemoteBufferEmpty() {
if ((queue.isEmpty()) && (bufferFreeSpace == BUFFER_SIZE)) {
return true;
}
if (queue.isEmpty() && ( !mustNotify)) {
return true;
} else {
return false;
}
}
public synchronized void clearBuffer() {
queue.removeAllElements();
}
public synchronized void addTransaction(HIDTransaction transaction) {
if ( !bufferInitDone) {
queue.add(transaction);
return;
}
if (constantUpdateMode) {
queue.add(transaction);
sendToBuffer(true);
return;
}
mustNotify = true;
//using sentAhead will slow down mouse. FW0.92 will solve the problems
if ((queue.isEmpty()) && (System.currentTimeMillis() > minNextTime) /*&& ( !sentAhead)*/) {
sentAhead = true;
ready = true;
} }
queue.add(transaction); queue.add(transaction);
if (ready) { if (ready) {
sendNext(); sendNext(BUFFER_SIZE);
} }
} }
public void deviceReady() { private synchronized void timerAction() {
if ( !timerCancelled) {
if (sentAhead) {
deviceReady(null, 0); //will set sentAhead to false;
sentAhead = true; //restore value
} else {
deviceReady(null, 0);
}
}
}
public synchronized void deviceReady(HIDInfo hidInfo, int reportsSentToHost) {
//it is possible that in the meantime some packets has been sent to IS!!!
bufferInitDone = true;
if (hidInfo != null) {
if (hidInfo.isSentToHostInfoAvailable()) {
constantUpdateMode = true;
// >= FW 0.93
bufferFreeSpace += reportsSentToHost;
if ((bufferFreeSpace == BUFFER_SIZE) && (queue.isEmpty())) {
notifyOnRemoteBufferEmpty();
}
immediatePacketsLeft = MAX_IMMEDIATE_PACKETS;
//reportsSentSinceLastUpdate = 0;
packetsSentSinceLastUpdate = 0;
sendToBuffer(false);
return;
}
}
long now = System.currentTimeMillis();
//System.out.println("v90 HID update");
if (now < minNextTime) {
//set timer, just in case if deviceReady won't be called again
timerCancelled = false;
t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
timerAction();
}
}, (minNextTime - now + 1));
} else {
timerCancelled = true;
sentAhead = false;
if (!queue.isEmpty()) { if (!queue.isEmpty()) {
sendNext(); sendNext(BUFFER_SIZE);
} else { } else {
ready = true; ready = true;
//queue is empty, InputStick reported that buffer is empty, data was added since last notification
if (mustNotify) {
notifyOnRemoteBufferEmpty();
mustNotify = false;
}
}
}
}
public synchronized void sendToBuffer(boolean justAdded) {
if ((justAdded) && (immediatePacketsLeft <= 0)) {
return;
}
if ( !InputStickHID.isReady()) {
return;
}
if (queue.isEmpty()) {
return;
}
if (bufferFreeSpace <= 0) {
return;
}
if (packetsSentSinceLastUpdate >= MAX_PACKETS_PER_UPDATE) {
return;
}
int reportsSent = sendNext(bufferFreeSpace);
if (reportsSent > 0) {
if (justAdded) {
immediatePacketsLeft --;
}
bufferFreeSpace -= reportsSent;
packetsSentSinceLastUpdate ++;
} }
} }

View File

@ -110,6 +110,49 @@ public class GermanLayout extends KeyboardLayout {
}; };
public static final int DEADKEYS[] = {
0x0060, 0x00b4, 0x005e
};
public static final int DEADKEY_LUT[][] = {
{ 0x00b4 , 0x0079 , 0x00fd } ,
{ 0x00b4 , 0x0061 , 0x00e1 } ,
{ 0x00b4 , 0x0065 , 0x00e9 } ,
{ 0x00b4 , 0x0075 , 0x00fa } ,
{ 0x00b4 , 0x0069 , 0x00ed } ,
{ 0x00b4 , 0x006f , 0x00f3 } ,
{ 0x00b4 , 0x0059 , 0x00dd } ,
{ 0x00b4 , 0x0041 , 0x00c1 } ,
{ 0x00b4 , 0x0045 , 0x00c9 } ,
{ 0x00b4 , 0x0055 , 0x00da } ,
{ 0x00b4 , 0x0049 , 0x00cd } ,
{ 0x00b4 , 0x004f , 0x00d3 } ,
{ 0x00b4 , 0x0020 , 0x00b4 } ,
{ 0x0060 , 0x0061 , 0x00e0 } ,
{ 0x0060 , 0x0065 , 0x00e8 } ,
{ 0x0060 , 0x0075 , 0x00f9 } ,
{ 0x0060 , 0x0069 , 0x00ec } ,
{ 0x0060 , 0x006f , 0x00f2 } ,
{ 0x0060 , 0x0041 , 0x00c0 } ,
{ 0x0060 , 0x0045 , 0x00c8 } ,
{ 0x0060 , 0x0055 , 0x00d9 } ,
{ 0x0060 , 0x0049 , 0x00cc } ,
{ 0x0060 , 0x004f , 0x00d2 } ,
{ 0x0060 , 0x0020 , 0x0060 } ,
{ 0x005e , 0x0061 , 0x00e2 } ,
{ 0x005e , 0x0065 , 0x00ea } ,
{ 0x005e , 0x0075 , 0x00fb } ,
{ 0x005e , 0x0069 , 0x00ee } ,
{ 0x005e , 0x006f , 0x00f4 } ,
{ 0x005e , 0x0041 , 0x00c2 } ,
{ 0x005e , 0x0045 , 0x00ca } ,
{ 0x005e , 0x0055 , 0x00db } ,
{ 0x005e , 0x0049 , 0x00ce } ,
{ 0x005e , 0x004f , 0x00d4 } ,
{ 0x005e , 0x0020 , 0x005e } ,
};
private static GermanLayout instance = new GermanLayout(); private static GermanLayout instance = new GermanLayout();
private GermanLayout() { private GermanLayout() {
@ -126,7 +169,12 @@ public class GermanLayout extends KeyboardLayout {
@Override @Override
public void type(String text) { public void type(String text) {
super.type(LUT, text); super.type(LUT, DEADKEY_LUT, DEADKEYS, text, (byte)0);
}
@Override
public void type(String text, byte modifiers) {
super.type(LUT, DEADKEY_LUT, DEADKEYS, text, modifiers);
} }
@Override @Override
@ -139,4 +187,14 @@ public class GermanLayout extends KeyboardLayout {
return LOCALE_NAME; return LOCALE_NAME;
} }
@Override
public int[][] getDeadkeyLUT() {
return DEADKEY_LUT;
}
@Override
public int[] getDeadkeys() {
return DEADKEYS;
}
} }

View File

@ -101,7 +101,7 @@ public abstract class KeyboardLayout {
/* 0x53 */ HIDKeycodes.KEY_DELETE, /* 0x53 */ HIDKeycodes.KEY_DELETE,
/* 0x54 */ 0, /* 0x54 */ 0,
/* 0x55 */ 0, /* 0x55 */ 0,
/* 0x56 */ 0, /* 0x56 */ HIDKeycodes.KEY_BACKSLASH_NON_US, //GERMAN LAYOUT!
/* 0x57 */ HIDKeycodes.KEY_F11, /* 0x57 */ HIDKeycodes.KEY_F11,
/* 0x58 */ HIDKeycodes.KEY_F12, /* 0x58 */ HIDKeycodes.KEY_F12,
/* 0x59 */ 0, /* 0x59 */ 0,
@ -117,16 +117,19 @@ public abstract class KeyboardLayout {
public static final int LAYOUT_CODE = 0; public static final int LAYOUT_CODE = 0;
public abstract int[][] getLUT(); public abstract int[][] getLUT();
public abstract int[][] getDeadkeyLUT();
public abstract int[] getDeadkeys();
public abstract String getLocaleName(); public abstract String getLocaleName();
public abstract void type(String text); public abstract void type(String text);
public abstract void type(String text, byte modifiers);
public abstract char getChar(int scanCode, boolean capsLock, boolean shift, boolean altGr); public abstract char getChar(int scanCode, boolean capsLock, boolean shift, boolean altGr);
public void type(int[][] lut, String text) { public void type(int[][] lut, int[][] deadkeyLUT, int[] deadkeys, String text, byte modifiers) {
if (InputStickHID.getState() == ConnectionManager.STATE_READY) { if (InputStickHID.getState() == ConnectionManager.STATE_READY) {
char[] chars = text.toCharArray(); char[] chars = text.toCharArray();
HIDTransaction t; HIDTransaction t;
for (char c : chars) { for (char c : chars) {
t = getHIDTransaction(lut, c); t = getHIDTransaction(lut, deadkeyLUT, deadkeys, c, modifiers);
if (t != null) { if (t != null) {
InputStickHID.addKeyboardTransaction(t); InputStickHID.addKeyboardTransaction(t);
} }
@ -194,7 +197,7 @@ public abstract class KeyboardLayout {
} }
public static int getScanCode(int[][] lut, char c) { public static int getScanCode(int[][] lut, char c) {
for (int scanCode = 0; scanCode < 80; scanCode++) { for (int scanCode = 0; scanCode < 0x60; scanCode++) {
if (lut[scanCode][0] == -1) { if (lut[scanCode][0] == -1) {
continue; continue;
} else { } else {
@ -233,7 +236,37 @@ public abstract class KeyboardLayout {
} }
public static HIDTransaction getHIDTransaction(int[][] lut, char c) { public static boolean isDeadkey(int[] deadkeys, char c) {
if (deadkeys != null) {
for (int key : deadkeys) {
if (key == (int)c) {
return true;
}
}
}
return false;
}
public static int searchLUT(int[][] deadkeyLUT, char c, int returnIndex) {
if (deadkeyLUT != null) {
for (int i = 0; i < deadkeyLUT.length; i++) {
if (deadkeyLUT[i][2] == (int)c) {
return deadkeyLUT[i][returnIndex];
}
}
}
return -1;
}
public static int findDeadKey(int[][] deadkeyLUT, char c) {
return searchLUT(deadkeyLUT, c, 0);
}
public static int findFollowingKey(int[][] deadkeyLUT, char c) {
return searchLUT(deadkeyLUT, c, 1);
}
public static HIDTransaction getHIDTransaction(int[][] lut, int[][] deadkeyLUT, int[] deadkeys, char c, byte additionalModifierKeys) {
byte modifiers, key; byte modifiers, key;
int scanCode; int scanCode;
@ -242,10 +275,40 @@ public abstract class KeyboardLayout {
if (scanCode > 0) { if (scanCode > 0) {
key = getKey(scanCode); key = getKey(scanCode);
modifiers = getModifiers(lut, scanCode, c); modifiers = getModifiers(lut, scanCode, c);
modifiers |= additionalModifierKeys;
t.addReport(new KeyboardReport(modifiers, (byte)0)); t.addReport(new KeyboardReport(modifiers, (byte)0));
t.addReport(new KeyboardReport(modifiers, key)); t.addReport(new KeyboardReport(modifiers, key));
t.addReport(new KeyboardReport()); t.addReport(new KeyboardReport());
//add space after deadkey!
if (isDeadkey(deadkeys, c)) {
t.addReport(new KeyboardReport((byte)0, HIDKeycodes.KEY_SPACEBAR)); //this won't work if modifiers are present!
t.addReport(new KeyboardReport());
}
} else {
//check if character can be obtained using deadkey:
int deadkey = findDeadKey(deadkeyLUT, c);
if (deadkey > 0) {
//yes it can
int following = findFollowingKey(deadkeyLUT, c);
scanCode = getScanCode(lut, (char)deadkey);
key = getKey(scanCode);
modifiers = getModifiers(lut, scanCode, (char)deadkey);
t.addReport(new KeyboardReport(modifiers, (byte)0));
t.addReport(new KeyboardReport(modifiers, key));
t.addReport(new KeyboardReport());
scanCode = getScanCode(lut, (char)following);
key = getKey(scanCode);
modifiers = getModifiers(lut, scanCode, (char)following);
t.addReport(new KeyboardReport(modifiers, (byte)0));
t.addReport(new KeyboardReport(modifiers, key));
t.addReport(new KeyboardReport());
}
} }
return t; return t;
} }
@ -260,7 +323,22 @@ public abstract class KeyboardLayout {
return RussianLayout.getInstance(); return RussianLayout.getInstance();
} else if (locale.equals(GermanLayout.getInstance().getLocaleName())) { } else if (locale.equals(GermanLayout.getInstance().getLocaleName())) {
return GermanLayout.getInstance(); return GermanLayout.getInstance();
} else if (locale.equals(SlovakLayout.getInstance().getLocaleName())) {
return SlovakLayout.getInstance();
} else if (locale.equals(PortugueseBrazilianLayout.getInstance().getLocaleName())) {
return PortugueseBrazilianLayout.getInstance();
} else if (locale.equals(DvorakLayout.getInstance().getLocaleName())) {
return DvorakLayout.getInstance();
} else if (locale.equals(NorwegianLayout.getInstance().getLocaleName())) {
return NorwegianLayout.getInstance();
} else if (locale.equals(SwedishLayout.getInstance().getLocaleName())) {
return SwedishLayout.getInstance();
} else if (locale.equals(FrenchLayout.getInstance().getLocaleName())) {
return FrenchLayout.getInstance();
} else if (locale.equals(SpanishLayout.getInstance().getLocaleName())) {
return SpanishLayout.getInstance();
} }
} }
return UnitedStatesLayout.getInstance(); return UnitedStatesLayout.getInstance();

View File

@ -111,6 +111,32 @@ public class PolishLayout extends KeyboardLayout {
}; };
public static final int DEADKEYS[] = {
0x007e
};
public static final int DEADKEY_LUT[][] = {
{ 0x007e , 0x006e , 0x0144 } ,
{ 0x007e , 0x0063 , 0x0107 } ,
{ 0x007e , 0x0078 , 0x017a } ,
{ 0x007e , 0x007a , 0x017c } ,
{ 0x007e , 0x0061 , 0x0105 } ,
{ 0x007e , 0x0073 , 0x015b } ,
{ 0x007e , 0x006c , 0x0142 } ,
{ 0x007e , 0x0065 , 0x0119 } ,
{ 0x007e , 0x006f , 0x00f3 } ,
{ 0x007e , 0x004e , 0x0143 } ,
{ 0x007e , 0x0043 , 0x0106 } ,
{ 0x007e , 0x0058 , 0x0179 } ,
{ 0x007e , 0x005a , 0x017b } ,
{ 0x007e , 0x0041 , 0x0104 } ,
{ 0x007e , 0x0053 , 0x015a } ,
{ 0x007e , 0x004c , 0x0141 } ,
{ 0x007e , 0x0045 , 0x0118 } ,
{ 0x007e , 0x004f , 0x00d3 } ,
{ 0x007e , 0x0020 , 0x007e } ,
};
private static PolishLayout instance = new PolishLayout(); private static PolishLayout instance = new PolishLayout();
private PolishLayout() { private PolishLayout() {
@ -127,7 +153,12 @@ public class PolishLayout extends KeyboardLayout {
@Override @Override
public void type(String text) { public void type(String text) {
super.type(LUT, text); super.type(LUT, DEADKEY_LUT, DEADKEYS, text, (byte)0);
}
@Override
public void type(String text, byte modifiers) {
super.type(LUT, DEADKEY_LUT, DEADKEYS, text, modifiers);
} }
@Override @Override
@ -140,5 +171,14 @@ public class PolishLayout extends KeyboardLayout {
return LOCALE_NAME; return LOCALE_NAME;
} }
@Override
public int[][] getDeadkeyLUT() {
return DEADKEY_LUT;
}
@Override
public int[] getDeadkeys() {
return DEADKEYS;
}
} }

View File

@ -112,6 +112,9 @@ public class RussianLayout extends KeyboardLayout {
}; };
public static final int DEADKEYS[] = null;
public static final int DEADKEY_LUT[][] = null;
private static RussianLayout instance = new RussianLayout(); private static RussianLayout instance = new RussianLayout();
private RussianLayout() { private RussianLayout() {
@ -128,7 +131,12 @@ public class RussianLayout extends KeyboardLayout {
@Override @Override
public void type(String text) { public void type(String text) {
super.type(LUT, text); super.type(LUT, DEADKEY_LUT, DEADKEYS, text, (byte)0);
}
@Override
public void type(String text, byte modifiers) {
super.type(LUT, DEADKEY_LUT, DEADKEYS, text, modifiers);
} }
@Override @Override
@ -141,4 +149,14 @@ public class RussianLayout extends KeyboardLayout {
return LOCALE_NAME; return LOCALE_NAME;
} }
@Override
public int[][] getDeadkeyLUT() {
return DEADKEY_LUT;
}
@Override
public int[] getDeadkeys() {
return DEADKEYS;
}
} }

View File

@ -114,6 +114,9 @@ public class UnitedStatesLayout extends KeyboardLayout {
}; };
public static final int DEADKEYS[] = null;
public static final int DEADKEY_LUT[][] = null;
private static UnitedStatesLayout instance = new UnitedStatesLayout(); private static UnitedStatesLayout instance = new UnitedStatesLayout();
private UnitedStatesLayout() { private UnitedStatesLayout() {
@ -130,7 +133,12 @@ public class UnitedStatesLayout extends KeyboardLayout {
@Override @Override
public void type(String text) { public void type(String text) {
super.type(LUT, text); super.type(LUT, DEADKEY_LUT, DEADKEYS, text, (byte)0);
}
@Override
public void type(String text, byte modifiers) {
super.type(LUT, DEADKEY_LUT, DEADKEYS, text, modifiers);
} }
@Override @Override
@ -143,4 +151,14 @@ public class UnitedStatesLayout extends KeyboardLayout {
return LOCALE_NAME; return LOCALE_NAME;
} }
@Override
public int[][] getDeadkeyLUT() {
return DEADKEY_LUT;
}
@Override
public int[] getDeadkeys() {
return DEADKEYS;
}
} }

View File

@ -5,8 +5,6 @@ import com.inputstick.api.Packet;
public class BasicInitManager extends InitManager { public class BasicInitManager extends InitManager {
private boolean initDone = false;
public BasicInitManager(byte[] key) { public BasicInitManager(byte[] key) {
super(key); super(key);
} }
@ -14,38 +12,36 @@ public class BasicInitManager extends InitManager {
@Override @Override
public void onConnected() { public void onConnected() {
/*Packet p = new Packet(false, Packet.RAW_OLD_BOOTLOADER); //compatibility /*Packet p = new Packet(false, Packet.RAW_OLD_BOOTLOADER); //compatibility with old protocol version
sendPacket(p);*/ sendPacket(p);*/
sendPacket(new Packet(true, Packet.CMD_RUN_FW)); sendPacket(new Packet(true, Packet.CMD_RUN_FW));
} }
@Override @Override
public void onData(byte[] data) { public void onData(byte[] data) {
byte cmd = data[0]; byte cmd = data[0];
byte respCode = data[1]; byte respCode = data[1];
byte param = data[1]; byte param = data[1];
if (cmd == Packet.CMD_RUN_FW) { switch (cmd) {
sendPacket(new Packet(true, Packet.CMD_GET_INFO)); case Packet.CMD_RUN_FW:
} sendPacket(new Packet(true, Packet.CMD_FW_INFO));
break;
if (cmd == Packet.CMD_GET_INFO) { case Packet.CMD_FW_INFO:
//store info onFWInfo(data, true, true, new Packet(true, Packet.CMD_INIT)); //TODO next FW: params!
sendPacket(new Packet(true, Packet.CMD_INIT)); //TODO params! break;
} case Packet.CMD_INIT:
if (cmd == Packet.CMD_INIT) {
if (respCode == Packet.RESP_OK) { if (respCode == Packet.RESP_OK) {
initDone = true; initDone = true;
sendPacket(new Packet(false, Packet.CMD_HID_STATUS_REPORT)); sendPacket(new Packet(true, Packet.CMD_HID_STATUS_REPORT));
} else { } else {
mListener.onInitFailure(respCode); mListener.onInitFailure(respCode);
} }
} break;
case Packet.CMD_INIT_AUTH:
if (cmd == Packet.CMD_HID_STATUS) { onAuth(data, true, new Packet(true, Packet.CMD_INIT)); //TODO next FW: params!
break;
case Packet.CMD_HID_STATUS:
if (initDone) { if (initDone) {
if (param == 0x05) { if (param == 0x05) {
mListener.onInitReady(); mListener.onInitReady();
@ -53,9 +49,9 @@ public class BasicInitManager extends InitManager {
mListener.onInitNotReady(); mListener.onInitNotReady();
} }
} }
break;
} }
} }
} }

View File

@ -1,33 +1,125 @@
package com.inputstick.init; package com.inputstick.init;
import java.util.Timer;
import java.util.TimerTask;
import com.inputstick.api.InputStickError;
import com.inputstick.api.Packet; import com.inputstick.api.Packet;
import com.inputstick.api.PacketManager; import com.inputstick.api.PacketManager;
public class InitManager { public class InitManager {
public static final int DEFAULT_INIT_TIMEOUT = 60000; //60s init timeout
protected PacketManager mPacketManager; protected PacketManager mPacketManager;
protected InitManagerListener mListener; protected InitManagerListener mListener;
protected byte[] mKey; protected byte[] mKey;
protected DeviceInfo mInfo;
protected boolean initDone;
//private Timer t;
public InitManager(byte[] key) { public InitManager(byte[] key) {
mKey = key; mKey = key;
} }
public DeviceInfo getDeviceInfo() {
return mInfo;
}
public boolean isEncrypted() {
return mPacketManager.isEncrypted();
}
public void init(InitManagerListener listener, PacketManager packetManager) { public void init(InitManagerListener listener, PacketManager packetManager) {
mListener = listener; mListener = listener;
mPacketManager = packetManager; mPacketManager = packetManager;
initDone = false;
} }
//WRONG THREAD!
/*public void startTimeoutCountdown(int timeout) {
t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
if ( !initDone) {
mListener.onInitFailure(InputStickError.ERROR_INIT_TIMEDOUT);
}
}
}, timeout);
}*/
public void onConnected() { public void onConnected() {
mListener.onInitReady(); mListener.onInitReady();
} }
public void onData(byte[] data) { public void onData(byte[] data) {
//byte cmd = data[0];
//byte param = data[1];
} }
public void sendPacket(Packet p) { public void sendPacket(Packet p) {
mPacketManager.sendPacket(p); mPacketManager.sendPacket(p);
} }
public void onFWInfo(byte[] data, boolean authenticate, boolean enableEncryption, Packet sendNext) {
mInfo = new DeviceInfo(data);
if (authenticate) {
if (mInfo.isPasswordProtected()) {
if (mKey != null) {
//authenticate
sendPacket(mPacketManager.encPacket(enableEncryption));
} else {
mListener.onInitFailure(InputStickError.ERROR_SECURITY_NO_KEY);
}
} else {
if (mKey != null) {
//possible scenarios: FW upgrade / password removed using other device/app / tampering!
mListener.onInitFailure(InputStickError.ERROR_SECURITY_NOT_PROTECTED);
}
sendPacket(sendNext);
}
} else {
sendPacket(sendNext);
}
}
public void onAuth(byte[] data, boolean enableOutEncryption, Packet sendNext) {
byte respCode = data[1];
switch (respCode) {
case Packet.RESP_OK:
byte[] cmp = new byte[16];
//TODO check length!
System.arraycopy(data, 2, cmp, 0, 16);
if (mPacketManager.setEncryption(cmp, enableOutEncryption)) {
sendPacket(sendNext);
} else {
mListener.onInitFailure(InputStickError.ERROR_SECURITY_CHALLENGE);
}
break;
case 0x20:
mListener.onInitFailure(InputStickError.ERROR_SECURITY_INVALID_KEY);
break;
case 0x21:
mListener.onInitFailure(InputStickError.ERROR_SECURITY_NOT_PROTECTED);
break;
case Packet.RESP_UNKNOWN_CMD:
mListener.onInitFailure(InputStickError.ERROR_SECURITY_NOT_SUPPORTED);
break;
default:
mListener.onInitFailure(InputStickError.ERROR_SECURITY);
}
}
} }

View File

@ -47,10 +47,14 @@ package com.keepassdroid.database;
// Java // Java
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.security.DigestOutputStream; import java.security.DigestOutputStream;
import java.security.MessageDigest; import java.security.MessageDigest;
@ -125,18 +129,18 @@ public class PwDatabaseV3 {
public void setMasterKey(String key, String keyFileName) public void setMasterKey(String key, InputStream keyfileStream)
throws InvalidKeyFileException, IOException { throws InvalidKeyFileException, IOException {
assert( key != null && keyFileName != null ); assert( key != null && keyfileStream != null );
masterKey = getMasterKey(key, keyFileName); masterKey = getMasterKey(key, keyfileStream);
} }
protected byte[] getCompositeKey(String key, String keyFileName) protected byte[] getCompositeKey(String key, InputStream keyfileStream)
throws InvalidKeyFileException, IOException { throws InvalidKeyFileException, IOException {
assert(key != null && keyFileName != null); assert(key != null && keyfileStream != null);
byte[] fileKey = getFileKey(keyFileName); byte[] fileKey = getFileKey(keyfileStream);
byte[] passwordKey = getPasswordKey(key); byte[] passwordKey = getPasswordKey(key);
@ -152,45 +156,39 @@ public class PwDatabaseV3 {
return md.digest(fileKey); return md.digest(fileKey);
} }
protected byte[] getFileKey(String fileName) protected byte[] getFileKey(InputStream keyfileStream)
throws InvalidKeyFileException, IOException { throws InvalidKeyFileException, IOException {
assert(fileName != null); assert(keyfileStream != null);
File keyfile = new File(fileName);
if ( ! keyfile.exists() ) { byte[] buff = new byte[8000];
throw new InvalidKeyFileException();
int bytesRead = 0;
ByteArrayOutputStream bao = new ByteArrayOutputStream();
while ((bytesRead = keyfileStream.read(buff)) != -1) {
bao.write(buff, 0, bytesRead);
} }
byte[] key = loadXmlKeyFile(fileName); byte[] keyFileData = bao.toByteArray();
if ( key != null ) {
return key;
}
FileInputStream fis; ByteArrayInputStream bin = new ByteArrayInputStream(keyFileData);
try {
fis = new FileInputStream(keyfile);
} catch (FileNotFoundException e) {
throw new InvalidKeyFileException();
}
BufferedInputStream bis = new BufferedInputStream(fis, 64);
long fileSize = keyfile.length();
if ( fileSize == 0 ) { if ( keyFileData.length == 32 ) {
throw new KeyFileEmptyException();
} else if ( fileSize == 32 ) {
byte[] outputKey = new byte[32]; byte[] outputKey = new byte[32];
if ( bis.read(outputKey, 0, 32) != 32 ) { if ( bin.read(outputKey, 0, 32) != 32 ) {
throw new IOException("Error reading key."); throw new IOException("Error reading key.");
} }
return outputKey; return outputKey;
} else if ( fileSize == 64 ) { } else if ( keyFileData.length == 64 ) {
byte[] hex = new byte[64]; byte[] hex = new byte[64];
bis.mark(64); bin.mark(64);
if ( bis.read(hex, 0, 64) != 64 ) { if ( bin.read(hex, 0, 64) != 64 ) {
throw new IOException("Error reading key."); throw new IOException("Error reading key.");
} }
@ -198,7 +196,7 @@ public class PwDatabaseV3 {
return hexStringToByteArray(new String(hex)); return hexStringToByteArray(new String(hex));
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
// Key is not base 64, treat it as binary data // Key is not base 64, treat it as binary data
bis.reset(); bin.reset();
} }
} }
@ -214,7 +212,7 @@ public class PwDatabaseV3 {
try { try {
while (true) { while (true) {
int bytesRead = bis.read(buffer, 0, 2048); bytesRead = bin.read(buffer, 0, 2048);
if ( bytesRead == -1 ) break; // End of file if ( bytesRead == -1 ) break; // End of file
md.update(buffer, 0, bytesRead); md.update(buffer, 0, bytesRead);
@ -495,16 +493,16 @@ public class PwDatabaseV3 {
return newId; return newId;
} }
public byte[] getMasterKey(String key, String keyFileName) public byte[] getMasterKey(String key, InputStream keyfileStream)
throws InvalidKeyFileException, IOException { throws InvalidKeyFileException, IOException {
assert (key != null && keyFileName != null); assert (key != null && keyfileStream != null);
if (key.length() > 0 && keyFileName.length() > 0) { if (key.length() > 0 && keyfileStream != null) {
return getCompositeKey(key, keyFileName); return getCompositeKey(key, keyfileStream);
} else if (key.length() > 0) { } else if (key.length() > 0) {
return getPasswordKey(key); return getPasswordKey(key);
} else if (keyFileName.length() > 0) { } else if (keyfileStream != null) {
return getFileKey(keyFileName); return getFileKey(keyfileStream);
} else { } else {
throw new IllegalArgumentException("Key cannot be empty."); throw new IllegalArgumentException("Key cannot be empty.");
} }
@ -515,11 +513,6 @@ public class PwDatabaseV3 {
return getPasswordKey(key, "ISO-8859-1"); return getPasswordKey(key, "ISO-8859-1");
} }
protected byte[] loadXmlKeyFile(String fileName) {
return null;
}
public long getNumRounds() { public long getNumRounds() {
return numKeyEncRounds; return numKeyEncRounds;

View File

@ -26,7 +26,7 @@ public class InvalidDBSignatureException extends InvalidDBException {
private static final long serialVersionUID = -5358923878743513758L; private static final long serialVersionUID = -5358923878743513758L;
public InvalidDBSignatureException() { public InvalidDBSignatureException() {
super(); super("Invalid database signature");
} }
} }

View File

@ -25,6 +25,10 @@ public class InvalidKeyFileException extends InvalidDBException {
private static final long serialVersionUID = 5540694419562294464L; private static final long serialVersionUID = 5540694419562294464L;
public InvalidKeyFileException() { public InvalidKeyFileException() {
super(); super("invalid key file!");
}
public InvalidKeyFileException(String msg) {
super(msg);
} }
} }

View File

@ -31,6 +31,6 @@ public class InvalidPasswordException extends InvalidDBException {
} }
public InvalidPasswordException() { public InvalidPasswordException() {
super(); super("Invalid key!");
} }
} }

View File

@ -25,6 +25,6 @@ public class KeyFileEmptyException extends InvalidKeyFileException {
private static final long serialVersionUID = -1630780661204212325L; private static final long serialVersionUID = -1630780661204212325L;
public KeyFileEmptyException() { public KeyFileEmptyException() {
super(); super("key file is empty!");
} }
} }

View File

@ -123,13 +123,13 @@ public class ImporterV3 {
* @throws InvalidAlgorithmParameterException if error decrypting main file body. * @throws InvalidAlgorithmParameterException if error decrypting main file body.
* @throws ShortBufferException if error decrypting main file body. * @throws ShortBufferException if error decrypting main file body.
*/ */
public PwDatabaseV3 openDatabase( InputStream inStream, String password, String keyfile ) public PwDatabaseV3 openDatabase( InputStream inStream, String password, InputStream keyfileStream )
throws IOException, InvalidDBException throws IOException, InvalidDBException
{ {
return openDatabase(inStream, password, keyfile, new UpdateStatus()); return openDatabase(inStream, password, keyfileStream, new UpdateStatus());
} }
public PwDatabaseV3 openDatabase( InputStream inStream, String password, String keyfile, UpdateStatus status ) public PwDatabaseV3 openDatabase( InputStream inStream, String password, InputStream keyfileStream, UpdateStatus status )
throws IOException, InvalidDBException throws IOException, InvalidDBException
{ {
PwDatabaseV3 newManager; PwDatabaseV3 newManager;
@ -175,7 +175,7 @@ public class ImporterV3 {
} }
newManager = createDB(); newManager = createDB();
newManager.setMasterKey( password, keyfile ); newManager.setMasterKey( password, keyfileStream );
// Select algorithm // Select algorithm
if( (hdr.flags & PwDbHeaderV3.FLAG_RIJNDAEL) != 0 ) { if( (hdr.flags & PwDbHeaderV3.FLAG_RIJNDAEL) != 0 ) {
@ -230,7 +230,7 @@ public class ImporterV3 {
} catch (IllegalBlockSizeException e1) { } catch (IllegalBlockSizeException e1) {
throw new IOException("Invalid block size"); throw new IOException("Invalid block size");
} catch (BadPaddingException e1) { } catch (BadPaddingException e1) {
throw new InvalidPasswordException(); throw new InvalidPasswordException("Invalid key!");
} }
// Copy decrypted data for testing // Copy decrypted data for testing
@ -251,7 +251,7 @@ public class ImporterV3 {
if( ! Arrays.equals(hash, hdr.contentsHash) ) { if( ! Arrays.equals(hash, hdr.contentsHash) ) {
Log.w("KeePassDroid","Database file did not decrypt correctly. (checksum code is broken)"); Log.w("KeePassDroid","Database file did not decrypt correctly. (checksum code is broken)");
throw new InvalidPasswordException(); throw new InvalidPasswordException("Invalid key!");
} }
// Import all groups // Import all groups

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="keepass2android.plugin.inputstick" package="keepass2android.plugin.inputstick"
android:versionCode="2" android:versionCode="3"
android:versionName="1.1" > android:versionName="1.2" >
<uses-sdk <uses-sdk
android:minSdkVersion="14" android:minSdkVersion="14"

View File

@ -11,7 +11,10 @@ public final class R {
public static final int ic_launcher = 0x7f020000; public static final int ic_launcher = 0x7f020000;
} }
public static final class string { public static final class string {
public static final int action_settings = 0x7f060002;
public static final int app_name = 0x7f060000; public static final int app_name = 0x7f060000;
public static final int hello_world = 0x7f060003;
public static final int title_activity_install_utility = 0x7f060001;
} }
public static final class style { public static final class style {
public static final int AppBaseTheme = 0x7f070000; public static final int AppBaseTheme = 0x7f070000;

View File

@ -49,20 +49,22 @@ public final class R {
public static final int main=0x7f090000; public static final int main=0x7f090000;
} }
public static final class string { public static final class string {
public static final int action_input_stick=0x7f060005; public static final int action_input_stick=0x7f060007;
public static final int action_settings=0x7f060001; public static final int action_settings=0x7f060002;
public static final int action_type_enter=0x7f060007; public static final int action_type_enter=0x7f060009;
public static final int action_type_tab=0x7f060006; public static final int action_type_tab=0x7f060008;
public static final int action_type_user_tab_pass_enter=0x7f060008; public static final int action_type_user_tab_pass_enter=0x7f06000a;
public static final int app_name=0x7f060000; public static final int app_name=0x7f060000;
/** Strings related to Settings /** Strings related to Settings
*/ */
public static final int configure_plugin=0x7f06000a; public static final int configure_plugin=0x7f06000c;
public static final int kp2aplugin_author=0x7f060004; public static final int hello_world=0x7f060003;
public static final int kp2aplugin_shortdesc=0x7f060003; public static final int kp2aplugin_author=0x7f060006;
public static final int kp2aplugin_title=0x7f060002; public static final int kp2aplugin_shortdesc=0x7f060005;
public static final int layout_title=0x7f06000b; public static final int kp2aplugin_title=0x7f060004;
public static final int title_activity_settings=0x7f060009; public static final int layout_title=0x7f06000d;
public static final int title_activity_install_utility=0x7f060001;
public static final int title_activity_settings=0x7f06000b;
} }
public static final class style { public static final class style {
/** /**

View File

@ -8,10 +8,17 @@
<string name="layout_title">Host keyboard layout</string> <string name="layout_title">Host keyboard layout</string>
<string-array name="layout_names"> <string-array name="layout_names">
<item>English (United States) - en-US</item> <item>English (US)</item>
<item>German - de-DE</item> <item>German</item>
<item>Polish - pl-PL</item> <item>Polish</item>
<item>Russian - ru-RU</item> <item>Russian</item>
<item>Slovak</item>
<item>Portuguese (BR)</item>
<item>Norwegian</item>
<item>Swedish</item>
<item>French</item>
<item>Spanish</item>
<item>English (Dvorak)</item>
</string-array> </string-array>
<string-array name="layout_values"> <string-array name="layout_values">
@ -19,6 +26,13 @@
<item>de-DE</item> <item>de-DE</item>
<item>pl-PL</item> <item>pl-PL</item>
<item>ru-RU</item> <item>ru-RU</item>
<item>sk-SK</item>
<item>pt-BR</item>
<item>nb-NO</item>
<item>sv-SE</item>
<item>fr-FR</item>
<item>es-ES</item>
<item>en-DV</item>
</string-array> </string-array>
</resources> </resources>

View File

@ -34,7 +34,7 @@ public class ActionReceiver extends keepass2android.pluginsdk.PluginActionBroadc
} catch (PluginAccessException e) { } catch (PluginAccessException e) {
e.printStackTrace(); e.printStackTrace();
} }
typeText(oe.getContext(), ""); //typeText(oe.getContext(), "");
} }
@Override @Override

View File

@ -327,9 +327,9 @@ namespace keepass2android
defaultPath => defaultPath =>
{ {
if (defaultPath.StartsWith("sftp://")) if (defaultPath.StartsWith("sftp://"))
Util.ShowSftpDialog(this, OnReceiveSftpData); Util.ShowSftpDialog(this, OnReceiveSftpData, () => { });
else else
Util.ShowFilenameDialog(this, OnCreateButton, null, false, defaultPath, GetString(Resource.String.enter_filename_details_url), Util.ShowFilenameDialog(this, OnCreateButton, null, null, false, defaultPath, GetString(Resource.String.enter_filename_details_url),
Intents.RequestCodeFileBrowseForOpen); Intents.RequestCodeFileBrowseForOpen);
} }
), true, RequestCodeDbFilename, protocolId); ), true, RequestCodeDbFilename, protocolId);

View File

@ -1,4 +1,4 @@
/* /*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. 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 Keepass2Android is free software: you can redistribute it and/or modify
@ -682,7 +682,7 @@ namespace keepass2android
addBinaryButton.SetCompoundDrawablesWithIntrinsicBounds( Resources.GetDrawable(Android.Resource.Drawable.IcMenuAdd) , null, null, null); addBinaryButton.SetCompoundDrawablesWithIntrinsicBounds( Resources.GetDrawable(Android.Resource.Drawable.IcMenuAdd) , null, null, null);
addBinaryButton.Click += (sender, e) => addBinaryButton.Click += (sender, e) =>
{ {
Util.ShowBrowseDialog("/mnt/sdcard", this, Intents.RequestCodeFileBrowseForBinary, false); Util.ShowBrowseDialog(this, Intents.RequestCodeFileBrowseForBinary, false);
}; };
binariesGroup.AddView(addBinaryButton,layoutParams); binariesGroup.AddView(addBinaryButton,layoutParams);

View File

@ -61,9 +61,9 @@ namespace keepass2android
defaultPath => defaultPath =>
{ {
if (defaultPath.StartsWith("sftp://")) if (defaultPath.StartsWith("sftp://"))
Util.ShowSftpDialog(this, OnReceiveSftpData); Util.ShowSftpDialog(this, OnReceiveSftpData, () => { });
else else
Util.ShowFilenameDialog(this, OnCreateButton, null, false, defaultPath, GetString(Resource.String.enter_filename_details_url), Util.ShowFilenameDialog(this, OnCreateButton, null, null, false, defaultPath, GetString(Resource.String.enter_filename_details_url),
Intents.RequestCodeFileBrowseForOpen); Intents.RequestCodeFileBrowseForOpen);
} }
), true, RequestCodeDbFilename, protocolId); ), true, RequestCodeDbFilename, protocolId);

View File

@ -14,7 +14,7 @@ namespace keepass2android
[Activity (Label = "@string/app_name", ConfigurationChanges=ConfigChanges.Orientation|ConfigChanges.KeyboardHidden , Theme="@style/NoTitleBar")] [Activity (Label = "@string/app_name", ConfigurationChanges=ConfigChanges.Orientation|ConfigChanges.KeyboardHidden , Theme="@style/NoTitleBar")]
public class FileStorageSelectionActivity : ListActivity public class FileStorageSelectionActivity : ListActivity
{ {
private ActivityDesign _design; private readonly ActivityDesign _design;
private FileStorageAdapter _fileStorageAdapter; private FileStorageAdapter _fileStorageAdapter;
@ -42,6 +42,9 @@ namespace keepass2android
//put file:// to the top //put file:// to the top
_protocolIds.Remove("file"); _protocolIds.Remove("file");
_protocolIds.Insert(0, "file"); _protocolIds.Insert(0, "file");
//remove "content" (covered by androidget)
_protocolIds.Remove("content");
if (context.Intent.GetBooleanExtra(AllowThirdPartyAppGet, false)) if (context.Intent.GetBooleanExtra(AllowThirdPartyAppGet, false))
_protocolIds.Add("androidget"); _protocolIds.Add("androidget");
if (context.Intent.GetBooleanExtra(AllowThirdPartyAppSend, false)) if (context.Intent.GetBooleanExtra(AllowThirdPartyAppSend, false))

View File

@ -24,6 +24,7 @@ using System.Xml.Serialization;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.Database; using Android.Database;
using Android.Graphics.Drawables;
using Android.OS; using Android.OS;
using Android.Runtime; using Android.Runtime;
using Android.Views; using Android.Views;
@ -86,7 +87,7 @@ namespace keepass2android
private const int RequestCodePrepareDbFile = 1000; private const int RequestCodePrepareDbFile = 1000;
private const int RequestCodePrepareOtpAuxFile = 1001; private const int RequestCodePrepareOtpAuxFile = 1001;
private const int RequestCodeChallengeYubikey = 1002; private const int RequestCodeChallengeYubikey = 1002;
private const int RequestCodeSelectKeyfile = 1003;
private Task<MemoryStream> _loadDbTask; private Task<MemoryStream> _loadDbTask;
private IOConnectionInfo _ioConnection; private IOConnectionInfo _ioConnection;
@ -137,6 +138,7 @@ namespace keepass2android
private ActivityDesign _design; private ActivityDesign _design;
private bool _performingLoad; private bool _performingLoad;
public PasswordActivity (IntPtr javaReference, JniHandleOwnership transfer) public PasswordActivity (IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer) : base(javaReference, transfer)
{ {
@ -258,26 +260,20 @@ namespace keepass2android
KcpKeyFile kcpKeyfile = (KcpKeyFile)App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey(typeof(KcpKeyFile)); KcpKeyFile kcpKeyfile = (KcpKeyFile)App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey(typeof(KcpKeyFile));
SetEditText(Resource.Id.pass_keyfile, kcpKeyfile.Path); FindViewById<TextView>(Resource.Id.label_keyfilename).Text =
App.Kp2a.GetFileStorage(kcpKeyfile.Ioc).GetDisplayName(kcpKeyfile.Ioc);
} }
} }
App.Kp2a.LockDatabase(false); App.Kp2a.LockDatabase(false);
break; break;
case Result.Ok: // Key file browse dialog OK'ed. case Result.Ok:
if (requestCode == Intents.RequestCodeFileBrowseForKeyfile) { if (requestCode == RequestCodeSelectKeyfile)
string filename = Util.IntentToFilename(data, this); {
if (filename != null) { IOConnectionInfo ioc = new IOConnectionInfo();
if (filename.StartsWith("file://")) { SetIoConnectionFromIntent(ioc, data);
filename = filename.Substring(7); _keyFileOrProvider = IOConnectionInfo.SerializeToString(ioc);
} UpdateKeyfileIocView();
filename = URLDecoder.Decode(filename);
EditText fn = (EditText) FindViewById(Resource.Id.pass_keyfile);
fn.Text = filename;
}
} }
break; break;
case (Result)FileStorageResults.FileUsagePrepared: case (Result)FileStorageResults.FileUsagePrepared:
@ -354,6 +350,39 @@ namespace keepass2android
} }
private void UpdateKeyfileIocView()
{
//store keyfile in the view so that we can show the selected keyfile again if the user switches to another key provider and back to key file
FindViewById<TextView>(Resource.Id.label_keyfilename).Tag = _keyFileOrProvider;
if (string.IsNullOrEmpty(_keyFileOrProvider))
{
FindViewById<TextView>(Resource.Id.filestorage_label).Visibility = ViewStates.Gone;
FindViewById<ImageView>(Resource.Id.filestorage_logo).Visibility = ViewStates.Gone;
FindViewById<TextView>(Resource.Id.label_keyfilename).Text = Resources.GetString(Resource.String.no_keyfile_selected);
return;
}
var ioc = IOConnectionInfo.UnserializeFromString(_keyFileOrProvider);
string displayPath = App.Kp2a.GetFileStorage(ioc).GetDisplayName(ioc);
int protocolSeparatorPos = displayPath.IndexOf("://", StringComparison.Ordinal);
string protocolId = protocolSeparatorPos < 0 ?
"file" : displayPath.Substring(0, protocolSeparatorPos);
Drawable drawable = App.Kp2a.GetResourceDrawable("ic_storage_" + protocolId);
FindViewById<ImageView>(Resource.Id.filestorage_logo).SetImageDrawable(drawable);
FindViewById<ImageView>(Resource.Id.filestorage_logo).Visibility = ViewStates.Visible;
String title = App.Kp2a.GetResourceString("filestoragename_" + protocolId);
FindViewById<TextView>(Resource.Id.filestorage_label).Text = title;
FindViewById<TextView>(Resource.Id.filestorage_label).Visibility = ViewStates.Visible;
FindViewById<TextView>(Resource.Id.label_keyfilename).Text = protocolSeparatorPos < 0 ?
displayPath :
displayPath.Substring(protocolSeparatorPos + 3);
}
private void LoadOtpFile() private void LoadOtpFile()
{ {
new LoadingDialog<object, object, object>(this, true, new LoadingDialog<object, object, object>(this, true,
@ -543,14 +572,11 @@ namespace keepass2android
InitializeFilenameView(); InitializeFilenameView();
if (KeyProviderType == KeyProviders.KeyFile) if (KeyProviderType == KeyProviders.KeyFile)
SetEditText(Resource.Id.pass_keyfile, _keyFileOrProvider);
FindViewById<EditText>(Resource.Id.pass_keyfile).TextChanged +=
(sender, args) =>
{ {
_keyFileOrProvider = FindViewById<EditText>(Resource.Id.pass_keyfile).Text; UpdateKeyfileIocView();
UpdateOkButtonState(); }
};
FindViewById<EditText>(Resource.Id.password).TextChanged += FindViewById<EditText>(Resource.Id.password).TextChanged +=
(sender, args) => (sender, args) =>
@ -705,20 +731,14 @@ namespace keepass2android
private void InitializeKeyfileBrowseButton() private void InitializeKeyfileBrowseButton()
{ {
ImageButton browse = (ImageButton) FindViewById(Resource.Id.browse_button); var browseButton = (Button)FindViewById(Resource.Id.btn_change_location);
browse.Click += (sender, evt) => browseButton.Click += (sender, evt) =>
{ {
string filename = null; Intent intent = new Intent(this, typeof(SelectStorageLocationActivity));
if (!String.IsNullOrEmpty(_ioConnection.Path)) intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, true);
{ intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, false);
File keyfile = new File(_ioConnection.Path); intent.PutExtra(FileStorageSetupDefs.ExtraIsForSave, false);
File parent = keyfile.ParentFile; StartActivityForResult(intent, RequestCodeSelectKeyfile);
if (parent != null)
{
filename = parent.AbsolutePath;
}
}
Util.ShowBrowseDialog(filename, this, Intents.RequestCodeFileBrowseForKeyfile, false);
}; };
} }
@ -738,7 +758,7 @@ namespace keepass2android
break; break;
case 1: case 1:
//don't set to "" to prevent losing the filename. (ItemSelected is also called during recreation!) //don't set to "" to prevent losing the filename. (ItemSelected is also called during recreation!)
_keyFileOrProvider = FindViewById<EditText>(Resource.Id.pass_keyfile).Text; _keyFileOrProvider = (FindViewById(Resource.Id.label_keyfilename).Tag ?? "").ToString();
break; break;
case 2: case 2:
_keyFileOrProvider = KeyProviderIdOtp; _keyFileOrProvider = KeyProviderIdOtp;
@ -779,7 +799,7 @@ namespace keepass2android
_showPassword = savedInstanceState.GetBoolean(ShowpasswordKey, false); _showPassword = savedInstanceState.GetBoolean(ShowpasswordKey, false);
MakePasswordMaskedOrVisible(); MakePasswordMaskedOrVisible();
_keyFileOrProvider = FindViewById<EditText>(Resource.Id.pass_keyfile).Text = savedInstanceState.GetString(KeyFileOrProviderKey); _keyFileOrProvider = savedInstanceState.GetString(KeyFileOrProviderKey);
_password = FindViewById<EditText>(Resource.Id.password).Text = savedInstanceState.GetString(PasswordKey); _password = FindViewById<EditText>(Resource.Id.password).Text = savedInstanceState.GetString(PasswordKey);
_pendingOtps = new List<string>(savedInstanceState.GetStringArrayList(PendingOtpsKey)); _pendingOtps = new List<string>(savedInstanceState.GetStringArrayList(PendingOtpsKey));
@ -850,6 +870,11 @@ namespace keepass2android
FindViewById(Resource.Id.keyfileLine).Visibility = KeyProviderType == KeyProviders.KeyFile FindViewById(Resource.Id.keyfileLine).Visibility = KeyProviderType == KeyProviders.KeyFile
? ViewStates.Visible ? ViewStates.Visible
: ViewStates.Gone; : ViewStates.Gone;
if (KeyProviderType == KeyProviders.KeyFile)
{
UpdateKeyfileIocView();
}
FindViewById(Resource.Id.otpView).Visibility = KeyProviderType == KeyProviders.Otp FindViewById(Resource.Id.otpView).Visibility = KeyProviderType == KeyProviders.Otp
? ViewStates.Visible ? ViewStates.Visible
: ViewStates.Gone; : ViewStates.Gone;
@ -876,16 +901,30 @@ namespace keepass2android
//no need to check for validity of password because if this method is called, the Ok button was enabled (i.e. there was a valid password) //no need to check for validity of password because if this method is called, the Ok button was enabled (i.e. there was a valid password)
CompositeKey compositeKey = new CompositeKey(); CompositeKey compositeKey = new CompositeKey();
compositeKey.AddUserKey(new KcpPassword(_password)); compositeKey.AddUserKey(new KcpPassword(_password));
if ((KeyProviderType == KeyProviders.KeyFile) && (_keyFileOrProvider != "")) if (KeyProviderType == KeyProviders.KeyFile)
{ {
try try
{ {
compositeKey.AddUserKey(new KcpKeyFile(_keyFileOrProvider)); if (_keyFileOrProvider == "")
throw new System.IO.FileNotFoundException();
var ioc = IOConnectionInfo.UnserializeFromString(_keyFileOrProvider);
using (var stream = App.Kp2a.GetFileStorage(ioc).OpenFileForRead(ioc))
{
byte[] keyfileData = StreamToMemoryStream(stream).ToArray();
compositeKey.AddUserKey(new KcpKeyFile(keyfileData, ioc, true));
}
}
catch (System.IO.FileNotFoundException e)
{
Kp2aLog.Log(e.ToString());
Toast.MakeText(this, App.Kp2a.GetResourceString(UiStringKey.keyfile_does_not_exist), ToastLength.Long).Show();
return;
} }
catch (Exception e) catch (Exception e)
{ {
Kp2aLog.Log(e.ToString()); Kp2aLog.Log(e.ToString());
Toast.MakeText(this, App.Kp2a.GetResourceString(UiStringKey.keyfile_does_not_exist), ToastLength.Long).Show(); Toast.MakeText(this, e.Message, ToastLength.Long).Show();
return; return;
} }
} }
@ -895,11 +934,12 @@ namespace keepass2android
try try
{ {
var lOtps = GetOtpsFromUi(); var lOtps = GetOtpsFromUi();
Kp2aLog.Log("received " + lOtps.Count + " otps.");
OathHotpKeyProv.CreateOtpSecret(lOtps, _otpInfo); OathHotpKeyProv.CreateOtpSecret(lOtps, _otpInfo);
} }
catch (Exception) catch (Exception e)
{ {
Kp2aLog.Log(e.ToString());
Toast.MakeText(this, GetString(Resource.String.OtpKeyError), ToastLength.Long).Show(); Toast.MakeText(this, GetString(Resource.String.OtpKeyError), ToastLength.Long).Show();
return; return;
@ -1031,10 +1071,19 @@ namespace keepass2android
var fileStorage = App.Kp2a.GetFileStorage(_ioConnection); var fileStorage = App.Kp2a.GetFileStorage(_ioConnection);
var stream = fileStorage.OpenFileForRead(_ioConnection); var stream = fileStorage.OpenFileForRead(_ioConnection);
var memoryStream = StreamToMemoryStream(stream);
Kp2aLog.Log("Pre-loading database file completed");
return memoryStream;
}
private static MemoryStream StreamToMemoryStream(Stream stream)
{
var memoryStream = stream as MemoryStream; var memoryStream = stream as MemoryStream;
if (memoryStream == null) if (memoryStream == null)
{ {
// Read the file into memory // Read the stream into memory
int capacity = 4096; // Default initial capacity, if stream can't report it. int capacity = 4096; // Default initial capacity, if stream can't report it.
if (stream.CanSeek) if (stream.CanSeek)
{ {
@ -1043,11 +1092,8 @@ namespace keepass2android
memoryStream = new MemoryStream(capacity); memoryStream = new MemoryStream(capacity);
stream.CopyTo(memoryStream); stream.CopyTo(memoryStream);
stream.Close(); stream.Close();
memoryStream.Seek(0, System.IO.SeekOrigin.Begin); memoryStream.Seek(0, SeekOrigin.Begin);
} }
Kp2aLog.Log("Pre-loading database file completed");
return memoryStream; return memoryStream;
} }

View File

@ -18,7 +18,7 @@
</intent-filter> </intent-filter>
</activity> </activity>
<!--
<provider android:name="group.pals.android.lib.ui.filechooser.providers.localfile.LocalFileProvider" android:authorities="keepass2android.keepass2android.android-filechooser.localfile" android:exported="false" /> <provider android:name="group.pals.android.lib.ui.filechooser.providers.localfile.LocalFileProvider" android:authorities="keepass2android.keepass2android.android-filechooser.localfile" android:exported="false" />
<provider android:name="group.pals.android.lib.ui.filechooser.providers.history.HistoryProvider" android:authorities="keepass2android.keepass2android.android-filechooser.history" android:exported="false" /> <provider android:name="group.pals.android.lib.ui.filechooser.providers.history.HistoryProvider" android:authorities="keepass2android.keepass2android.android-filechooser.history" android:exported="false" />
<activity android:name="group.pals.android.lib.ui.filechooser.FileChooserActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:screenOrientation="user" android:theme="@style/Afc.Theme.Light"> <activity android:name="group.pals.android.lib.ui.filechooser.FileChooserActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:screenOrientation="user" android:theme="@style/Afc.Theme.Light">
@ -29,7 +29,7 @@
<category android:name="android.intent.category.OPENABLE" /> <category android:name="android.intent.category.OPENABLE" />
</intent-filter> </intent-filter>
</activity> </activity>
<service android:name="keepass2android.softkeyboard.KP2AKeyboard" android:permission="android.permission.BIND_INPUT_METHOD"> --> <service android:name="keepass2android.softkeyboard.KP2AKeyboard" android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter> <intent-filter>
<action android:name="android.view.InputMethod" /> <action android:name="android.view.InputMethod" />
</intent-filter> </intent-filter>

File diff suppressed because it is too large Load Diff

View File

@ -86,25 +86,61 @@ android:layout_height="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:src="@drawable/ic_menu_view" /> android:src="@drawable/ic_menu_view" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/keyfileLine" android:id="@+id/keyfileLine"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:baselineAligned="false" android:baselineAligned="false"
android:orientation="horizontal"> android:orientation="vertical">
<EditText
android:id="@+id/pass_keyfile" <TextView
android:layout_width="0px" android:id="@+id/keyfile_heading"
android:layout_height="wrap_content"
android:singleLine="true"
android:layout_weight="1"
android:layout_gravity="bottom"
android:hint="@string/entry_keyfile" />
<ImageButton
android:id="@+id/browse_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:src="@drawable/ic_launcher_folder_small" /> android:text="@string/keyfile_heading" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/filestorage_logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_storage_file"
android:padding="5dp"
/>
<TextView
android:id="@+id/filestorage_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="Local file (TODO!)"
android:textSize="16dp" >
</TextView>
</LinearLayout>
<TextView
android:id="@+id/label_keyfilename"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="[path]"
android:layout_marginLeft="18dp"
/>
<Button android:id="@+id/btn_change_location"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_change_location"
style="@style/TextAppearance_SubElement"
/>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/otpView" android:id="@+id/otpView"

View File

@ -32,6 +32,8 @@
<string name="ShowGroupInEntry_title">Show group name in entry view</string> <string name="ShowGroupInEntry_title">Show group name in entry view</string>
<string name="unknown_uri_scheme">Sorry! Keepass2Android cannot handle the returned URI %1$s. Please contact the developer!</string>
<string name="security_prefs">Security</string> <string name="security_prefs">Security</string>
<string name="display_prefs">Display</string> <string name="display_prefs">Display</string>
<string name="password_access_prefs">Password entry access</string> <string name="password_access_prefs">Password entry access</string>
@ -68,6 +70,7 @@
<string name="entry_expires">Expires</string> <string name="entry_expires">Expires</string>
<string name="entry_group_name">Group Name</string> <string name="entry_group_name">Group Name</string>
<string name="entry_keyfile">Key file (optional)</string> <string name="entry_keyfile">Key file (optional)</string>
<string name="keyfile_heading">Key file</string>
<string name="entry_modified">Modified</string> <string name="entry_modified">Modified</string>
<string name="entry_password">Password</string> <string name="entry_password">Password</string>
<string name="entry_save">Save</string> <string name="entry_save">Save</string>
@ -114,6 +117,7 @@
<string name="invalid_algorithm">Invalid algorithm.</string> <string name="invalid_algorithm">Invalid algorithm.</string>
<string name="invalid_db_sig">Database format not recognized.</string> <string name="invalid_db_sig">Database format not recognized.</string>
<string name="keyfile_does_not_exist">Key file does not exist.</string> <string name="keyfile_does_not_exist">Key file does not exist.</string>
<string name="no_keyfile_selected">No key file selected.</string>
<string name="keyfile_is_empty">Key file is empty.</string> <string name="keyfile_is_empty">Key file is empty.</string>
<string name="length">Length</string> <string name="length">Length</string>
<string name="list_size_title">Group list size</string> <string name="list_size_title">Group list size</string>
@ -471,6 +475,17 @@
<string name="killed_by_os">Sorry! Keepass2Android was killed by the Android OS! For security reasons, Keepass2Android did not persist your selected credentials on disk, so you need to re-open your database. Note: This should happen only very rarely. If it does, please drop me a message at crocoapps@gmail.com.</string> <string name="killed_by_os">Sorry! Keepass2Android was killed by the Android OS! For security reasons, Keepass2Android did not persist your selected credentials on disk, so you need to re-open your database. Note: This should happen only very rarely. If it does, please drop me a message at crocoapps@gmail.com.</string>
<string name="FileIsTemporarilyAvailable">The file is only temporarily available for Keepass2Android.</string>
<string name="FileIsReadOnly">The file you selected is read-only.</string>
<string name="FileIsReadOnlyOnKitkat">The file you selected is read-only for Keepass2Android due to restrictions on Android 4.4+.</string>
<string name="CopyFileRequired">To use it, you must copy it to another location.</string>
<string name="CopyFileRequiredForEditing">To edit it, you must copy the file to another location.</string>
<string name="ClickOkToSelectLocation">Click OK to select a location where the file should be copied.</string>
<string name="CancelReadOnly">Cancel, open read-only.</string>
<string name="CopyingFile">Copying file...</string>
<string name="ChangeLog_title">Change log</string> <string name="ChangeLog_title">Change log</string>
<string name="PreviewWarning">Please note! This is a preview release and might come with some flaws! If you experience *anything* unexpected, please let me know (on Codeplex or by email).</string> <string name="PreviewWarning">Please note! This is a preview release and might come with some flaws! If you experience *anything* unexpected, please let me know (on Codeplex or by email).</string>

View File

@ -0,0 +1,238 @@
using System;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Widget;
using KeePassLib.Serialization;
using KeePassLib.Utility;
using keepass2android.Io;
using keepass2android.Utils;
namespace keepass2android
{
[Activity(Label = "")]
public class SelectStorageLocationActivity : SelectStorageLocationActivityBase, IDialogInterfaceOnDismissListener
{
private ActivityDesign _design;
private const string BundleKeySelectedIoc = "BundleKeySelectedIoc";
public const string ExtraKeyWritableRequirements = "EXTRA_KEY_WRITABLE_REQUIREMENTS";
public SelectStorageLocationActivity() : base(App.Kp2a)
{
_design = new ActivityDesign(this);
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
_design.ApplyTheme();
Kp2aLog.Log("SelectStorageLocationActivity.OnCreate");
if (IsStorageSelectionForSave)
{
throw new Exception("save is not yet implemented. In StartSelectFile, no handler for onCreate is passed.");
}
bool allowThirdPartyGet = Intent.GetBooleanExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, false);
bool allowThirdPartySend = Intent.GetBooleanExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, false);
bool isRecreated = false;
if (bundle == null)
State = new Bundle();
else
{
State = (Bundle)bundle.Clone();
var selectedIocString = bundle.GetString(BundleKeySelectedIoc, null);
if (selectedIocString != null)
_selectedIoc = IOConnectionInfo.UnserializeFromString(selectedIocString);
isRecreated = true;
}
//todo: handle orientation change while dialog is shown
if (!isRecreated)
{
StartFileStorageSelection(RequestCodeFileStorageSelectionForPrimarySelect, allowThirdPartyGet, allowThirdPartySend);
}
}
protected Bundle State { get; set; }
protected override void ShowToast(string text)
{
Toast.MakeText(this, text, ToastLength.Long).Show();
}
protected override void ShowInvalidSchemeMessage(string dataString)
{
Toast.MakeText(this, Resources.GetString(Resource.String.unknown_uri_scheme, new Java.Lang.Object[] { dataString }),
ToastLength.Long).Show();
}
protected override string IntentToFilename(Intent data)
{
return Util.IntentToFilename(data, this);
}
protected override void SetIoConnectionFromIntent(IOConnectionInfo ioc, Intent data)
{
PasswordActivity.SetIoConnectionFromIntent(ioc, data);
}
protected override Result ExitFileStorageSelectionOk
{
get { return KeePass.ExitFileStorageSelectionOk; }
}
protected override void StartSelectFile( bool isForSave, int browseRequestCode, string protocolId)
{
var startManualFileSelect = new Action<string>(defaultPath =>
{
if (defaultPath.StartsWith("sftp://"))
Util.ShowSftpDialog(this, filename => OnReceivedSftpData(filename, browseRequestCode, isForSave), ReturnCancel);
else
//todo oncreate nur wenn for save?
Util.ShowFilenameDialog(this, filename => OnOpenButton(filename, browseRequestCode),
filename => OnOpenButton(filename, browseRequestCode),
ReturnCancel, false, defaultPath, GetString(Resource.String.enter_filename_details_url),
browseRequestCode);
});
;
App.Kp2a.GetFileStorage(protocolId).StartSelectFile(new FileStorageSetupInitiatorActivity(this,
OnActivityResult,
startManualFileSelect
), isForSave, browseRequestCode, protocolId);
}
protected override void ShowAndroidBrowseDialog(int requestCode, bool isForSave)
{
Util.ShowBrowseDialog(this, requestCode, isForSave);
}
protected override bool IsStorageSelectionForSave
{
get { return Intent.GetBooleanExtra(FileStorageSetupDefs.ExtraIsForSave, false); }
}
protected override void OnSaveInstanceState(Bundle outState)
{
base.OnSaveInstanceState(outState);
outState.PutAll(State);
if (_selectedIoc != null)
outState.PutString(BundleKeySelectedIoc, IOConnectionInfo.SerializeToString(_selectedIoc));
}
protected override void OnResume()
{
base.OnResume();
_design.ReapplyTheme();
}
protected override void ReturnCancel()
{
SetResult(Result.Canceled);
Finish();
}
protected override void ReturnOk(IOConnectionInfo ioc)
{
Intent intent = new Intent();
PasswordActivity.PutIoConnectionToIntent(ioc, intent);
SetResult(Result.Ok, intent);
Finish();
}
protected override void ShowAlertDialog(string message, EventHandler<DialogClickEventArgs> onOk, EventHandler<DialogClickEventArgs> onCancel)
{
new AlertDialog.Builder(this)
.SetPositiveButton(Android.Resource.String.Ok, onOk)
.SetMessage(message)
.SetCancelable(false)
.SetNegativeButton(Android.Resource.String.Cancel, onCancel)
.Create()
.Show();
}
protected override WritableRequirements RequestedWritableRequirements
{
get { return (WritableRequirements) Intent.GetIntExtra(ExtraKeyWritableRequirements, (int)WritableRequirements.ReadOnly); }
}
protected override void PerformCopy(Func<Action> copyAndReturnPostExecute)
{
new SimpleLoadingDialog(this, GetString(Resource.String.CopyingFile), false,
copyAndReturnPostExecute
).Execute();
}
protected override void StartFileStorageSelection(int requestCode, bool allowThirdPartyGet,
bool allowThirdPartySend)
{
#if !EXCLUDE_FILECHOOSER
Intent intent = new Intent(this, typeof(FileStorageSelectionActivity));
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, allowThirdPartyGet);
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, allowThirdPartySend);
StartActivityForResult(intent, requestCode);
#else
Toast.MakeText(this, "File chooser is excluded!", ToastLength.Long).Show();
#endif
}
protected override void StartFileChooser(string defaultPath, int requestCode, bool forSave)
{
#if !EXCLUDE_FILECHOOSER
Kp2aLog.Log("FSA: defaultPath=" + defaultPath);
string fileProviderAuthority = FileChooserFileProvider.TheAuthority;
if (defaultPath.StartsWith("file://"))
{
fileProviderAuthority = PackageName + ".android-filechooser.localfile";
}
Intent i = Keepass2android.Kp2afilechooser.Kp2aFileChooserBridge.GetLaunchFileChooserIntent(this, fileProviderAuthority,
defaultPath);
if (forSave)
{
i.PutExtra("group.pals.android.lib.ui.filechooser.FileChooserActivity.save_dialog", true);
var ext = UrlUtil.GetExtension(defaultPath);
if ((ext != String.Empty) && (ext.Contains("?")==false))
i.PutExtra("group.pals.android.lib.ui.filechooser.FileChooserActivity.default_file_ext", ext);
}
StartActivityForResult(i, requestCode);
#else
Toast.MakeText(this, "File chooser is excluded!", ToastLength.Long).Show();
#endif
}
public void OnDismiss(IDialogInterface dialog)
{
// ReturnCancel();
}
}
}

View File

@ -114,7 +114,7 @@ namespace OtpProviderClient
| ((hash[offset + 2] & 0xff) << 8) //Math. | ((hash[offset + 2] & 0xff) << 8) //Math.
| (hash[offset + 3] & 0xff); //Math. | (hash[offset + 3] & 0xff); //Math.
int password = binary % (int)Math.Pow(10, _Length); //Math. int password = binary % Convert.ToInt32(Math.Pow(10, _Length)); //Math.
return password.ToString(new string('0', _Length)); //Math. return password.ToString(new string('0', _Length)); //Math.
} }
} }

View File

@ -14,8 +14,8 @@ namespace keepass2android.Utils
private readonly Context _context; private readonly Context _context;
private readonly string _message; private readonly string _message;
private readonly bool _cancelable; private readonly bool _cancelable;
readonly Func<Object[], Object> _doInBackground; private readonly Func<Object[], Object> _doInBackground;
readonly Action<Object> _onPostExecute; private readonly Action<Object> _onPostExecute;
private ProgressDialog mDialog; private ProgressDialog mDialog;
/** /**
@ -30,7 +30,8 @@ namespace keepass2android.Utils
private Exception mLastException; private Exception mLastException;
public LoadingDialog(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) public LoadingDialog(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{ {
} }
@ -58,7 +59,8 @@ namespace keepass2android.Utils
} }
} }
public LoadingDialog(Context context, bool cancelable, Func<Object[], Object> doInBackground, Action<Object> onPostExecute) public LoadingDialog(Context context, bool cancelable, Func<Object[], Object> doInBackground,
Action<Object> onPostExecute)
{ {
_message = context.GetString(Resource.String.loading); _message = context.GetString(Resource.String.loading);
_context = context; _context = context;
@ -97,24 +99,33 @@ namespace keepass2android.Utils
* If you override this method, you must call {@code super.onCancelled()} at * If you override this method, you must call {@code super.onCancelled()} at
* beginning of the method. * beginning of the method.
*/ */
protected override void OnCancelled() {
protected override void OnCancelled()
{
DoFinish(); DoFinish();
base.OnCancelled(); base.OnCancelled();
}// onCancelled() }
private void DoFinish() { // onCancelled()
private void DoFinish()
{
mFinished = true; mFinished = true;
try { try
{
/* /*
* Sometime the activity has been finished before we dismiss this * Sometime the activity has been finished before we dismiss this
* dialog, it will raise error. * dialog, it will raise error.
*/ */
mDialog.Dismiss(); mDialog.Dismiss();
} catch (Exception e) }
catch (Exception e)
{ {
Kp2aLog.Log(e.ToString()); Kp2aLog.Log(e.ToString());
} }
}// doFinish() }
// doFinish()
/** /**
@ -124,18 +135,26 @@ namespace keepass2android.Utils
* @param t * @param t
* {@link Throwable} * {@link Throwable}
*/ */
protected void SetLastException(Exception e) {
protected void SetLastException(Exception e)
{
mLastException = e; mLastException = e;
}// setLastException() }
// setLastException()
/** /**
* Gets last exception. * Gets last exception.
* *
* @return {@link Throwable} * @return {@link Throwable}
*/ */
protected Exception GetLastException() {
protected Exception GetLastException()
{
return mLastException; return mLastException;
}// getLastException() }
// getLastException()
protected override Object DoInBackground(params Object[] @params) protected override Object DoInBackground(params Object[] @params)
@ -161,4 +180,37 @@ namespace keepass2android.Utils
} }
public class SimpleLoadingDialog : LoadingDialog<object, object, object>
{
private class BackgroundResult : Object
{
private readonly Action _onPostExec;
public BackgroundResult(Action onPostExec)
{
_onPostExec = onPostExec;
}
public Action OnPostExec
{
get { return _onPostExec; }
}
}
public SimpleLoadingDialog(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
public SimpleLoadingDialog(Context ctx, string message, bool cancelable, Func<Action> doInBackgroundReturnOnPostExec)
: base(ctx, message, cancelable, input =>
{ return new BackgroundResult(doInBackgroundReturnOnPostExec()); }
, res => { ((BackgroundResult) res).OnPostExec(); })
{
}
}
} }

View File

@ -141,7 +141,7 @@ namespace keepass2android
return list.Count > 0; return list.Count > 0;
} }
public static void ShowBrowseDialog(string filename, Activity act, int requestCodeBrowse, bool forSaving) public static void ShowBrowseDialog(Activity act, int requestCodeBrowse, bool forSaving)
{ {
if ((!forSaving) && (IsIntentAvailable(act, Intent.ActionGetContent, "*/*", new List<string> { Intent.CategoryOpenable}))) if ((!forSaving) && (IsIntentAvailable(act, Intent.ActionGetContent, "*/*", new List<string> { Intent.CategoryOpenable})))
{ {
@ -223,7 +223,7 @@ namespace keepass2android
public delegate bool FileSelectedHandler(string filename); public delegate bool FileSelectedHandler(string filename);
public static void ShowSftpDialog(Activity activity, FileSelectedHandler onStartBrowse) public static void ShowSftpDialog(Activity activity, FileSelectedHandler onStartBrowse, Action onCancel)
{ {
#if !EXCLUDE_JAVAFILESTORAGE #if !EXCLUDE_JAVAFILESTORAGE
AlertDialog.Builder builder = new AlertDialog.Builder(activity); AlertDialog.Builder builder = new AlertDialog.Builder(activity);
@ -244,7 +244,9 @@ namespace keepass2android
password); password);
onStartBrowse(sftpPath); onStartBrowse(sftpPath);
}); });
builder.SetNegativeButton(Android.Resource.String.Cancel, (sender, args) => {}); EventHandler<DialogClickEventArgs> evtH = new EventHandler<DialogClickEventArgs>( (sender, e) => onCancel());
builder.SetNegativeButton(Android.Resource.String.Cancel, evtH);
builder.SetTitle(activity.GetString(Resource.String.enter_sftp_login_title)); builder.SetTitle(activity.GetString(Resource.String.enter_sftp_login_title));
Dialog dialog = builder.Create(); Dialog dialog = builder.Create();
@ -252,16 +254,50 @@ namespace keepass2android
#endif #endif
} }
public static void ShowFilenameDialog(Activity activity, FileSelectedHandler onOpen, FileSelectedHandler onCreate, bool showBrowseButton, class DismissListener: Java.Lang.Object, IDialogInterfaceOnDismissListener
string defaultFilename, string detailsText, int requestCodeBrowse) {
private readonly Action _onDismiss;
public DismissListener(Action onDismiss)
{
_onDismiss = onDismiss;
}
public void OnDismiss(IDialogInterface dialog)
{
_onDismiss();
}
}
class CancelListener: Java.Lang.Object, IDialogInterfaceOnCancelListener
{
private readonly Action _onCancel;
public CancelListener(Action onCancel)
{
_onCancel = onCancel;
}
public void OnCancel(IDialogInterface dialog)
{
_onCancel();
}
}
public static void ShowFilenameDialog(Activity activity, FileSelectedHandler onOpen, FileSelectedHandler onCreate, Action onCancel, bool showBrowseButton, string defaultFilename, string detailsText, int requestCodeBrowse)
{ {
AlertDialog.Builder builder = new AlertDialog.Builder(activity); AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.SetView(activity.LayoutInflater.Inflate(Resource.Layout.file_selection_filename, null)); builder.SetView(activity.LayoutInflater.Inflate(Resource.Layout.file_selection_filename, null));
if (onCancel != null)
builder.SetOnCancelListener(new CancelListener(onCancel));
Dialog dialog = builder.Create(); Dialog dialog = builder.Create();
dialog.Show(); dialog.Show();
Button openButton = (Button) dialog.FindViewById(Resource.Id.open); Button openButton = (Button) dialog.FindViewById(Resource.Id.open);
Button createButton = (Button) dialog.FindViewById(Resource.Id.create); Button createButton = (Button) dialog.FindViewById(Resource.Id.create);
TextView enterFilenameDetails = (TextView) dialog.FindViewById(Resource.Id.label_open_by_filename_details); TextView enterFilenameDetails = (TextView) dialog.FindViewById(Resource.Id.label_open_by_filename_details);
openButton.Visibility = onOpen != null ? ViewStates.Visible : ViewStates.Gone; openButton.Visibility = onOpen != null ? ViewStates.Visible : ViewStates.Gone;
createButton.Visibility = onCreate != null? ViewStates.Visible : ViewStates.Gone; createButton.Visibility = onCreate != null? ViewStates.Visible : ViewStates.Gone;
@ -290,7 +326,17 @@ namespace keepass2android
}; };
Button cancelButton = (Button) dialog.FindViewById(Resource.Id.fnv_cancel); Button cancelButton = (Button) dialog.FindViewById(Resource.Id.fnv_cancel);
cancelButton.Click += (sender, e) => dialog.Dismiss(); cancelButton.Click += delegate
{
dialog.Dismiss();
if (onCancel != null)
onCancel();
};
ImageButton browseButton = (ImageButton) dialog.FindViewById(Resource.Id.browse_button); ImageButton browseButton = (ImageButton) dialog.FindViewById(Resource.Id.browse_button);
if (!showBrowseButton) if (!showBrowseButton)
@ -301,7 +347,7 @@ namespace keepass2android
{ {
string filename = ((EditText) dialog.FindViewById(Resource.Id.file_filename)).Text; string filename = ((EditText) dialog.FindViewById(Resource.Id.file_filename)).Text;
Util.ShowBrowseDialog(filename, activity, requestCodeBrowse, onCreate != null); Util.ShowBrowseDialog(activity, requestCodeBrowse, onCreate != null);
}; };

View File

@ -392,6 +392,10 @@ namespace keepass2android
} }
public IFileStorage GetFileStorage(IOConnectionInfo iocInfo) public IFileStorage GetFileStorage(IOConnectionInfo iocInfo)
{
return GetFileStorage(iocInfo, true);
}
public IFileStorage GetFileStorage(IOConnectionInfo iocInfo, bool allowCache)
{ {
if (iocInfo.IsLocalFile()) if (iocInfo.IsLocalFile())
return new BuiltInFileStorage(this); return new BuiltInFileStorage(this);
@ -399,9 +403,8 @@ namespace keepass2android
{ {
IFileStorage innerFileStorage = GetCloudFileStorage(iocInfo); IFileStorage innerFileStorage = GetCloudFileStorage(iocInfo);
if (DatabaseCacheEnabled) if (DatabaseCacheEnabled && allowCache)
{ {
//TODO
return new CachingFileStorage(innerFileStorage, Application.Context.CacheDir.Path, this); return new CachingFileStorage(innerFileStorage, Application.Context.CacheDir.Path, this);
} }
else else
@ -443,7 +446,8 @@ namespace keepass2android
new SftpFileStorage(this), new SftpFileStorage(this),
#endif #endif
#endif #endif
new BuiltInFileStorage(this) new BuiltInFileStorage(this),
new AndroidContentStorage(Application.Context)
}; };
} }
return _fileStorages; return _fileStorages;

View File

@ -69,6 +69,7 @@ namespace keepass2android
view.FileSelectButtons _fileSelectButtons; view.FileSelectButtons _fileSelectButtons;
internal AppTask AppTask; internal AppTask AppTask;
private const int RequestCodeSelectIoc = 456;
public const string NoForwardToPasswordActivity = "NoForwardToPasswordActivity"; public const string NoForwardToPasswordActivity = "NoForwardToPasswordActivity";
@ -129,9 +130,12 @@ namespace keepass2android
EventHandler openFileButtonClick = (sender, e) => EventHandler openFileButtonClick = (sender, e) =>
{ {
Intent intent = new Intent(this, typeof(FileStorageSelectionActivity)); Intent intent = new Intent(this, typeof(SelectStorageLocationActivity));
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, true); intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, true);
StartActivityForResult(intent, 0); intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, false);
intent.PutExtra(SelectStorageLocationActivity.ExtraKeyWritableRequirements, (int) SelectStorageLocationActivity.WritableRequirements.WriteDesired);
intent.PutExtra(FileStorageSetupDefs.ExtraIsForSave, false);
StartActivityForResult(intent, RequestCodeSelectIoc);
}; };
openFileButton.Click += openFileButtonClick; openFileButton.Click += openFileButtonClick;
@ -294,19 +298,7 @@ namespace keepass2android
App.Kp2a.GetFileStorage(ioc) App.Kp2a.GetFileStorage(ioc)
.PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), ioc, 0, false); .PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), ioc, 0, false);
} }
private bool OnOpenButton(String fileName)
{
IOConnectionInfo ioc = new IOConnectionInfo
{
Path = fileName
};
LaunchPasswordActivityForIoc(ioc);
return true;
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{ {
@ -326,59 +318,6 @@ namespace keepass2android
FillData(); FillData();
if (resultCode == KeePass.ExitFileStorageSelectionOk)
{
string protocolId = data.GetStringExtra("protocolId");
if (protocolId == "androidget")
{
string defaultFilename = Environment.ExternalStorageDirectory +
GetString(Resource.String.default_file_path);
Util.ShowBrowseDialog(defaultFilename, this, Intents.RequestCodeFileBrowseForOpen, false);
}
else
{
App.Kp2a.GetFileStorage(protocolId).StartSelectFile(new FileStorageSetupInitiatorActivity(this,
OnActivityResult,
defaultPath =>
{
if (defaultPath.StartsWith("sftp://"))
Util.ShowSftpDialog(this, OnReceivedSftpData);
else
Util.ShowFilenameDialog(this, OnOpenButton, null, false, defaultPath, GetString(Resource.String.enter_filename_details_url),
Intents.RequestCodeFileBrowseForOpen);
}
), false, 0, protocolId);
}
}
if ( (requestCode == Intents.RequestCodeFileBrowseForCreate
|| requestCode == Intents.RequestCodeFileBrowseForOpen)
&& resultCode == Result.Ok) {
string filename = Util.IntentToFilename(data, this);
if (filename != null) {
if (filename.StartsWith("file://")) {
filename = filename.Substring(7);
filename = Java.Net.URLDecoder.Decode(filename);
}
if (requestCode == Intents.RequestCodeFileBrowseForOpen)
{
IOConnectionInfo ioc = new IOConnectionInfo
{
Path = filename
};
LaunchPasswordActivityForIoc(ioc);
}
}
}
if (resultCode == (Result)FileStorageResults.FileUsagePrepared) if (resultCode == (Result)FileStorageResults.FileUsagePrepared)
{ {
@ -386,49 +325,16 @@ namespace keepass2android
PasswordActivity.SetIoConnectionFromIntent(ioc, data); PasswordActivity.SetIoConnectionFromIntent(ioc, data);
LaunchPasswordActivityForIoc(ioc); LaunchPasswordActivityForIoc(ioc);
} }
if (resultCode == (Result)FileStorageResults.FileChooserPrepared)
if ((resultCode == Result.Ok) && (requestCode == RequestCodeSelectIoc))
{ {
IOConnectionInfo ioc = new IOConnectionInfo(); IOConnectionInfo ioc = new IOConnectionInfo();
PasswordActivity.SetIoConnectionFromIntent(ioc, data); PasswordActivity.SetIoConnectionFromIntent(ioc, data);
#if !EXCLUDE_FILECHOOSER
StartFileChooser(ioc.Path);
#else
LaunchPasswordActivityForIoc(new IOConnectionInfo { Path = "/mnt/sdcard/keepass/yubi.kdbx"});
#endif
}
if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE")))
{
Toast.MakeText(this, data.GetStringExtra("EXTRA_ERROR_MESSAGE"), ToastLength.Long).Show();
}
}
private bool OnReceivedSftpData(string filename)
{
IOConnectionInfo ioc = new IOConnectionInfo { Path = filename };
#if !EXCLUDE_FILECHOOSER
StartFileChooser(ioc.Path);
#else
LaunchPasswordActivityForIoc(ioc); LaunchPasswordActivityForIoc(ioc);
#endif
return true;
} }
#if !EXCLUDE_FILECHOOSER
private void StartFileChooser(string defaultPath)
{
Kp2aLog.Log("FSA: defaultPath="+defaultPath);
string fileProviderAuthority = FileChooserFileProvider.TheAuthority;
if (defaultPath.StartsWith("file://"))
{
fileProviderAuthority = PackageName+".android-filechooser.localfile";
}
Intent i = Keepass2android.Kp2afilechooser.Kp2aFileChooserBridge.GetLaunchFileChooserIntent(this, fileProviderAuthority,
defaultPath);
StartActivityForResult(i, Intents.RequestCodeFileBrowseForOpen);
} }
#endif
protected override void OnResume() protected override void OnResume()
{ {
base.OnResume(); base.OnResume();

View File

@ -58,6 +58,12 @@ namespace keepass2android.fileselect
} }
protected override void OnRestart()
{
base.OnRestart();
_isRecreated = true;
}
protected override void OnStart() protected override void OnStart()
{ {
base.OnStart(); base.OnStart();

View File

@ -3,8 +3,6 @@
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{A6CF8A86-37C1-4197-80FE-519DE2C842F5}</ProjectGuid> <ProjectGuid>{A6CF8A86-37C1-4197-80FE-519DE2C842F5}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
@ -17,12 +15,9 @@
<AssemblyName>keepass2android</AssemblyName> <AssemblyName>keepass2android</AssemblyName>
<newfilesearch>OnLoad</newfilesearch> <newfilesearch>OnLoad</newfilesearch>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest> <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<TargetFrameworkVersion>v4.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.4</TargetFrameworkVersion>
<AndroidStoreUncompressedFileExtensions />
<MandroidI18n /> <MandroidI18n />
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize> <JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
<JavaOptions />
<AndroidUseLatestPlatformSdk />
<AndroidSupportedAbis>armeabi,armeabi-v7a,x86</AndroidSupportedAbis> <AndroidSupportedAbis>armeabi,armeabi-v7a,x86</AndroidSupportedAbis>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@ -30,7 +25,7 @@
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath> <OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;EXCLUDE_TWOFISH;INCLUDE_KEYBOARD;INCLUDE_FILECHOOSER;INCLUDE_JAVAFILESTORAGE;EXCLUDE_KEYTRANSFORM</DefineConstants> <DefineConstants>DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;EXCLUDE_FILECHOOSER;_EXCLUDE_JAVAFILESTORAGE;INCLUDE_KEYTRANSFORM</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<ConsolePause>False</ConsolePause> <ConsolePause>False</ConsolePause>
@ -40,7 +35,8 @@
<Command type="BeforeBuild" command="UseManifestNet.bat" /> <Command type="BeforeBuild" command="UseManifestNet.bat" />
</CustomCommands> </CustomCommands>
</CustomCommands> </CustomCommands>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk> <EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
<AndroidSupportedAbis>armeabi;armeabi-v7a;x86</AndroidSupportedAbis>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType> <DebugType>none</DebugType>
@ -50,7 +46,6 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime> <AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<ConsolePause>False</ConsolePause> <ConsolePause>False</ConsolePause>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
<AndroidSupportedAbis>armeabi,armeabi-v7a</AndroidSupportedAbis> <AndroidSupportedAbis>armeabi,armeabi-v7a</AndroidSupportedAbis>
<CustomCommands> <CustomCommands>
<CustomCommands> <CustomCommands>
@ -61,7 +56,10 @@
<DefineConstants>RELEASE</DefineConstants> <DefineConstants>RELEASE</DefineConstants>
<AndroidLinkSkip>System.Core%3b</AndroidLinkSkip> <AndroidLinkSkip>System.Core%3b</AndroidLinkSkip>
<EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk> <EmbedAssembliesIntoApk>True</EmbedAssembliesIntoApk>
<BundleAssemblies>False</BundleAssemblies> <AndroidStoreUncompressedFileExtensions>
</AndroidStoreUncompressedFileExtensions>
<JavaOptions>
</JavaOptions>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU' ">
<DebugType>none</DebugType> <DebugType>none</DebugType>
@ -69,7 +67,6 @@
<OutputPath>bin\ReleaseNoNet</OutputPath> <OutputPath>bin\ReleaseNoNet</OutputPath>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime> <AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
<DefineConstants>NoNet</DefineConstants> <DefineConstants>NoNet</DefineConstants>
<CustomCommands> <CustomCommands>
<CustomCommands> <CustomCommands>
@ -78,15 +75,27 @@
</CustomCommands> </CustomCommands>
<AndroidSupportedAbis>armeabi,armeabi-v7a</AndroidSupportedAbis> <AndroidSupportedAbis>armeabi,armeabi-v7a</AndroidSupportedAbis>
<DeployExternal>True</DeployExternal> <DeployExternal>True</DeployExternal>
<AndroidStoreUncompressedFileExtensions>
</AndroidStoreUncompressedFileExtensions>
<JavaOptions>
</JavaOptions>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="Mono.Android" /> <Reference Include="Mono.Android" />
<Reference Include="Mono.Android.Support.v4" /> <Reference Include="GooglePlayServicesLib">
<Reference Include="GooglePlayServicesFroyoLib"> <HintPath>..\Components\googleplayservices-19.0.0\lib\android\GooglePlayServicesLib.dll</HintPath>
<HintPath>..\Components\googleplayservicesfroyo-9.0\lib\android\GooglePlayServicesFroyoLib.dll</HintPath> </Reference>
<Reference Include="Xamarin.Android.Support.v4">
<HintPath>..\Components\googleplayservices-19.0.0\lib\android\Xamarin.Android.Support.v4.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Android.Support.v7.AppCompat">
<HintPath>..\Components\googleplayservices-19.0.0\lib\android\Xamarin.Android.Support.v7.AppCompat.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Android.Support.v7.MediaRouter">
<HintPath>..\Components\googleplayservices-19.0.0\lib\android\Xamarin.Android.Support.v7.MediaRouter.dll</HintPath>
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -138,6 +147,7 @@
<Compile Include="fileselect\FileSelectActivity.cs" /> <Compile Include="fileselect\FileSelectActivity.cs" />
<Compile Include="fileselect\FileDbHelper.cs" /> <Compile Include="fileselect\FileDbHelper.cs" />
<Compile Include="search\SearchProvider.cs" /> <Compile Include="search\SearchProvider.cs" />
<Compile Include="SelectStorageLocationActivity.cs" />
<Compile Include="services\OngoingNotificationsService.cs" /> <Compile Include="services\OngoingNotificationsService.cs" />
<Compile Include="settings\DatabaseSettingsActivity.cs" /> <Compile Include="settings\DatabaseSettingsActivity.cs" />
<Compile Include="intents\Intents.cs" /> <Compile Include="intents\Intents.cs" />
@ -663,12 +673,8 @@
<Folder Include="SupportLib\" /> <Folder Include="SupportLib\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\AndroidFileChooserBinding\AndroidFileChooserBinding.csproj">
<Project>{3c0f7fe5-639f-4422-a087-8b26cf862d1b}</Project>
<Name>AndroidFileChooserBinding</Name>
</ProjectReference>
<ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj"> <ProjectReference Include="..\JavaFileStorageBindings\JavaFileStorageBindings.csproj">
<Project>{48574278-4779-4b3a-a9e4-9cf1bc285d0b}</Project> <Project>{48574278-4779-4B3A-A9E4-9CF1BC285D0B}</Project>
<Name>JavaFileStorageBindings</Name> <Name>JavaFileStorageBindings</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj"> <ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj">
@ -680,21 +686,25 @@
<Name>Kp2aBusinessLogic</Name> <Name>Kp2aBusinessLogic</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\KP2AKdbLibraryBinding\KP2AKdbLibraryBinding.csproj"> <ProjectReference Include="..\KP2AKdbLibraryBinding\KP2AKdbLibraryBinding.csproj">
<Project>{70d3844a-d9fa-4a64-b205-a84c6a822196}</Project> <Project>{70D3844A-D9FA-4A64-B205-A84C6A822196}</Project>
<Name>KP2AKdbLibraryBinding</Name> <Name>KP2AKdbLibraryBinding</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\Kp2aKeyboardBinding\Kp2aKeyboardBinding.csproj"> <ProjectReference Include="..\Kp2aKeyboardBinding\Kp2aKeyboardBinding.csproj">
<Project>{a8779d4d-7c49-4c2f-82bd-2cdc448391da}</Project> <Project>{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}</Project>
<Name>Kp2aKeyboardBinding</Name> <Name>Kp2aKeyboardBinding</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\PluginSdkBinding\PluginSdkBinding.csproj"> <ProjectReference Include="..\PluginSdkBinding\PluginSdkBinding.csproj">
<Project>{3da3911e-36de-465e-8f15-f1991b6437e5}</Project> <Project>{3DA3911E-36DE-465E-8F15-F1991B6437E5}</Project>
<Name>PluginSdkBinding</Name> <Name>PluginSdkBinding</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\TwofishCipher\TwofishCipher.csproj"> <ProjectReference Include="..\TwofishCipher\TwofishCipher.csproj">
<Project>{5cf675a5-9bee-4720-bed9-d5bf14a2ebf9}</Project> <Project>{5CF675A5-9BEE-4720-BED9-D5BF14A2EBF9}</Project>
<Name>TwofishCipher</Name> <Name>TwofishCipher</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\AndroidFileChooserBinding\AndroidFileChooserBinding.csproj">
<Project>{3C0F7FE5-639F-4422-A087-8B26CF862D1B}</Project>
<Name>AndroidFileChooserBinding</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<ProjectExtensions> <ProjectExtensions>
<MonoDevelop> <MonoDevelop>
@ -832,12 +842,6 @@
<ItemGroup> <ItemGroup>
<AndroidResource Include="Resources\layout\text_with_help.xml" /> <AndroidResource Include="Resources\layout\text_with_help.xml" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<XamarinComponentReference Include="googleplayservicesfroyo">
<Version>9.0</Version>
<Visible>False</Visible>
</XamarinComponentReference>
</ItemGroup>
<ItemGroup> <ItemGroup>
<AndroidResource Include="Resources\drawable\ic_storage_skydrive.png" /> <AndroidResource Include="Resources\drawable\ic_storage_skydrive.png" />
</ItemGroup> </ItemGroup>
@ -981,4 +985,10 @@
<ItemGroup> <ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\device_access_new_account_holodark.png" /> <AndroidResource Include="Resources\drawable-hdpi\device_access_new_account_holodark.png" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<XamarinComponentReference Include="googleplayservices">
<Version>19.0.0</Version>
<Visible>False</Visible>
</XamarinComponentReference>
</ItemGroup>
</Project> </Project>