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/PluginSdkBinding/obj/ReleaseNoNet
/src/MasterKeeWinPlugin/bin/Release
/src/SamplePlugin

View File

@ -3,8 +3,6 @@
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{3C0F7FE5-639F-4422-A087-8B26CF862D1B}</ProjectGuid>
<ProjectTypeGuids>{10368E6C-D01B-4462-8E8B-01FC667A7035};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType>
@ -22,6 +20,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -30,6 +29,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'">
<OutputPath>bin\ReleaseNoNet\</OutputPath>
@ -39,30 +39,30 @@
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="System" />
<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>
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<LibraryProjectZip Include="..\java\android-filechooser\code\project.zip">
<Link>project.zip</Link>
</LibraryProjectZip>
<None Include="Jars\AboutJars.txt" />
<None Include="Additions\AboutAdditions.txt" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<TransformFile Include="Transforms\Metadata.xml" />
<TransformFile Include="Transforms\EnumFields.xml" />
<TransformFile Include="Transforms\EnumMethods.xml" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\android-support-v4.jar" />
</ItemGroup>
<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.
Other similar extension points exist, see Microsoft.Common.targets.
@ -71,4 +71,15 @@
<Target Name="AfterBuild">
</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>

View File

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

View File

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

View File

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

View File

@ -41,15 +41,16 @@ namespace KeePassLib.Keys
/// </summary>
public sealed class KcpKeyFile : IUserKey
{
private string m_strPath;
private IOConnectionInfo m_ioc;
private ProtectedBinary m_pbKeyData;
private ProtectedBinary m_pbFileData;
/// <summary>
/// Path to the key file.
/// </summary>
public string Path
{
get { return m_strPath; }
get { return m_ioc.Path; }
}
/// <summary>
@ -62,6 +63,16 @@ namespace KeePassLib.Keys
get { return m_pbKeyData; }
}
public IOConnectionInfo Ioc
{
get { return m_ioc; }
}
public ProtectedBinary RawFileData
{
get { return m_pbFileData; }
}
public KcpKeyFile(string strKeyFile)
{
Construct(IOConnectionInfo.FromPath(strKeyFile), false);
@ -82,17 +93,22 @@ namespace KeePassLib.Keys
Construct(iocKeyFile, bThrowIfDbFile);
}
private void Construct(IOConnectionInfo iocFile, bool bThrowIfDbFile)
public KcpKeyFile(byte[] keyFileContents, IOConnectionInfo iocKeyFile, bool bThrowIfDbFile)
{
byte[] pbFileData = IOConnection.ReadFile(iocFile);
if(pbFileData == null) throw new Java.IO.FileNotFoundException();
Construct(keyFileContents, iocKeyFile, bThrowIfDbFile);
}
if(bThrowIfDbFile && (pbFileData.Length >= 8))
private void Construct(byte[] pbFileData, IOConnectionInfo iocKeyFile, bool bThrowIfDbFile)
{
if (pbFileData == null) throw new Java.IO.FileNotFoundException();
m_pbFileData = new ProtectedBinary(true, pbFileData);
if (bThrowIfDbFile && (pbFileData.Length >= 8))
{
uint uSig1 = MemUtil.BytesToUInt32(MemUtil.Mid(pbFileData, 0, 4));
uint uSig2 = MemUtil.BytesToUInt32(MemUtil.Mid(pbFileData, 4, 4));
if(((uSig1 == KdbxFile.FileSignature1) &&
if (((uSig1 == KdbxFile.FileSignature1) &&
(uSig2 == KdbxFile.FileSignature2)) ||
((uSig1 == KdbxFile.FileSignaturePreRelease1) &&
(uSig2 == KdbxFile.FileSignaturePreRelease2)) ||
@ -106,16 +122,22 @@ namespace KeePassLib.Keys
}
byte[] pbKey = LoadXmlKeyFile(pbFileData);
if(pbKey == null) pbKey = LoadKeyFile(pbFileData);
if (pbKey == null) pbKey = LoadKeyFile(pbFileData);
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);
MemUtil.ZeroByteArray(pbKey);
}
private void Construct(IOConnectionInfo iocFile, bool bThrowIfDbFile)
{
byte[] pbFileData = IOConnection.ReadFile(iocFile);
Construct(pbFileData, iocFile, bThrowIfDbFile);
}
// public void Clear()
// {
// m_strPath = string.Empty;

View File

@ -647,14 +647,17 @@ namespace KeePassLib.Serialization
try
{
sIn = IOConnection.OpenRead(ioc);
if(sIn == null) return null;
if (sIn == null) return null;
ms = new MemoryStream();
MemUtil.CopyStream(sIn, ms);
return ms.ToArray();
}
catch(Exception) { }
catch (Exception e)
{
Kp2aLog.Log("error opening file: " + e);
}
finally
{
if(sIn != null) sIn.Close();

View File

@ -79,8 +79,17 @@ namespace keepass2android
Handler UiThreadHandler { get; }
IProgressDialog CreateProgressDialog(Context ctx);
/// <summary>
/// returns the file storage for the given ioc. might be a caching file storage
/// </summary>
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);
/// <summary>
@ -90,5 +99,7 @@ namespace keepass2android
//bool OnServerCertificateError(int certificateProblem);
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.Net;
using System.Net.Security;
using System.Security;
using Android.Content;
using Android.OS;
using Java.Security.Cert;
@ -295,5 +296,67 @@ namespace keepass2android.Io
res.Path += filename;
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));
}
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
{
_cachedStorage.PrepareFileUsage(ctx, ioc);
}
public void OnCreate(IFileStorageSetupActivity activity, Bundle 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)
{
File.WriteAllText(CachedFilePath(GetPseudoIoc(folderPath, filename)) + ".filepath", res.Path);

View File

@ -157,6 +157,17 @@ namespace keepass2android.Io
/// </summary>
/// The method may throw FileNotFoundException or not in case the file doesn't exist.
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

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)
{
_jfs.OnCreate(((IJavaFileStorageFileStorageSetupActivity)activity), savedInstanceState);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,7 +19,11 @@ namespace Kp2aUnitTests
TestRunner runner = new TestRunner();
// Run all tests from this assembly
//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(typeof(TestLoadDb).GetMethod("LoadErrorWithCertificateTrustFailure"));
//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();
}
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();
}
public IFileStorage GetFileStorage(IOConnectionInfo iocInfo)
public virtual IFileStorage GetFileStorage(IOConnectionInfo iocInfo)
{
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;
private TestFileStorage _testFileStorage;

View File

@ -2,11 +2,15 @@
using System.IO;
using System.Linq;
using Android.App;
using Com.Keepassdroid.Database.Load;
using Java.IO;
using KeePassLib;
using KeePassLib.Keys;
using KeePassLib.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using keepass2android;
using keepass2android.Io;
using FileNotFoundException = System.IO.FileNotFoundException;
namespace Kp2aUnitTests
{
@ -72,6 +76,56 @@ namespace Kp2aUnitTests
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]
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>
</PropertyGroup>
<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.Core" />
<Reference Include="System.Xml.Linq" />
@ -46,6 +42,9 @@
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<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>
<Compile Include="Main.cs" />
@ -57,7 +56,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MasterPassword\MasterPassword.csproj">
<Project>{2f7cb5b4-ac2a-4790-b0f3-42e6c9a060d5}</Project>
<Project>{2F7CB5B4-AC2A-4790-B0F3-42E6C9A060D5}</Project>
<Name>MasterPassword</Name>
</ProjectReference>
</ItemGroup>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,10 @@ public final class R {
public static int ic_launcher=0x7f020000;
}
public static final class string {
public static int action_settings=0x7f030002;
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 {
/**

View File

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<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>

View File

@ -10,8 +10,10 @@ public class AES {
private Cipher mCipherEncr;
private Cipher mCipherDecr;
private SecretKeySpec mKey;
private boolean ready;
public AES() {
ready = false;
}
public static byte[] getMD5(String s) {
@ -31,10 +33,10 @@ public class AES {
mCipherEncr = Cipher.getInstance("AES/CBC/NoPadding");
mCipherEncr.init(Cipher.ENCRYPT_MODE, mKey);
iv = mCipherEncr.getIV();
//System.out.println("AES IV: ");
Util.printHex(iv);
Util.printHex(iv, "AES IV: ");
mCipherDecr = Cipher.getInstance("AES/CBC/NoPadding");
mCipherDecr.init(Cipher.DECRYPT_MODE, mKey, new IvParameterSpec(iv));
ready = true;
} catch (Exception e) {
e.printStackTrace();
}
@ -49,4 +51,7 @@ public class AES {
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 BTService mBTService;
private PacketManager mPacketManager;
//private PacketQueue mPacketQueue;
private final BTHandler mBTHandler = new BTHandler(this);
@ -46,30 +45,11 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
case BTService.EVENT_CANCELLED:
manager.onDisconnected();
break;
case BTService.EVENT_CONNECTION_FAILED:
manager.onFailure(1);
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);
case BTService.EVENT_ERROR:
manager.onFailure(msg.arg1);
break;
default:
manager.onFailure(InputStickError.ERROR_BLUETOOTH);
}
}
}
@ -80,6 +60,7 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
private void onConnected() {
stateNotify(ConnectionManager.STATE_CONNECTED);
//mInitManager.startTimeoutCountdown(InitManager.DEFAULT_INIT_TIMEOUT);
mInitManager.onConnected();
}
@ -90,6 +71,7 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
private void onFailure(int code) {
mErrorCode = code;
stateNotify(ConnectionManager.STATE_FAILURE);
disconnect();
}
private void onData(byte[] rawData) {
@ -97,13 +79,12 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
data = mPacketManager.bytesToPacket(rawData);
if (data == null) {
//TODO
//TODO failure?
return;
}
mInitManager.onData(data);
//sendNext(); TODO
for (InputStickDataListener listener : mDataListeners) {
listener.onInputStickData(data);
}
@ -124,7 +105,7 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
public void connect(boolean reflection, int timeout) {
mErrorCode = ConnectionManager.ERROR_NONE;
mErrorCode = InputStickError.ERROR_NONE;
if (mBTService == null) {
mBTService = new BTService(mApp, mBTHandler);
mPacketManager = new PacketManager(mBTService, mKey);
@ -143,10 +124,14 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
}
}
public void disconnect(int failureCode) {
onFailure(failureCode);
}
@Override
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 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<InputStickDataListener> mDataListeners = new Vector<InputStickDataListener>();
@ -47,7 +40,9 @@ public abstract class ConnectionManager {
public void addStateListener(InputStickStateListener listener) {
if (listener != null) {
mStateListeners.add(listener);
if ( !mStateListeners.contains(listener)) {
mStateListeners.add(listener);
}
}
}
@ -59,7 +54,9 @@ public abstract class ConnectionManager {
public void addDataListener(InputStickDataListener listener) {
if (listener != null) {
mDataListeners.add(listener);
if ( !mDataListeners.contains(listener)) {
mDataListeners.add(listener);
}
}
}

View File

@ -15,10 +15,16 @@ public class HIDInfo {
private boolean mouseReady;
private boolean consumerReady;
// >= 0.93
private boolean sentToHostInfo;
private int keyboardReportsSentToHost;
private int mouseReportsSentToHost;
private int consumerReportsSentToHost;
public HIDInfo() {
keyboardReportProtocol = true;
mouseReportProtocol = true;
sentToHostInfo = false;
}
public void update(byte[] data) {
@ -70,6 +76,14 @@ public class HIDInfo {
} else {
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() {
@ -112,4 +126,24 @@ public class HIDInfo {
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;
case SERVICE_CMD_STATE:
//System.out.println("CMD STATE: "+msg.arg1);
manager.stateNotify(msg.arg1);
break;
}
@ -61,7 +60,6 @@ public class IPCConnectionManager extends ConnectionManager {
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
//System.out.println("onServiceConnected!");
mService = new Messenger(service);
mBound = true;
sendMessage(SERVICE_CMD_CONNECT, 0, 0);
@ -69,9 +67,10 @@ public class IPCConnectionManager extends ConnectionManager {
public void onServiceDisconnected(ComponentName className) {
// unexpectedly disconnected from service
//System.out.println("onService DISCONNECTED!");
mService = null;
mBound = false;
mErrorCode = InputStickError.ERROR_ANDROID_SERVICE_DISCONNECTED;
stateNotify(STATE_FAILURE);
stateNotify(STATE_DISCONNECTED);
}
};
@ -130,34 +129,32 @@ public class IPCConnectionManager extends ConnectionManager {
}
if (exists) {
mErrorCode = ConnectionManager.ERROR_NONE;
mErrorCode = InputStickError.ERROR_NONE;
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.inputstick.apps.inputstickutility","com.inputstick.apps.inputstickutility.service.InputStickService"));
mCtx.startService(intent);
mCtx.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
if (mBound) {
//already bound?
//System.out.println("Service already Connected");
//already bound
sendMessage(SERVICE_CMD_CONNECT, 0, 0);
}
} else {
mErrorCode = 1; //TODO
mErrorCode = InputStickError.ERROR_ANDROID_NO_UTILITY_APP;
stateNotify(STATE_FAILURE);
stateNotify(STATE_DISCONNECTED);
}
}
@Override
public void disconnect() {
if (mBound) {
//System.out.println("UNBIND");
sendMessage(SERVICE_CMD_DISCONNECT, 0, 0);
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.inputstick.apps.inputstickutility","com.inputstick.apps.inputstickutility.service.InputStickService"));
mCtx.unbindService(mConnection);
mCtx.stopService(intent);
mBound = false;
//TODO stateNotify
//service will pass notification message
//service will pass notification message (disconnected)
} else {
//just set state, there is nothing else to do
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_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;
@ -37,6 +43,7 @@ public class Packet {
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};
@ -119,4 +126,8 @@ public class Packet {
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) {
Random r = new Random();
Packet p = new Packet(true, Packet.CMD_INIT);
Packet p = new Packet(true, Packet.CMD_INIT_AUTH);
if (enable) {
p.addByte((byte)1);
} else {
@ -68,13 +72,13 @@ public class PacketManager {
initData = mAes.encrypt(initData);
p.addBytes(initData);
//Util.printHex(initData, "InitData: ");
Util.printHex(initData, "InitData: ");
cmpData = new byte[16];
r.nextBytes(cmpData);
p.addBytes(cmpData);
//Util.printHex(cmpData, "CmpData: ");
Util.printHex(cmpData, "CmpData: ");
return p;
}
@ -87,11 +91,15 @@ public class PacketManager {
payload = Arrays.copyOfRange(data, 2, data.length); //remove TAG, info
if ((data[1] & Packet.FLAG_ENCRYPTED) != 0) {
Util.log("DECRYPT");
payload = mAes.decrypt(payload);
//Util.log("DECRYPT");
if (mAes.isReady()) {
payload = mAes.decrypt(payload);
} else {
return null;
}
}
Util.printHex(payload, "DATA IN: ");
//Util.printHex(payload, "DATA IN: ");
//check CRC
crcCompare = Util.getLong(payload[0], payload[1], payload[2], payload[3]);
@ -114,7 +122,9 @@ public class PacketManager {
}
public void sendPacket(Packet p) {
sendPacket(p, mEncryption);
if (p != null) {
sendPacket(p, mEncryption);
}
}
public void sendPacket(Packet p, boolean encrypt) {
@ -137,7 +147,7 @@ public class PacketManager {
mCrc.reset();
mCrc.update(result, CRC_OFFSET, result.length - CRC_OFFSET);
crcValue = mCrc.getValue();
Util.log("CRC: "+crcValue);
//Util.log("CRC: "+crcValue);
result[3] = (byte)crcValue;
crcValue >>= 8;
result[2] = (byte)crcValue;

View File

@ -1,13 +1,25 @@
package com.inputstick.api;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public abstract class Util {
private static final boolean debug = false;
public static boolean debug = false;
public static void log(String msg) {
log(msg, false);
}
public static void log(String msg, boolean displayTime) {
if (debug) {
System.out.println("LOG: " + msg);
System.out.print("LOG: " + msg);
if (displayTime) {
System.out.print(" @ " + System.currentTimeMillis());
}
System.out.println();
}
}
@ -21,29 +33,35 @@ public abstract class Util {
public static void printHex(byte[] toPrint) {
if (debug) {
int cnt = 0;
String s;
byte b;
for (int i = 0; i < toPrint.length; i++) {
b = toPrint[i];
if ((b < 10) && (b >= 0)) {
s = Integer.toHexString((int)b);
s = "0" + s;
} else {
s = Integer.toHexString((int)b);
if (s.length() > 2) {
s = s.substring(s.length() - 2);
if (toPrint != null) {
int cnt = 0;
String s;
byte b;
for (int i = 0; i < toPrint.length; i++) {
b = toPrint[i];
//0x0..0xF = 0x00..0x0F
if ((b < 0x10) && (b >= 0)) {
s = Integer.toHexString((int)b);
s = "0" + s;
} else {
s = Integer.toHexString((int)b);
if (s.length() > 2) {
s = s.substring(s.length() - 2);
}
}
}
s = s.toUpperCase();
System.out.print("0x" + s + " ");
cnt++;
if (cnt == 8) {
System.out.println("");
cnt = 0;
}
}
System.out.println("\n#####");
s = s.toUpperCase();
System.out.print("0x" + s + " ");
cnt++;
if (cnt == 8) {
System.out.println("");
cnt = 0;
}
}
} else {
System.out.println("null");
}
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;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import android.app.AlertDialog;
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.ConnectionManager;
import com.inputstick.api.HIDInfo;
import com.inputstick.api.IPCConnectionManager;
import com.inputstick.api.InputStickDataListener;
import com.inputstick.api.InputStickError;
import com.inputstick.api.InputStickStateListener;
import com.inputstick.api.OnEmptyBufferListener;
import com.inputstick.api.Packet;
import com.inputstick.api.Util;
import com.inputstick.api.hid.HIDTransaction;
import com.inputstick.api.hid.HIDTransactionQueue;
import com.inputstick.init.InitManager;
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 Vector<InputStickStateListener> mStateListeners = new Vector<InputStickStateListener>();
private static InputStickHID instance = new InputStickHID();
private static HIDInfo mHIDInfo = new HIDInfo();
private static HIDInfo mHIDInfo;
private static HIDTransactionQueue keyboardQueue;
private static HIDTransactionQueue mouseQueue;
private static HIDTransactionQueue consumerQueue;
// >= FW 0.93
private static Timer t1;
private static boolean constantUpdateMode;
private InputStickHID() {
}
@ -38,9 +57,11 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
}
private static void init() {
keyboardQueue = new HIDTransactionQueue(HIDTransactionQueue.KEYBOARD, mConnectionManager);
mouseQueue = new HIDTransactionQueue(HIDTransactionQueue.MOUSE, mConnectionManager);
consumerQueue = new HIDTransactionQueue(HIDTransactionQueue.CONSUMER, mConnectionManager);
mHIDInfo = new HIDInfo();
constantUpdateMode = false;
keyboardQueue = new HIDTransactionQueue(INTERFACE_KEYBOARD, mConnectionManager);
mouseQueue = new HIDTransactionQueue(INTERFACE_MOUSE, mConnectionManager);
consumerQueue = new HIDTransactionQueue(INTERFACE_CONSUMER, mConnectionManager);
mConnectionManager.addStateListener(instance);
mConnectionManager.addDataListener(instance);
@ -55,8 +76,6 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
//direct Bluetooth connection
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);
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() {
if (getState() == ConnectionManager.STATE_READY) {
return true;
@ -90,7 +119,9 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
public static void addStateListener(InputStickStateListener listener) {
if (listener != null) {
mStateListeners.add(listener);
if ( !mStateListeners.contains(listener)) {
mStateListeners.add(listener);
}
}
}
@ -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) {
keyboardQueue.addTransaction(transaction);
}
@ -112,6 +159,18 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
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) {
if (mConnectionManager != null) {
mConnectionManager.sendPacket(p);
@ -123,24 +182,70 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
@Override
public void onStateChanged(int state) {
if ((state == ConnectionManager.STATE_DISCONNECTED) && (t1 != null)) {
t1.cancel();
t1 = null;
}
for (InputStickStateListener listener : mStateListeners) {
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
public void onInputStickData(byte[] data) {
if (data[0] == Packet.CMD_HID_STATUS) {
mHIDInfo.update(data);
if (mHIDInfo.isKeyboardReady()) {
keyboardQueue.deviceReady();
}
if (mHIDInfo.isMouseReady()) {
mouseQueue.deviceReady();
}
if (mHIDInfo.isConsumerReady()) {
consumerQueue.deviceReady();
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()) {
keyboardQueue.deviceReady(null, 0);
}
if (mHIDInfo.isMouseReady()) {
mouseQueue.deviceReady(null, 0);
}
if (mHIDInfo.isConsumerReady()) {
consumerQueue.deviceReady(null, 0);
}
}
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 android.util.SparseArray;
import com.inputstick.api.InputStickKeyboardListener;
import com.inputstick.api.hid.HIDKeycodes;
import com.inputstick.api.hid.HIDTransaction;
@ -13,6 +15,10 @@ public class InputStickKeyboard {
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 mNumLock;
private static boolean mCapsLock;
@ -20,12 +26,23 @@ public class InputStickKeyboard {
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() {
}
public static void addKeyboardListener(InputStickKeyboardListener listener) {
if (listener != null) {
mKeyboardListeners.add(listener);
if ( !mKeyboardListeners.contains(listener)) {
mKeyboardListeners.add(listener);
}
}
}
@ -115,4 +132,24 @@ public class InputStickKeyboard {
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;
import android.util.SparseArray;
import com.inputstick.api.hid.HIDTransaction;
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_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 InputStickMouse() {
@ -54,4 +65,24 @@ public class InputStickMouse {
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.Util;
import com.inputstick.api.InputStickError;
public class BTService {
@ -28,16 +29,7 @@ public class BTService {
public static final int EVENT_DATA = 1;
public static final int EVENT_CONNECTED = 2;
public static final int EVENT_CANCELLED = 3;
public static final int EVENT_CONNECTION_FAILED = 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;
public static final int EVENT_ERROR = 4;
private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); //SPP
@ -72,7 +64,6 @@ public class BTService {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
System.out.println("ACTION: "+action);
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
if ((state == BluetoothAdapter.STATE_ON) && (turnBluetoothOn)) {
@ -103,11 +94,11 @@ public class BTService {
mUseReflection = enabled;
}
private synchronized void event(int event) {
private synchronized void event(int event, int arg1) {
Util.log("event() " + mLastEvent + " -> " + event);
mLastEvent = event;
Message msg = Message.obtain(null, mLastEvent, 0, 0);
Message msg = Message.obtain(null, mLastEvent, arg1, 0);
mHandler.sendMessage(msg);
}
@ -163,7 +154,7 @@ public class BTService {
if (BluetoothAdapter.checkBluetoothAddress(mac)) {
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
event(EVENT_NO_BT_HW);
event(EVENT_ERROR, InputStickError.ERROR_BLUETOOTH_NOT_SUPPORTED);
} else {
if (mBluetoothAdapter.isEnabled()) {
doConnect(false);
@ -172,7 +163,7 @@ public class BTService {
}
}
} else {
event(EVENT_INVALID_MAC);
event(EVENT_ERROR, InputStickError.ERROR_BLUETOOTH_INVALID_MAC);
}
}
@ -180,7 +171,7 @@ public class BTService {
Util.log("disconnect");
disconnecting = true;
cancelThreads();
event(EVENT_CANCELLED);
event(EVENT_CANCELLED, 0);
}
@ -226,7 +217,7 @@ public class BTService {
mConnectedThread.start();
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()));
doConnect(true);
} else {
event(EVENT_CONNECTION_FAILED);
event(EVENT_ERROR, InputStickError.ERROR_BLUETOOTH_CONNECTION_FAILED);
}
}
}
@ -249,7 +240,7 @@ public class BTService {
if (disconnecting) {
disconnecting = false;
} else {
event(EVENT_CONNECTION_LOST);
event(EVENT_ERROR, InputStickError.ERROR_BLUETOOTH_CONNECTION_LOST);
}
}
@ -357,10 +348,12 @@ public class BTService {
int rxTmp;
int lengthByte;
int length;
int wdgCnt = 0;
while (true) {
try {
rxTmp = mmInStream.read();
if (rxTmp == Packet.START_TAG) {
wdgCnt = 0;
lengthByte = mmInStream.read();
length = lengthByte;
length &= 0x3F;
@ -373,8 +366,13 @@ public class BTService {
}
mHandler.obtainMessage(EVENT_DATA, 0, 0, buffer).sendToTarget();
} else {
System.out.println("RX: " + rxTmp);
//possible WDG reset
Util.log("Unexpected RX byte" + rxTmp);
if (rxTmp == 0xAF) {
wdgCnt++;
}
if (wdgCnt > 1024) {
//TODO
}
}
} catch (IOException e) {
connectionLost();
@ -388,7 +386,7 @@ public class BTService {
mmOutStream.write(buffer);
mmOutStream.flush();
} catch (IOException e) {
Util.log("Exception during write");
Util.log("write() exception");
}
}
@ -396,7 +394,7 @@ public class BTService {
try {
mmSocket.close();
} 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;
import android.util.SparseArray;
public class HIDKeycodes {
public static final byte NONE = 0x00;
@ -13,6 +15,7 @@ public class HIDKeycodes {
public static final byte ALT_RIGHT = 0x40;
public static final byte GUI_RIGHT = (byte)0x80;
public static final byte KEY_ENTER = 0x28;
public static final byte KEY_ESCAPE = 0x29;
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_DOT = 0x63;
public static final byte KEY_BACKSLASH_NON_US = 0x64;
public static final byte KEY_A = 0x04;
public static final byte KEY_B = 0x05;
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 = {
0, //000
0, //001
@ -257,6 +389,14 @@ public class HIDKeycodes {
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) {
return (byte)ASCIItoHID[c]; //TODO range
@ -266,7 +406,31 @@ public class HIDKeycodes {
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;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import com.inputstick.api.ConnectionManager;
import com.inputstick.api.HIDInfo;
import com.inputstick.api.OnEmptyBufferListener;
import com.inputstick.api.Packet;
import com.inputstick.api.basic.InputStickHID;
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 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 ConnectionManager mConnectionManager;
private final byte cmd;
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 minNextTime;
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>();
mConnectionManager = connectionManager;
ready = false;
switch (type) {
case KEYBOARD:
sentAhead = false;
minNextTime = 0;
mustNotify = false;
mInterfaceType = interfaceType;
switch (interfaceType) {
case InputStickHID.INTERFACE_KEYBOARD:
cmd = Packet.CMD_HID_DATA_KEYB;
break;
case MOUSE:
case InputStickHID.INTERFACE_MOUSE:
cmd = Packet.CMD_HID_DATA_MOUSE;
break;
case CONSUMER:
case InputStickHID.INTERFACE_CONSUMER:
cmd = Packet.CMD_HID_DATA_CONSUMER;
break;
default:
@ -41,26 +73,32 @@ public class HIDTransactionQueue {
}
}
private void sendNext() {
private int sendNext(int maxReports) {
HIDTransaction transaction;
byte reports = 0;
ready = false;
Packet p = new Packet(false, cmd, reports);
//assume there is at least 1 element in queue
transaction = queue.firstElement();
if (transaction.getReportsCount() > BUFFER_SIZE) {
//transaction too big! split
if (transaction.getReportsCount() > maxReports) {
// 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);
} else {
queue.removeElementAt(0);
}
byte reports = 0;
ready = false;
Packet p = new Packet(false, cmd, reports);
while (transaction.hasNext()) {
p.addBytes(transaction.getNextReport());
reports++;
}
//TODO add next transactions if possible
while(true) {
if (queue.isEmpty()) {
@ -68,7 +106,7 @@ public class HIDTransactionQueue {
}
transaction = queue.firstElement();
if (reports + transaction.getReportsCount() < BUFFER_SIZE) {
if (reports + transaction.getReportsCount() < maxReports) {
queue.removeElementAt(0);
while (transaction.hasNext()) {
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
mConnectionManager.sendPacket(p);
lastReports = reports;
lastTime = System.currentTimeMillis();
minNextTime = lastTime + (lastReports * 4) + BT_DELAY;
if (queue.isEmpty()) {
notifyOnLocalBufferEmpty();
}
return reports;
}
public void addTransaction(HIDTransaction transaction) {
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);
if (ready) {
sendNext();
sendNext(BUFFER_SIZE);
}
}
public void deviceReady() {
if (!queue.isEmpty()) {
sendNext();
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 {
ready = true;
timerCancelled = true;
sentAhead = false;
if (!queue.isEmpty()) {
sendNext(BUFFER_SIZE);
} else {
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 GermanLayout() {
@ -126,7 +169,12 @@ public class GermanLayout extends KeyboardLayout {
@Override
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
@ -139,4 +187,14 @@ public class GermanLayout extends KeyboardLayout {
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,
/* 0x54 */ 0,
/* 0x55 */ 0,
/* 0x56 */ 0,
/* 0x56 */ HIDKeycodes.KEY_BACKSLASH_NON_US, //GERMAN LAYOUT!
/* 0x57 */ HIDKeycodes.KEY_F11,
/* 0x58 */ HIDKeycodes.KEY_F12,
/* 0x59 */ 0,
@ -117,16 +117,19 @@ public abstract class KeyboardLayout {
public static final int LAYOUT_CODE = 0;
public abstract int[][] getLUT();
public abstract int[][] getDeadkeyLUT();
public abstract int[] getDeadkeys();
public abstract String getLocaleName();
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 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) {
char[] chars = text.toCharArray();
HIDTransaction t;
for (char c : chars) {
t = getHIDTransaction(lut, c);
t = getHIDTransaction(lut, deadkeyLUT, deadkeys, c, modifiers);
if (t != null) {
InputStickHID.addKeyboardTransaction(t);
}
@ -194,7 +197,7 @@ public abstract class KeyboardLayout {
}
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) {
continue;
} 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;
int scanCode;
@ -242,10 +275,40 @@ public abstract class KeyboardLayout {
if (scanCode > 0) {
key = getKey(scanCode);
modifiers = getModifiers(lut, scanCode, c);
modifiers |= additionalModifierKeys;
t.addReport(new KeyboardReport(modifiers, (byte)0));
t.addReport(new KeyboardReport(modifiers, key));
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;
}
@ -260,7 +323,22 @@ public abstract class KeyboardLayout {
return RussianLayout.getInstance();
} else if (locale.equals(GermanLayout.getInstance().getLocaleName())) {
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();

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 PolishLayout() {
@ -127,7 +153,12 @@ public class PolishLayout extends KeyboardLayout {
@Override
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
@ -140,5 +171,14 @@ public class PolishLayout extends KeyboardLayout {
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 RussianLayout() {
@ -128,7 +131,12 @@ public class RussianLayout extends KeyboardLayout {
@Override
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
@ -141,4 +149,14 @@ public class RussianLayout extends KeyboardLayout {
return LOCALE_NAME;
}
@Override
public int[][] getDeadkeyLUT() {
return DEADKEY_LUT;
}
@Override
public int[] getDeadkeys() {
return DEADKEYS;
}
}

View File

@ -98,10 +98,10 @@ public class UnitedStatesLayout extends KeyboardLayout {
/* 50 */ { -1 , 0 , 0 , 0 , 0 , 0 } ,
/* 51 */ { -1 , 0 , 0 , 0 , 0 , 0 } ,
/* 52 */ { -1 , 0 , 0 , 0 , 0 , 0 } ,
/* 53 */ { 0 , 0x002e , 0x002e , -1 , -1 , -1 } ,
/* 53 */ { 0 , 0x002e , 0x002e , -1 , -1 , -1 } ,
/* 54 */ { -1 , 0 , 0 , 0 , 0 , 0 } ,
/* 55 */ { -1 , 0 , 0 , 0 , 0 , 0 } ,
/* 56 */ { 0 , 0x005c , 0x007c , 0x001c , -1 , -1 } ,
/* 56 */ { 0 , 0x005c , 0x007c , 0x001c , -1 , -1 } ,
/* 57 */ { -1 , 0 , 0 , 0 , 0 , 0 } ,
/* 58 */ { -1 , 0 , 0 , 0 , 0 , 0 } ,
/* 59 */ { -1 , 0 , 0 , 0 , 0 , 0 } ,
@ -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 UnitedStatesLayout() {
@ -130,7 +133,12 @@ public class UnitedStatesLayout extends KeyboardLayout {
@Override
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
@ -143,4 +151,14 @@ public class UnitedStatesLayout extends KeyboardLayout {
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 {
private boolean initDone = false;
public BasicInitManager(byte[] key) {
super(key);
}
@ -14,48 +12,46 @@ public class BasicInitManager extends InitManager {
@Override
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(new Packet(true, Packet.CMD_RUN_FW));
}
@Override
public void onData(byte[] data) {
byte cmd = data[0];
byte respCode = data[1];
byte param = data[1];
if (cmd == Packet.CMD_RUN_FW) {
sendPacket(new Packet(true, Packet.CMD_GET_INFO));
}
if (cmd == Packet.CMD_GET_INFO) {
//store info
sendPacket(new Packet(true, Packet.CMD_INIT)); //TODO params!
}
if (cmd == Packet.CMD_INIT) {
if (respCode == Packet.RESP_OK) {
initDone = true;
sendPacket(new Packet(false, Packet.CMD_HID_STATUS_REPORT));
} else {
mListener.onInitFailure(respCode);
}
}
if (cmd == Packet.CMD_HID_STATUS) {
if (initDone) {
if (param == 0x05) {
mListener.onInitReady();
switch (cmd) {
case Packet.CMD_RUN_FW:
sendPacket(new Packet(true, Packet.CMD_FW_INFO));
break;
case Packet.CMD_FW_INFO:
onFWInfo(data, true, true, new Packet(true, Packet.CMD_INIT)); //TODO next FW: params!
break;
case Packet.CMD_INIT:
if (respCode == Packet.RESP_OK) {
initDone = true;
sendPacket(new Packet(true, Packet.CMD_HID_STATUS_REPORT));
} else {
mListener.onInitNotReady();
mListener.onInitFailure(respCode);
}
}
break;
case Packet.CMD_INIT_AUTH:
onAuth(data, true, new Packet(true, Packet.CMD_INIT)); //TODO next FW: params!
break;
case Packet.CMD_HID_STATUS:
if (initDone) {
if (param == 0x05) {
mListener.onInitReady();
} else {
mListener.onInitNotReady();
}
}
break;
}
}
}

View File

@ -1,33 +1,125 @@
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.PacketManager;
public class InitManager {
public static final int DEFAULT_INIT_TIMEOUT = 60000; //60s init timeout
protected PacketManager mPacketManager;
protected InitManagerListener mListener;
protected byte[] mKey;
protected DeviceInfo mInfo;
protected boolean initDone;
//private Timer t;
public InitManager(byte[] key) {
mKey = key;
}
public DeviceInfo getDeviceInfo() {
return mInfo;
}
public boolean isEncrypted() {
return mPacketManager.isEncrypted();
}
public void init(InitManagerListener listener, PacketManager packetManager) {
mListener = listener;
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() {
mListener.onInitReady();
}
public void onData(byte[] data) {
//byte cmd = data[0];
//byte param = data[1];
}
public void sendPacket(Packet 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
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.io.UnsupportedEncodingException;
import java.security.DigestOutputStream;
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 {
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 {
assert(key != null && keyFileName != null);
assert(key != null && keyfileStream != null);
byte[] fileKey = getFileKey(keyFileName);
byte[] fileKey = getFileKey(keyfileStream);
byte[] passwordKey = getPasswordKey(key);
@ -152,45 +156,39 @@ public class PwDatabaseV3 {
return md.digest(fileKey);
}
protected byte[] getFileKey(String fileName)
protected byte[] getFileKey(InputStream keyfileStream)
throws InvalidKeyFileException, IOException {
assert(fileName != null);
assert(keyfileStream != null);
File keyfile = new File(fileName);
if ( ! keyfile.exists() ) {
throw new InvalidKeyFileException();
byte[] buff = new byte[8000];
int bytesRead = 0;
ByteArrayOutputStream bao = new ByteArrayOutputStream();
while ((bytesRead = keyfileStream.read(buff)) != -1) {
bao.write(buff, 0, bytesRead);
}
byte[] key = loadXmlKeyFile(fileName);
if ( key != null ) {
return key;
}
byte[] keyFileData = bao.toByteArray();
FileInputStream fis;
try {
fis = new FileInputStream(keyfile);
} catch (FileNotFoundException e) {
throw new InvalidKeyFileException();
}
ByteArrayInputStream bin = new ByteArrayInputStream(keyFileData);
BufferedInputStream bis = new BufferedInputStream(fis, 64);
long fileSize = keyfile.length();
if ( fileSize == 0 ) {
throw new KeyFileEmptyException();
} else if ( fileSize == 32 ) {
if ( keyFileData.length == 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.");
}
return outputKey;
} else if ( fileSize == 64 ) {
} else if ( keyFileData.length == 64 ) {
byte[] hex = new byte[64];
bis.mark(64);
if ( bis.read(hex, 0, 64) != 64 ) {
bin.mark(64);
if ( bin.read(hex, 0, 64) != 64 ) {
throw new IOException("Error reading key.");
}
@ -198,7 +196,7 @@ public class PwDatabaseV3 {
return hexStringToByteArray(new String(hex));
} catch (IndexOutOfBoundsException e) {
// Key is not base 64, treat it as binary data
bis.reset();
bin.reset();
}
}
@ -214,7 +212,7 @@ public class PwDatabaseV3 {
try {
while (true) {
int bytesRead = bis.read(buffer, 0, 2048);
bytesRead = bin.read(buffer, 0, 2048);
if ( bytesRead == -1 ) break; // End of file
md.update(buffer, 0, bytesRead);
@ -495,16 +493,16 @@ public class PwDatabaseV3 {
return newId;
}
public byte[] getMasterKey(String key, String keyFileName)
public byte[] getMasterKey(String key, InputStream keyfileStream)
throws InvalidKeyFileException, IOException {
assert (key != null && keyFileName != null);
assert (key != null && keyfileStream != null);
if (key.length() > 0 && keyFileName.length() > 0) {
return getCompositeKey(key, keyFileName);
if (key.length() > 0 && keyfileStream != null) {
return getCompositeKey(key, keyfileStream);
} else if (key.length() > 0) {
return getPasswordKey(key);
} else if (keyFileName.length() > 0) {
return getFileKey(keyFileName);
} else if (keyfileStream != null) {
return getFileKey(keyfileStream);
} else {
throw new IllegalArgumentException("Key cannot be empty.");
}
@ -515,11 +513,6 @@ public class PwDatabaseV3 {
return getPasswordKey(key, "ISO-8859-1");
}
protected byte[] loadXmlKeyFile(String fileName) {
return null;
}
public long getNumRounds() {
return numKeyEncRounds;

View File

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

View File

@ -25,6 +25,10 @@ public class InvalidKeyFileException extends InvalidDBException {
private static final long serialVersionUID = 5540694419562294464L;
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() {
super();
super("Invalid key!");
}
}

View File

@ -25,6 +25,6 @@ public class KeyFileEmptyException extends InvalidKeyFileException {
private static final long serialVersionUID = -1630780661204212325L;
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 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
{
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
{
PwDatabaseV3 newManager;
@ -175,7 +175,7 @@ public class ImporterV3 {
}
newManager = createDB();
newManager.setMasterKey( password, keyfile );
newManager.setMasterKey( password, keyfileStream );
// Select algorithm
if( (hdr.flags & PwDbHeaderV3.FLAG_RIJNDAEL) != 0 ) {
@ -230,7 +230,7 @@ public class ImporterV3 {
} catch (IllegalBlockSizeException e1) {
throw new IOException("Invalid block size");
} catch (BadPaddingException e1) {
throw new InvalidPasswordException();
throw new InvalidPasswordException("Invalid key!");
}
// Copy decrypted data for testing
@ -251,7 +251,7 @@ public class ImporterV3 {
if( ! Arrays.equals(hash, hdr.contentsHash) ) {
Log.w("KeePassDroid","Database file did not decrypt correctly. (checksum code is broken)");
throw new InvalidPasswordException();
throw new InvalidPasswordException("Invalid key!");
}
// Import all groups

View File

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

View File

@ -11,7 +11,10 @@ public final class R {
public static final int ic_launcher = 0x7f020000;
}
public static final class string {
public static final int action_settings = 0x7f060002;
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 int AppBaseTheme = 0x7f070000;

View File

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

View File

@ -8,10 +8,17 @@
<string name="layout_title">Host keyboard layout</string>
<string-array name="layout_names">
<item>English (United States) - en-US</item>
<item>German - de-DE</item>
<item>Polish - pl-PL</item>
<item>Russian - ru-RU</item>
<item>English (US)</item>
<item>German</item>
<item>Polish</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 name="layout_values">
@ -19,6 +26,13 @@
<item>de-DE</item>
<item>pl-PL</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>
</resources>

View File

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

View File

@ -327,9 +327,9 @@ namespace keepass2android
defaultPath =>
{
if (defaultPath.StartsWith("sftp://"))
Util.ShowSftpDialog(this, OnReceiveSftpData);
Util.ShowSftpDialog(this, OnReceiveSftpData, () => { });
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);
}
), 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.
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.Click += (sender, e) =>
{
Util.ShowBrowseDialog("/mnt/sdcard", this, Intents.RequestCodeFileBrowseForBinary, false);
Util.ShowBrowseDialog(this, Intents.RequestCodeFileBrowseForBinary, false);
};
binariesGroup.AddView(addBinaryButton,layoutParams);

View File

@ -61,9 +61,9 @@ namespace keepass2android
defaultPath =>
{
if (defaultPath.StartsWith("sftp://"))
Util.ShowSftpDialog(this, OnReceiveSftpData);
Util.ShowSftpDialog(this, OnReceiveSftpData, () => { });
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);
}
), true, RequestCodeDbFilename, protocolId);

View File

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

View File

@ -24,6 +24,7 @@ using System.Xml.Serialization;
using Android.App;
using Android.Content;
using Android.Database;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Runtime;
using Android.Views;
@ -86,7 +87,7 @@ namespace keepass2android
private const int RequestCodePrepareDbFile = 1000;
private const int RequestCodePrepareOtpAuxFile = 1001;
private const int RequestCodeChallengeYubikey = 1002;
private const int RequestCodeSelectKeyfile = 1003;
private Task<MemoryStream> _loadDbTask;
private IOConnectionInfo _ioConnection;
@ -137,6 +138,7 @@ namespace keepass2android
private ActivityDesign _design;
private bool _performingLoad;
public PasswordActivity (IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
@ -258,26 +260,20 @@ namespace keepass2android
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);
break;
case Result.Ok: // Key file browse dialog OK'ed.
if (requestCode == Intents.RequestCodeFileBrowseForKeyfile) {
string filename = Util.IntentToFilename(data, this);
if (filename != null) {
if (filename.StartsWith("file://")) {
filename = filename.Substring(7);
}
filename = URLDecoder.Decode(filename);
EditText fn = (EditText) FindViewById(Resource.Id.pass_keyfile);
fn.Text = filename;
}
case Result.Ok:
if (requestCode == RequestCodeSelectKeyfile)
{
IOConnectionInfo ioc = new IOConnectionInfo();
SetIoConnectionFromIntent(ioc, data);
_keyFileOrProvider = IOConnectionInfo.SerializeToString(ioc);
UpdateKeyfileIocView();
}
break;
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()
{
new LoadingDialog<object, object, object>(this, true,
@ -543,14 +572,11 @@ namespace keepass2android
InitializeFilenameView();
if (KeyProviderType == KeyProviders.KeyFile)
SetEditText(Resource.Id.pass_keyfile, _keyFileOrProvider);
{
UpdateKeyfileIocView();
}
FindViewById<EditText>(Resource.Id.pass_keyfile).TextChanged +=
(sender, args) =>
{
_keyFileOrProvider = FindViewById<EditText>(Resource.Id.pass_keyfile).Text;
UpdateOkButtonState();
};
FindViewById<EditText>(Resource.Id.password).TextChanged +=
(sender, args) =>
@ -705,20 +731,14 @@ namespace keepass2android
private void InitializeKeyfileBrowseButton()
{
ImageButton browse = (ImageButton) FindViewById(Resource.Id.browse_button);
browse.Click += (sender, evt) =>
var browseButton = (Button)FindViewById(Resource.Id.btn_change_location);
browseButton.Click += (sender, evt) =>
{
string filename = null;
if (!String.IsNullOrEmpty(_ioConnection.Path))
{
File keyfile = new File(_ioConnection.Path);
File parent = keyfile.ParentFile;
if (parent != null)
{
filename = parent.AbsolutePath;
}
}
Util.ShowBrowseDialog(filename, this, Intents.RequestCodeFileBrowseForKeyfile, false);
Intent intent = new Intent(this, typeof(SelectStorageLocationActivity));
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, true);
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, false);
intent.PutExtra(FileStorageSetupDefs.ExtraIsForSave, false);
StartActivityForResult(intent, RequestCodeSelectKeyfile);
};
}
@ -738,7 +758,7 @@ namespace keepass2android
break;
case 1:
//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;
case 2:
_keyFileOrProvider = KeyProviderIdOtp;
@ -779,7 +799,7 @@ namespace keepass2android
_showPassword = savedInstanceState.GetBoolean(ShowpasswordKey, false);
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);
_pendingOtps = new List<string>(savedInstanceState.GetStringArrayList(PendingOtpsKey));
@ -850,6 +870,11 @@ namespace keepass2android
FindViewById(Resource.Id.keyfileLine).Visibility = KeyProviderType == KeyProviders.KeyFile
? ViewStates.Visible
: ViewStates.Gone;
if (KeyProviderType == KeyProviders.KeyFile)
{
UpdateKeyfileIocView();
}
FindViewById(Resource.Id.otpView).Visibility = KeyProviderType == KeyProviders.Otp
? ViewStates.Visible
: 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)
CompositeKey compositeKey = new CompositeKey();
compositeKey.AddUserKey(new KcpPassword(_password));
if ((KeyProviderType == KeyProviders.KeyFile) && (_keyFileOrProvider != ""))
if (KeyProviderType == KeyProviders.KeyFile)
{
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)
{
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;
}
}
@ -895,11 +934,12 @@ namespace keepass2android
try
{
var lOtps = GetOtpsFromUi();
Kp2aLog.Log("received " + lOtps.Count + " otps.");
OathHotpKeyProv.CreateOtpSecret(lOtps, _otpInfo);
}
catch (Exception)
catch (Exception e)
{
Kp2aLog.Log(e.ToString());
Toast.MakeText(this, GetString(Resource.String.OtpKeyError), ToastLength.Long).Show();
return;
@ -1031,23 +1071,29 @@ namespace keepass2android
var fileStorage = App.Kp2a.GetFileStorage(_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;
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.
if (stream.CanSeek)
{
capacity = (int)stream.Length;
capacity = (int) stream.Length;
}
memoryStream = new MemoryStream(capacity);
stream.CopyTo(memoryStream);
stream.Close();
memoryStream.Seek(0, System.IO.SeekOrigin.Begin);
memoryStream.Seek(0, SeekOrigin.Begin);
}
Kp2aLog.Log("Pre-loading database file completed");
return memoryStream;
}

View File

@ -18,7 +18,7 @@
</intent-filter>
</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.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">
@ -29,7 +29,7 @@
<category android:name="android.intent.category.OPENABLE" />
</intent-filter>
</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>
<action android:name="android.view.InputMethod" />
</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:src="@drawable/ic_menu_view" />
</LinearLayout>
<LinearLayout
android:id="@+id/keyfileLine"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:orientation="horizontal">
<EditText
android:id="@+id/pass_keyfile"
android:layout_width="0px"
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:orientation="vertical">
<TextView
android:id="@+id/keyfile_heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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_launcher_folder_small" />
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
android:id="@+id/otpView"

View File

@ -32,6 +32,8 @@
<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="display_prefs">Display</string>
<string name="password_access_prefs">Password entry access</string>
@ -68,6 +70,7 @@
<string name="entry_expires">Expires</string>
<string name="entry_group_name">Group Name</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_password">Password</string>
<string name="entry_save">Save</string>
@ -114,6 +117,7 @@
<string name="invalid_algorithm">Invalid algorithm.</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="no_keyfile_selected">No key file selected.</string>
<string name="keyfile_is_empty">Key file is empty.</string>
<string name="length">Length</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="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="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 + 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.
}
}

View File

@ -14,8 +14,8 @@ namespace keepass2android.Utils
private readonly Context _context;
private readonly string _message;
private readonly bool _cancelable;
readonly Func<Object[], Object> _doInBackground;
readonly Action<Object> _onPostExecute;
private readonly Func<Object[], Object> _doInBackground;
private readonly Action<Object> _onPostExecute;
private ProgressDialog mDialog;
/**
@ -30,12 +30,13 @@ namespace keepass2android.Utils
private Exception mLastException;
public LoadingDialog(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
public LoadingDialog(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
public LoadingDialog(Context context, string message, bool cancelable, Func<Object[], Object> doInBackground,
Action<Object> onPostExecute)
Action<Object> onPostExecute)
{
_context = context;
_message = message;
@ -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);
_context = context;
@ -89,7 +91,7 @@ namespace keepass2android.Utils
}
}
, mDelayTime);
, mDelayTime);
}
@ -97,24 +99,33 @@ namespace keepass2android.Utils
* If you override this method, you must call {@code super.onCancelled()} at
* beginning of the method.
*/
protected override void OnCancelled() {
protected override void OnCancelled()
{
DoFinish();
base.OnCancelled();
}// onCancelled()
}
private void DoFinish() {
// onCancelled()
private void DoFinish()
{
mFinished = true;
try {
try
{
/*
* Sometime the activity has been finished before we dismiss this
* dialog, it will raise error.
*/
mDialog.Dismiss();
} catch (Exception e)
}
catch (Exception e)
{
Kp2aLog.Log(e.ToString());
}
}// doFinish()
}
// doFinish()
/**
@ -124,18 +135,26 @@ namespace keepass2android.Utils
* @param t
* {@link Throwable}
*/
protected void SetLastException(Exception e) {
protected void SetLastException(Exception e)
{
mLastException = e;
}// setLastException()
}
// setLastException()
/**
* Gets last exception.
*
* @return {@link Throwable}
*/
protected Exception GetLastException() {
protected Exception GetLastException()
{
return mLastException;
}// getLastException()
}
// getLastException()
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;
}
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})))
{
@ -223,7 +223,7 @@ namespace keepass2android
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
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
@ -244,7 +244,9 @@ namespace keepass2android
password);
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));
Dialog dialog = builder.Create();
@ -252,16 +254,50 @@ namespace keepass2android
#endif
}
public static void ShowFilenameDialog(Activity activity, FileSelectedHandler onOpen, FileSelectedHandler onCreate, bool showBrowseButton,
string defaultFilename, string detailsText, int requestCodeBrowse)
class DismissListener: Java.Lang.Object, IDialogInterfaceOnDismissListener
{
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);
builder.SetView(activity.LayoutInflater.Inflate(Resource.Layout.file_selection_filename, null));
if (onCancel != null)
builder.SetOnCancelListener(new CancelListener(onCancel));
Dialog dialog = builder.Create();
dialog.Show();
Button openButton = (Button) dialog.FindViewById(Resource.Id.open);
Button createButton = (Button) dialog.FindViewById(Resource.Id.create);
TextView enterFilenameDetails = (TextView) dialog.FindViewById(Resource.Id.label_open_by_filename_details);
openButton.Visibility = onOpen != 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);
cancelButton.Click += (sender, e) => dialog.Dismiss();
cancelButton.Click += delegate
{
dialog.Dismiss();
if (onCancel != null)
onCancel();
};
ImageButton browseButton = (ImageButton) dialog.FindViewById(Resource.Id.browse_button);
if (!showBrowseButton)
@ -301,7 +347,7 @@ namespace keepass2android
{
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)
{
return GetFileStorage(iocInfo, true);
}
public IFileStorage GetFileStorage(IOConnectionInfo iocInfo, bool allowCache)
{
if (iocInfo.IsLocalFile())
return new BuiltInFileStorage(this);
@ -399,9 +403,8 @@ namespace keepass2android
{
IFileStorage innerFileStorage = GetCloudFileStorage(iocInfo);
if (DatabaseCacheEnabled)
if (DatabaseCacheEnabled && allowCache)
{
//TODO
return new CachingFileStorage(innerFileStorage, Application.Context.CacheDir.Path, this);
}
else
@ -443,7 +446,8 @@ namespace keepass2android
new SftpFileStorage(this),
#endif
#endif
new BuiltInFileStorage(this)
new BuiltInFileStorage(this),
new AndroidContentStorage(Application.Context)
};
}
return _fileStorages;

View File

@ -69,6 +69,7 @@ namespace keepass2android
view.FileSelectButtons _fileSelectButtons;
internal AppTask AppTask;
private const int RequestCodeSelectIoc = 456;
public const string NoForwardToPasswordActivity = "NoForwardToPasswordActivity";
@ -129,9 +130,12 @@ namespace keepass2android
EventHandler openFileButtonClick = (sender, e) =>
{
Intent intent = new Intent(this, typeof(FileStorageSelectionActivity));
Intent intent = new Intent(this, typeof(SelectStorageLocationActivity));
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;
@ -294,19 +298,7 @@ namespace keepass2android
App.Kp2a.GetFileStorage(ioc)
.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)
{
@ -326,109 +318,23 @@ namespace keepass2android
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)
{
IOConnectionInfo ioc = new IOConnectionInfo();
PasswordActivity.SetIoConnectionFromIntent(ioc, data);
LaunchPasswordActivityForIoc(ioc);
}
if (resultCode == (Result)FileStorageResults.FileChooserPrepared)
if ((resultCode == Result.Ok) && (requestCode == RequestCodeSelectIoc))
{
IOConnectionInfo ioc = new IOConnectionInfo();
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();
LaunchPasswordActivityForIoc(ioc);
}
}
private bool OnReceivedSftpData(string filename)
{
IOConnectionInfo ioc = new IOConnectionInfo { Path = filename };
#if !EXCLUDE_FILECHOOSER
StartFileChooser(ioc.Path);
#else
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()
{
base.OnResume();

View File

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

View File

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