diff --git a/.gitignore b/.gitignore
index ec4e4d19..15afed1a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -282,3 +282,5 @@ Thumbs.db
/src/java/MasterKee
/src/PluginSdkBinding/obj/ReleaseNoNet
+/src/MasterKeeWinPlugin/bin/Release
+/src/SamplePlugin
diff --git a/src/AndroidFileChooserBinding/AndroidFileChooserBinding.csproj b/src/AndroidFileChooserBinding/AndroidFileChooserBinding.csproj
index b30112ca..7b39d548 100644
--- a/src/AndroidFileChooserBinding/AndroidFileChooserBinding.csproj
+++ b/src/AndroidFileChooserBinding/AndroidFileChooserBinding.csproj
@@ -3,8 +3,6 @@
Debug
AnyCPU
- 8.0.30703
- 2.0
{3C0F7FE5-639F-4422-A087-8B26CF862D1B}
{10368E6C-D01B-4462-8E8B-01FC667A7035};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
Library
@@ -22,6 +20,7 @@
DEBUG;TRACE
prompt
4
+ None
pdbonly
@@ -30,6 +29,7 @@
TRACE
prompt
4
+ false
bin\ReleaseNoNet\
@@ -39,30 +39,30 @@
AnyCPU
prompt
MinimumRecommendedRules.ruleset
+ false
+ 4
+
+ ..\packages\Xamarin.Android.Support.v4.20.0.0.4\lib\MonoAndroid10\Xamarin.Android.Support.v4.dll
+
-
- project.zip
-
+
-
-
-
+
+
+ project.zip
+
+
+
+
+ 20.0.0.4
+ False
+
+
\ No newline at end of file
diff --git a/src/AndroidFileChooserBinding/Jars/android-support-v4.jar b/src/AndroidFileChooserBinding/Jars/android-support-v4.jar
index 428bdbc0..187bdf48 100644
Binary files a/src/AndroidFileChooserBinding/Jars/android-support-v4.jar and b/src/AndroidFileChooserBinding/Jars/android-support-v4.jar differ
diff --git a/src/AndroidFileChooserBinding/Transforms/Metadata.xml b/src/AndroidFileChooserBinding/Transforms/Metadata.xml
index c9cc997f..ea974e92 100644
--- a/src/AndroidFileChooserBinding/Transforms/Metadata.xml
+++ b/src/AndroidFileChooserBinding/Transforms/Metadata.xml
@@ -1,5 +1,8 @@
+
+
+
+
+
+ 19.0.0
+ False
+
+
\ No newline at end of file
diff --git a/src/KP2AKdbLibraryBinding/KP2AKdbLibraryBinding.csproj b/src/KP2AKdbLibraryBinding/KP2AKdbLibraryBinding.csproj
index 89a315db..2bb393b7 100644
--- a/src/KP2AKdbLibraryBinding/KP2AKdbLibraryBinding.csproj
+++ b/src/KP2AKdbLibraryBinding/KP2AKdbLibraryBinding.csproj
@@ -3,8 +3,6 @@
Debug
AnyCPU
- 8.0.30703
- 2.0
{70D3844A-D9FA-4A64-B205-A84C6A822196}
{10368E6C-D01B-4462-8E8B-01FC667A7035};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
Library
@@ -22,6 +20,7 @@
DEBUG;TRACE
prompt
4
+ None
pdbonly
@@ -30,6 +29,7 @@
TRACE
prompt
4
+ false
bin\ReleaseNoNet\
@@ -39,6 +39,8 @@
AnyCPU
prompt
MinimumRecommendedRules.ruleset
+ false
+ 4
diff --git a/src/KeePassLib2Android/KeePassLib2Android.csproj b/src/KeePassLib2Android/KeePassLib2Android.csproj
index d6516bda..29752ec8 100644
--- a/src/KeePassLib2Android/KeePassLib2Android.csproj
+++ b/src/KeePassLib2Android/KeePassLib2Android.csproj
@@ -3,8 +3,6 @@
Debug
AnyCPU
- 10.0.0
- 2.0
{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}
{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
Library
@@ -20,7 +18,7 @@
full
False
bin\Debug
- DEBUG;EXCLUDE_TWOFISH;INCLUDE_KEYBOARD;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;EXCLUDE_KEYTRANSFORM
+ DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;INCLUDE_FILECHOOSER;_EXCLUDE_JAVAFILESTORAGE;INCLUDE_KEYTRANSFORM
prompt
4
False
@@ -34,7 +32,6 @@
4
False
False
- SdkOnly
none
@@ -44,7 +41,6 @@
4
False
False
- SdkOnly
@@ -156,7 +152,7 @@
- {70d3844a-d9fa-4a64-b205-a84c6a822196}
+ {70D3844A-D9FA-4A64-B205-A84C6A822196}
KP2AKdbLibraryBinding
diff --git a/src/KeePassLib2Android/Keys/KcpKeyFile.cs b/src/KeePassLib2Android/Keys/KcpKeyFile.cs
index edbc949d..8da75c49 100644
--- a/src/KeePassLib2Android/Keys/KcpKeyFile.cs
+++ b/src/KeePassLib2Android/Keys/KcpKeyFile.cs
@@ -41,15 +41,16 @@ namespace KeePassLib.Keys
///
public sealed class KcpKeyFile : IUserKey
{
- private string m_strPath;
+ private IOConnectionInfo m_ioc;
private ProtectedBinary m_pbKeyData;
+ private ProtectedBinary m_pbFileData;
///
/// Path to the key file.
///
public string Path
{
- get { return m_strPath; }
+ get { return m_ioc.Path; }
}
///
@@ -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;
diff --git a/src/KeePassLib2Android/Serialization/IOConnection.cs b/src/KeePassLib2Android/Serialization/IOConnection.cs
index e02452be..aff3ad2d 100644
--- a/src/KeePassLib2Android/Serialization/IOConnection.cs
+++ b/src/KeePassLib2Android/Serialization/IOConnection.cs
@@ -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();
diff --git a/src/Kp2aBusinessLogic/IKp2aApp.cs b/src/Kp2aBusinessLogic/IKp2aApp.cs
index ec726578..4c930487 100644
--- a/src/Kp2aBusinessLogic/IKp2aApp.cs
+++ b/src/Kp2aBusinessLogic/IKp2aApp.cs
@@ -79,8 +79,17 @@ namespace keepass2android
Handler UiThreadHandler { get; }
IProgressDialog CreateProgressDialog(Context ctx);
+
+ ///
+ /// returns the file storage for the given ioc. might be a caching file storage
+ ///
IFileStorage GetFileStorage(IOConnectionInfo iocInfo);
+ ///
+ /// returns the file storage for the given ioc. if allowCache=false, no cached file storage is returned
+ ///
+ IFileStorage GetFileStorage(IOConnectionInfo iocInfo, bool allowCache);
+
void TriggerReload(Context context);
///
@@ -90,5 +99,7 @@ namespace keepass2android
//bool OnServerCertificateError(int certificateProblem);
RemoteCertificateValidationCallback CertificateValidationCallback { get; }
+
+
}
}
\ No newline at end of file
diff --git a/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs b/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs
new file mode 100644
index 00000000..b406542b
--- /dev/null
+++ b/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs
@@ -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 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 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);
+ }
+
+
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs b/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs
index f856f4cc..edb3477a 100644
--- a/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs
+++ b/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs
@@ -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;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs b/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs
index a1c48b8a..d20bbd56 100644
--- a/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs
+++ b/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs
@@ -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);
diff --git a/src/Kp2aBusinessLogic/Io/IFileStorage.cs b/src/Kp2aBusinessLogic/Io/IFileStorage.cs
index 1ae092f9..a290acb2 100644
--- a/src/Kp2aBusinessLogic/Io/IFileStorage.cs
+++ b/src/Kp2aBusinessLogic/Io/IFileStorage.cs
@@ -157,6 +157,17 @@ namespace keepass2android.Io
///
/// The method may throw FileNotFoundException or not in case the file doesn't exist.
IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename);
+
+ ///
+ /// 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)
+ ///
+ /// Does not require to exist forever!
+ bool IsPermanentLocation(IOConnectionInfo ioc);
+
+ ///
+ /// Should return true if the file cannot be written.
+ ///
+ bool IsReadOnly(IOConnectionInfo ioc);
}
public interface IWriteTransaction: IDisposable
diff --git a/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs b/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs
index b162b6df..63e0855d 100644
--- a/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs
+++ b/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs
@@ -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);
diff --git a/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj b/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj
index 198d4208..0318a87d 100644
--- a/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj
+++ b/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj
@@ -3,8 +3,6 @@
Debug
AnyCPU
- 8.0.30703
- 2.0
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}
{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
Library
@@ -20,9 +18,10 @@
full
false
bin\Debug\
- TRACE;DEBUG;EXCLUDE_TWOFISH;INCLUDE_KEYBOARD;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;EXCLUDE_KEYTRANSFORM
+ DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;INCLUDE_FILECHOOSER;_EXCLUDE_JAVAFILESTORAGE;INCLUDE_KEYTRANSFORM
prompt
4
+ None
pdbonly
@@ -31,6 +30,7 @@
TRACE
prompt
4
+ false
bin\ReleaseNoNet\
@@ -38,9 +38,10 @@
true
pdbonly
AnyCPU
- Off
prompt
MinimumRecommendedRules.ruleset
+ false
+ 4
@@ -64,6 +65,7 @@
+
@@ -79,6 +81,7 @@
+
@@ -113,19 +116,19 @@
- {48574278-4779-4b3a-a9e4-9cf1bc285d0b}
+ {48574278-4779-4B3A-A9E4-9CF1BC285D0B}
JavaFileStorageBindings
- {545b4a6b-8bba-4fbe-92fc-4ac060122a54}
+ {545B4A6B-8BBA-4FBE-92FC-4AC060122A54}
KeePassLib2Android
- {70d3844a-d9fa-4a64-b205-a84c6a822196}
+ {70D3844A-D9FA-4A64-B205-A84C6A822196}
KP2AKdbLibraryBinding
- {5cf675a5-9bee-4720-bed9-d5bf14a2ebf9}
+ {5CF675A5-9BEE-4720-BED9-D5BF14A2EBF9}
TwofishCipher
diff --git a/src/Kp2aBusinessLogic/SelectStorageLocationActivityBase.cs b/src/Kp2aBusinessLogic/SelectStorageLocationActivityBase.cs
new file mode 100644
index 00000000..7e242332
--- /dev/null
+++ b/src/Kp2aBusinessLogic/SelectStorageLocationActivityBase.cs
@@ -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
+{
+ ///
+ /// base class for SelectStorageLocationActivity containing testable (non-UI) code
+ ///
+ 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; }
+
+ ///
+ /// Starts the appropriate file selection process (either manual file select or prepare filechooser + filechooser)
+ ///
+ ///
+ ///
+ ///
+ 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 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 onOk = (sender, args) => { MoveToWritableLocation(ioc); };
+ EventHandler 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 onOk = (sender, args) => { MoveToWritableLocation(ioc); };
+ EventHandler 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 onOk, EventHandler onCancel);
+
+ protected abstract WritableRequirements RequestedWritableRequirements { get; }
+
+ protected abstract void ReturnOk(IOConnectionInfo ioc);
+
+ protected abstract void ReturnCancel();
+ }
+}
\ No newline at end of file
diff --git a/src/Kp2aBusinessLogic/UiStringKey.cs b/src/Kp2aBusinessLogic/UiStringKey.cs
index dbd6289e..017801d6 100644
--- a/src/Kp2aBusinessLogic/UiStringKey.cs
+++ b/src/Kp2aBusinessLogic/UiStringKey.cs
@@ -48,6 +48,12 @@ namespace keepass2android
SynchronizingOtpAuxFile,
SavingOtpAuxFile,
CertificateFailure,
- exporting_database
+ exporting_database,
+ FileIsTemporarilyAvailable,
+ CopyFileRequired,
+ ClickOkToSelectLocation,
+ FileIsReadOnly,
+ FileIsReadOnlyOnKitkat,
+ CopyFileRequiredForEditing
}
-}
\ No newline at end of file
+}
diff --git a/src/Kp2aBusinessLogic/database/Database.cs b/src/Kp2aBusinessLogic/database/Database.cs
index d91d98bc..78960aeb 100644
--- a/src/Kp2aBusinessLogic/database/Database.cs
+++ b/src/Kp2aBusinessLogic/database/Database.cs
@@ -119,7 +119,7 @@ namespace keepass2android
KpDatabase = pwDatabase;
SearchHelper = new SearchDbHelper(app);
- CanWrite = databaseLoader.CanWrite;
+ CanWrite = databaseLoader.CanWrite && !fileStorage.IsReadOnly(iocInfo);
}
///
diff --git a/src/Kp2aBusinessLogic/database/KdbDatabaseLoader.cs b/src/Kp2aBusinessLogic/database/KdbDatabaseLoader.cs
index 7f5c68a0..15831a9a 100644
--- a/src/Kp2aBusinessLogic/database/KdbDatabaseLoader.cs
+++ b/src/Kp2aBusinessLogic/database/KdbDatabaseLoader.cs
@@ -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(
diff --git a/src/Kp2aKeyboardBinding/Kp2aKeyboardBinding.csproj b/src/Kp2aKeyboardBinding/Kp2aKeyboardBinding.csproj
index f3a8803c..c5585dbd 100644
--- a/src/Kp2aKeyboardBinding/Kp2aKeyboardBinding.csproj
+++ b/src/Kp2aKeyboardBinding/Kp2aKeyboardBinding.csproj
@@ -3,8 +3,6 @@
Debug
AnyCPU
- 10.0.0
- 2.0
{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}
{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{10368E6C-D01B-4462-8E8B-01FC667A7035};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
Library
@@ -32,7 +30,6 @@
4
False
False
- SdkOnly
bin\ReleaseNoNet\
@@ -40,6 +37,8 @@
AnyCPU
prompt
MinimumRecommendedRules.ruleset
+ false
+ 4
diff --git a/src/Kp2aUnitTests/Kp2aUnitTests.csproj b/src/Kp2aUnitTests/Kp2aUnitTests.csproj
index 90ff6b5d..a1b94ac4 100644
--- a/src/Kp2aUnitTests/Kp2aUnitTests.csproj
+++ b/src/Kp2aUnitTests/Kp2aUnitTests.csproj
@@ -3,8 +3,6 @@
Debug
AnyCPU
- 8.0.30703
- 2.0
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}
{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
Library
@@ -16,7 +14,6 @@
Resources\Resource.Designer.cs
Off
Properties\AndroidManifest.xml
-
v4.2
armeabi,armeabi-v7a,x86
@@ -32,7 +29,6 @@
DEBUG;TRACE
prompt
4
- True
None
@@ -43,10 +39,8 @@
prompt
4
False
- SdkOnly
System.Core%3b
True
- False
bin\ReleaseNoNet\
@@ -54,9 +48,10 @@
true
pdbonly
AnyCPU
- Off
prompt
MinimumRecommendedRules.ruleset
+ false
+ 4
@@ -67,6 +62,7 @@
+
@@ -83,10 +79,10 @@
+
-
@@ -109,7 +105,7 @@
Kp2aBusinessLogic
- {70d3844a-d9fa-4a64-b205-a84c6a822196}
+ {70D3844A-D9FA-4A64-B205-A84C6A822196}
KP2AKdbLibraryBinding
diff --git a/src/Kp2aUnitTests/MainActivity.cs b/src/Kp2aUnitTests/MainActivity.cs
index 71c1c621..c2ca9fed 100644
--- a/src/Kp2aUnitTests/MainActivity.cs
+++ b/src/Kp2aUnitTests/MainActivity.cs
@@ -19,7 +19,11 @@ namespace Kp2aUnitTests
TestRunner runner = new TestRunner();
// Run all tests from this assembly
//runner.AddTests(Assembly.GetExecutingAssembly());
- runner.AddTests(new List { typeof(TestIntentsAndBundles) });
+ //runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdb1WithKeyfileByDirectCall"));
+ //runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdb1WithKeyfileOnly"));
+
+
+ runner.AddTests(new List { typeof(TestSelectStorageLocation) });
//runner.AddTests(new List { typeof(TestSynchronizeCachedDatabase)});
//runner.AddTests(typeof(TestLoadDb).GetMethod("LoadErrorWithCertificateTrustFailure"));
//runner.AddTests(typeof(TestLoadDb).GetMethod("LoadWithAcceptedCertificateTrustFailure"));
diff --git a/src/Kp2aUnitTests/TestAndroidContentFileStorage.cs b/src/Kp2aUnitTests/TestAndroidContentFileStorage.cs
new file mode 100644
index 00000000..7e53482e
--- /dev/null
+++ b/src/Kp2aUnitTests/TestAndroidContentFileStorage.cs
@@ -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")));
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Kp2aUnitTests/TestFileStorage.cs b/src/Kp2aUnitTests/TestFileStorage.cs
index ef67480b..4629e684 100644
--- a/src/Kp2aUnitTests/TestFileStorage.cs
+++ b/src/Kp2aUnitTests/TestFileStorage.cs
@@ -183,5 +183,15 @@ namespace Kp2aUnitTests
{
throw new NotImplementedException();
}
+
+ public bool IsPermanentLocation(IOConnectionInfo ioc)
+ {
+ return true;
+ }
+
+ public bool IsReadOnly(IOConnectionInfo ioc)
+ {
+ return false;
+ }
}
}
\ No newline at end of file
diff --git a/src/Kp2aUnitTests/TestKp2aApp.cs b/src/Kp2aUnitTests/TestKp2aApp.cs
index 156e0464..fe20db4b 100644
--- a/src/Kp2aUnitTests/TestKp2aApp.cs
+++ b/src/Kp2aUnitTests/TestKp2aApp.cs
@@ -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;
diff --git a/src/Kp2aUnitTests/TestLoadDb.cs b/src/Kp2aUnitTests/TestLoadDb.cs
index 24d800bc..ecf1553b 100644
--- a/src/Kp2aUnitTests/TestLoadDb.cs
+++ b/src/Kp2aUnitTests/TestLoadDb.cs
@@ -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()
{
diff --git a/src/Kp2aUnitTests/TestSelectStorageLocation.cs b/src/Kp2aUnitTests/TestSelectStorageLocation.cs
new file mode 100644
index 00000000..2a980f77
--- /dev/null
+++ b/src/Kp2aUnitTests/TestSelectStorageLocation.cs
@@ -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 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 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 toasts = new List();
+ 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 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 onOk, EventHandler onCancel)
+ {
+ _userAction = new ShowAlertDialogAction(message, onOk, onCancel);
+ }
+
+ internal class ShowAlertDialogAction
+ {
+ public string Message { get; set; }
+ public EventHandler OnOk { get; set; }
+ public EventHandler OnCancel { get; set; }
+
+ public ShowAlertDialogAction(string message, EventHandler onOk, EventHandler 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());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/MPTest/MPTest.csproj b/src/MPTest/MPTest.csproj
index a81e31e8..14915121 100644
--- a/src/MPTest/MPTest.csproj
+++ b/src/MPTest/MPTest.csproj
@@ -35,10 +35,6 @@
false
-
- False
- ..\..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll
-
@@ -46,6 +42,9 @@
+
+ ..\..\..\..\Program Files %28x86%29\Microsoft Visual Studio 11.0\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll
+
@@ -57,7 +56,7 @@
- {2f7cb5b4-ac2a-4790-b0f3-42e6c9a060d5}
+ {2F7CB5B4-AC2A-4790-B0F3-42E6C9A060D5}
MasterPassword
diff --git a/src/MasterKeePlugin/MasterKeePlugin.csproj b/src/MasterKeePlugin/MasterKeePlugin.csproj
index e34d7dbc..032ef79b 100644
--- a/src/MasterKeePlugin/MasterKeePlugin.csproj
+++ b/src/MasterKeePlugin/MasterKeePlugin.csproj
@@ -3,8 +3,6 @@
Debug
AnyCPU
- 8.0.30703
- 2.0
{9A4C5BAA-1A8A-49B4-BBC3-60D4871FB36C}
{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
Library
@@ -26,7 +24,6 @@
DEBUG;TRACE
prompt
4
- True
None
@@ -37,7 +34,6 @@
prompt
4
False
- SdkOnly
@@ -86,11 +82,11 @@
- {2f7cb5b4-ac2a-4790-b0f3-42e6c9a060d5}
+ {2F7CB5B4-AC2A-4790-B0F3-42E6C9A060D5}
MasterPassword
- {3da3911e-36de-465e-8f15-f1991b6437e5}
+ {3DA3911E-36DE-465E-8F15-F1991B6437E5}
PluginSdkBinding
diff --git a/src/PluginHostTest/PluginHostTest.csproj b/src/PluginHostTest/PluginHostTest.csproj
index 5cf983a0..e8043a70 100644
--- a/src/PluginHostTest/PluginHostTest.csproj
+++ b/src/PluginHostTest/PluginHostTest.csproj
@@ -3,8 +3,6 @@
Debug
AnyCPU
- 8.0.30703
- 2.0
{C9F4AE81-0996-4E17-B3F2-D0F652F6AC50}
{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
Library
@@ -16,7 +14,6 @@
Resources\Resource.Designer.cs
Off
Properties\AndroidManifest.xml
-
v4.2
armeabi,armeabi-v7a,x86
@@ -32,7 +29,6 @@
DEBUG;TRACE
prompt
4
- True
None
@@ -43,7 +39,6 @@
prompt
4
False
- SdkOnly
@@ -122,7 +117,7 @@
KeePassLib2Android
- {3da3911e-36de-465e-8f15-f1991b6437e5}
+ {3DA3911E-36DE-465E-8F15-F1991B6437E5}
PluginSdkBinding
diff --git a/src/PluginSdkBinding/PluginSdkBinding.csproj b/src/PluginSdkBinding/PluginSdkBinding.csproj
index c3c5c767..30b75a9c 100644
--- a/src/PluginSdkBinding/PluginSdkBinding.csproj
+++ b/src/PluginSdkBinding/PluginSdkBinding.csproj
@@ -3,8 +3,6 @@
Debug
AnyCPU
- 8.0.30703
- 2.0
{3DA3911E-36DE-465E-8F15-F1991B6437E5}
{10368E6C-D01B-4462-8E8B-01FC667A7035};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
Library
@@ -22,6 +20,7 @@
DEBUG;TRACE
prompt
4
+ None
pdbonly
@@ -30,6 +29,7 @@
TRACE
prompt
4
+ false
bin\ReleaseNoNet\
@@ -39,6 +39,8 @@
AnyCPU
prompt
MinimumRecommendedRules.ruleset
+ false
+ 4
diff --git a/src/TwofishCipher/TwofishCipher.csproj b/src/TwofishCipher/TwofishCipher.csproj
index da265914..a031a7b0 100644
--- a/src/TwofishCipher/TwofishCipher.csproj
+++ b/src/TwofishCipher/TwofishCipher.csproj
@@ -3,8 +3,6 @@
Debug
AnyCPU
- 8.0.30703
- 2.0
{5CF675A5-9BEE-4720-BED9-D5BF14A2EBF9}
{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
Library
@@ -23,6 +21,7 @@
DEBUG;TRACE
prompt
4
+ None
pdbonly
@@ -31,6 +30,7 @@
TRACE
prompt
4
+ false
bin\ReleaseNoNet\
@@ -38,9 +38,10 @@
true
pdbonly
AnyCPU
- Off
prompt
MinimumRecommendedRules.ruleset
+ false
+ 4
@@ -66,7 +67,7 @@
- {545b4a6b-8bba-4fbe-92fc-4ac060122a54}
+ {545B4A6B-8BBA-4FBE-92FC-4AC060122A54}
KeePassLib2Android
diff --git a/src/java/InputStickAPI/AndroidManifest.xml b/src/java/InputStickAPI/AndroidManifest.xml
index 0ee5fbb4..4133ae18 100644
--- a/src/java/InputStickAPI/AndroidManifest.xml
+++ b/src/java/InputStickAPI/AndroidManifest.xml
@@ -1,3 +1,4 @@
+
InputStickAPI
+ Download InputStickUtility
+ Settings
+ Hello world!
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/AES.java b/src/java/InputStickAPI/src/com/inputstick/api/AES.java
index 054dcb6b..e07cadb2 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/AES.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/AES.java
@@ -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));
+ 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;
+ }
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/BTConnectionManager.java b/src/java/InputStickAPI/src/com/inputstick/api/BTConnectionManager.java
index 4d853bec..c3adf551 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/BTConnectionManager.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/BTConnectionManager.java
@@ -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);
@@ -45,31 +44,12 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
break;
case BTService.EVENT_CANCELLED:
manager.onDisconnected();
- break;
- case BTService.EVENT_CONNECTION_FAILED:
- manager.onFailure(1);
+ break;
+ case BTService.EVENT_ERROR:
+ manager.onFailure(msg.arg1);
break;
- case BTService.EVENT_CONNECTION_LOST:
- manager.onFailure(1);
- break;
- case BTService.EVENT_NO_BT_HW:
- manager.onFailure(1);
- break;
- case BTService.EVENT_INVALID_MAC:
- manager.onFailure(1);
- break;
- case BTService.EVENT_CMD_TIMEOUT:
- manager.onFailure(1);
- break;
- case BTService.EVENT_INTERVAL_TIMEOUT:
- manager.onFailure(1);
- break;
- case BTService.EVENT_TURN_ON_TIMEOUT:
- manager.onFailure(1);
- break;
- case BTService.EVENT_OTHER_ERROR:
- manager.onFailure(1);
- break;
+ 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();
}
@@ -89,7 +70,8 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
private void onFailure(int code) {
mErrorCode = code;
- stateNotify(ConnectionManager.STATE_FAILURE);
+ stateNotify(ConnectionManager.STATE_FAILURE);
+ disconnect();
}
private void onData(byte[] rawData) {
@@ -97,16 +79,15 @@ 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);
}
@@ -162,7 +147,7 @@ public class BTConnectionManager extends ConnectionManager implements InitManage
@Override
public void onInitFailure(int code) {
- onFailure(code);
+ onFailure(code);
}
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/ConnectionManager.java b/src/java/InputStickAPI/src/com/inputstick/api/ConnectionManager.java
index d62cdcc4..a47aca63 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/ConnectionManager.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/ConnectionManager.java
@@ -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 mStateListeners = new Vector();
protected Vector mDataListeners = new Vector();
@@ -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);
+ }
}
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/HIDInfo.java b/src/java/InputStickAPI/src/com/inputstick/api/HIDInfo.java
index 6b8f6c4b..b1c2c1d5 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/HIDInfo.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/HIDInfo.java
@@ -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;
+ }
+
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/IPCConnectionManager.java b/src/java/InputStickAPI/src/com/inputstick/api/IPCConnectionManager.java
index 5548f6f5..cac0d4d9 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/IPCConnectionManager.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/IPCConnectionManager.java
@@ -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);
+ 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);
@@ -172,9 +169,8 @@ public class IPCConnectionManager extends ConnectionManager {
} else {
sendMessage(IPCConnectionManager.SERVICE_CMD_DATA, 0, 0, p.getBytes());
}
- }
+ }
}
-
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/Packet.java b/src/java/InputStickAPI/src/com/inputstick/api/Packet.java
index 27e0ac93..ddb24ef5 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/Packet.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/Packet.java
@@ -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:");
+ }
+
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/PacketManager.java b/src/java/InputStickAPI/src/com/inputstick/api/PacketManager.java
index 78ae191f..f579c439 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/PacketManager.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/PacketManager.java
@@ -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]);
@@ -104,17 +112,19 @@ public class PacketManager {
payload = Arrays.copyOfRange(payload, 4, payload.length); //remove CRC
return payload;
} else {
- return null; //TODO
+ return null; //TODO
}
}
public void sendRAW(byte[] data) {
mBTService.write(data);
- }
+ }
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;
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/Util.java b/src/java/InputStickAPI/src/com/inputstick/api/Util.java
index 112e3312..57b187c5 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/Util.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/Util.java
@@ -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;
}
- }
- s = s.toUpperCase();
- System.out.print("0x" + s + " ");
- cnt++;
- if (cnt == 8) {
- System.out.println("");
- cnt = 0;
- }
- }
- System.out.println("\n#####");
+ }
+
+ } else {
+ System.out.println("null");
+ }
+ System.out.println("\n#####");
}
}
@@ -79,5 +97,18 @@ public abstract class Util {
return result;
}
+
+ 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;
+ }
+
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/basic/InputStickHID.java b/src/java/InputStickAPI/src/com/inputstick/api/basic/InputStickHID.java
index cf9adafa..5bc4d5ca 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/basic/InputStickHID.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/basic/InputStickHID.java
@@ -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 mStateListeners = new Vector();
+
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);
+ }
}
}
@@ -99,6 +130,22 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
mStateListeners.remove(listener);
}
}
+
+ 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);
@@ -122,31 +181,112 @@ public class InputStickHID implements InputStickStateListener, InputStickDataLis
}
@Override
- public void onStateChanged(int state) {
+ public void onStateChanged(int state) {
+ if ((state == ConnectionManager.STATE_DISCONNECTED) && (t1 != null)) {
+ t1.cancel();
+ t1 = null;
+ }
for (InputStickStateListener listener : mStateListeners) {
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.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);
+ }
}
- if (mHIDInfo.isMouseReady()) {
- mouseQueue.deviceReady();
- }
- if (mHIDInfo.isConsumerReady()) {
- consumerQueue.deviceReady();
- }
InputStickKeyboard.setLEDs(mHIDInfo.getNumLock(), mHIDInfo.getCapsLock(), mHIDInfo.getScrollLock());
}
}
+ 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;
+ }
+ }
+
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/basic/InputStickKeyboard.java b/src/java/InputStickAPI/src/com/inputstick/api/basic/InputStickKeyboard.java
index 3c902203..9bb05e67 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/basic/InputStickKeyboard.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/basic/InputStickKeyboard.java
@@ -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 mKeyboardListeners = new Vector();
+ private static final SparseArray ledsMap;
+ static
+ {
+ ledsMap = new SparseArray();
+ 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);
+ }
}
}
@@ -114,5 +131,25 @@ public class InputStickKeyboard {
t.addReport(report);
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;
+ }
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/basic/InputStickMouse.java b/src/java/InputStickAPI/src/com/inputstick/api/basic/InputStickMouse.java
index a5db55f2..19924a8f 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/basic/InputStickMouse.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/basic/InputStickMouse.java
@@ -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 buttonsMap;
+ static
+ {
+ buttonsMap = new SparseArray();
+ buttonsMap.put(BUTTON_LEFT, "Left");
+ buttonsMap.put(BUTTON_RIGHT, "Right");
+ buttonsMap.put(BUTTON_MIDDLE, "Middle");
+ }
+
private static boolean mReportProtocol;
private InputStickMouse() {
@@ -53,5 +64,25 @@ public class InputStickMouse {
t.addReport(new MouseReport(buttons, x, y, wheel));
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;
+ }
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/bluetooth/BTService.java b/src/java/InputStickAPI/src/com/inputstick/api/bluetooth/BTService.java
index fd0098ac..54b46d30 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/bluetooth/BTService.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/bluetooth/BTService.java
@@ -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
@@ -71,8 +63,7 @@ public class BTService {
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- System.out.println("ACTION: "+action);
+ final String action = intent.getAction();
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
if ((state == BluetoothAdapter.STATE_ON) && (turnBluetoothOn)) {
@@ -102,12 +93,12 @@ public class BTService {
public void enableReflection(boolean enabled) {
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);
+ mLastEvent = event;
+ 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);
}
}
@@ -356,11 +347,13 @@ public class BTService {
byte[] buffer = null;
int rxTmp;
int lengthByte;
- int length;
+ 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");
}
}
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/hid/HIDKeycodes.java b/src/java/InputStickAPI/src/com/inputstick/api/hid/HIDKeycodes.java
index 3528bdbd..7edec591 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/hid/HIDKeycodes.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/hid/HIDKeycodes.java
@@ -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 modifiersMap;
+ static
+ {
+ modifiersMap = new SparseArray();
+ 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 keyMap;
+ static
+ {
+ keyMap = new SparseArray();
+ 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;
+ }
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/hid/HIDTransactionQueue.java b/src/java/InputStickAPI/src/com/inputstick/api/hid/HIDTransactionQueue.java
index 64b174ee..f6cb57d0 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/hid/HIDTransactionQueue.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/hid/HIDTransactionQueue.java
@@ -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 queue;
private final ConnectionManager mConnectionManager;
private final byte cmd;
private boolean ready;
+ private int mInterfaceType;
+ private boolean mustNotify;
+ private Vector mBufferEmptyListeners = new Vector();
+ private Timer t;
+ private boolean timerCancelled;
+ private boolean sentAhead;
private long lastTime;
- private int lastReports;
+ 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();
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() {
- HIDTransaction transaction;
- byte reports = 0;
- ready = false;
- Packet p = new Packet(false, cmd, reports);
+ private int sendNext(int maxReports) {
+ HIDTransaction transaction;
//assume there is at least 1 element in queue
transaction = queue.firstElement();
- if (transaction.getReportsCount() > BUFFER_SIZE) {
- //transaction too big! split
- transaction = transaction.split(BUFFER_SIZE);
+ 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,33 +117,183 @@ public class HIDTransactionQueue {
}
}
-
+ //!! total number of reports must be < 32 ! (max packet limitation)
p.modifyByte(1, reports); //set reports count
- mConnectionManager.sendPacket(p);
+ 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);
}
+ }
+
+ private synchronized void timerAction() {
+ if ( !timerCancelled) {
+ if (sentAhead) {
+ deviceReady(null, 0); //will set sentAhead to false;
+ sentAhead = true; //restore value
+ } else {
+ deviceReady(null, 0);
+ }
+ }
+ }
+
+ public synchronized void deviceReady(HIDInfo hidInfo, int reportsSentToHost) {
+ //it is possible that in the meantime some packets has been sent to IS!!!
+
+ bufferInitDone = true;
+
+ if (hidInfo != null) {
+ if (hidInfo.isSentToHostInfoAvailable()) {
+ constantUpdateMode = true;
+ // >= FW 0.93
+ bufferFreeSpace += reportsSentToHost;
+ if ((bufferFreeSpace == BUFFER_SIZE) && (queue.isEmpty())) {
+ notifyOnRemoteBufferEmpty();
+ }
+ immediatePacketsLeft = MAX_IMMEDIATE_PACKETS;
+ //reportsSentSinceLastUpdate = 0;
+ packetsSentSinceLastUpdate = 0;
+ sendToBuffer(false);
+ return;
+ }
+ }
+
+
+
+ long now = System.currentTimeMillis();
+ //System.out.println("v90 HID update");
+ if (now < minNextTime) {
+ //set timer, just in case if deviceReady won't be called again
+ timerCancelled = false;
+ t = new Timer();
+ t.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ timerAction();
+ }
+ }, (minNextTime - now + 1));
+ } else {
+ timerCancelled = true;
+ sentAhead = false;
+ if (!queue.isEmpty()) {
+ 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 ++;
+ }
}
- public void deviceReady() {
- if (!queue.isEmpty()) {
- sendNext();
- } else {
- ready = true;
- }
- }
-
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/layout/GermanLayout.java b/src/java/InputStickAPI/src/com/inputstick/api/layout/GermanLayout.java
index b5c56677..a5717d6c 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/layout/GermanLayout.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/layout/GermanLayout.java
@@ -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,9 +169,14 @@ 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
public char getChar(int scanCode, boolean capsLock, boolean shift, boolean altGr) {
return super.getChar(LUT, scanCode, capsLock, shift, altGr);
@@ -137,6 +185,16 @@ public class GermanLayout extends KeyboardLayout {
@Override
public String getLocaleName() {
return LOCALE_NAME;
- }
+ }
+
+ @Override
+ public int[][] getDeadkeyLUT() {
+ return DEADKEY_LUT;
+ }
+
+ @Override
+ public int[] getDeadkeys() {
+ return DEADKEYS;
+ }
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/layout/KeyboardLayout.java b/src/java/InputStickAPI/src/com/inputstick/api/layout/KeyboardLayout.java
index 20cbd121..c5a741bb 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/layout/KeyboardLayout.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/layout/KeyboardLayout.java
@@ -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,19 +236,79 @@ 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;
HIDTransaction t = new HIDTransaction();
scanCode = getScanCode(lut, c);
- if (scanCode > 0) {
+ 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();
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/layout/PolishLayout.java b/src/java/InputStickAPI/src/com/inputstick/api/layout/PolishLayout.java
index e9a3ec4b..b06ec3cc 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/layout/PolishLayout.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/layout/PolishLayout.java
@@ -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;
+ }
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/layout/RussianLayout.java b/src/java/InputStickAPI/src/com/inputstick/api/layout/RussianLayout.java
index 0000d097..f75debb1 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/layout/RussianLayout.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/layout/RussianLayout.java
@@ -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
@@ -140,5 +148,15 @@ public class RussianLayout extends KeyboardLayout {
public String getLocaleName() {
return LOCALE_NAME;
}
+
+ @Override
+ public int[][] getDeadkeyLUT() {
+ return DEADKEY_LUT;
+ }
+
+ @Override
+ public int[] getDeadkeys() {
+ return DEADKEYS;
+ }
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/api/layout/UnitedStatesLayout.java b/src/java/InputStickAPI/src/com/inputstick/api/layout/UnitedStatesLayout.java
index 7f16d907..1790a11b 100644
--- a/src/java/InputStickAPI/src/com/inputstick/api/layout/UnitedStatesLayout.java
+++ b/src/java/InputStickAPI/src/com/inputstick/api/layout/UnitedStatesLayout.java
@@ -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
@@ -141,6 +149,16 @@ public class UnitedStatesLayout extends KeyboardLayout {
@Override
public String getLocaleName() {
return LOCALE_NAME;
- }
+ }
+
+ @Override
+ public int[][] getDeadkeyLUT() {
+ return DEADKEY_LUT;
+ }
+
+ @Override
+ public int[] getDeadkeys() {
+ return DEADKEYS;
+ }
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/init/BasicInitManager.java b/src/java/InputStickAPI/src/com/inputstick/init/BasicInitManager.java
index 413da8e2..621b6945 100644
--- a/src/java/InputStickAPI/src/com/inputstick/init/BasicInitManager.java
+++ b/src/java/InputStickAPI/src/com/inputstick/init/BasicInitManager.java
@@ -3,9 +3,7 @@ package com.inputstick.init;
import com.inputstick.api.Packet;
-public class BasicInitManager extends InitManager {
-
- private boolean initDone = false;
+public class BasicInitManager extends InitManager {
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
- sendPacket(p);*/
-
+ /*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;
}
}
-
}
diff --git a/src/java/InputStickAPI/src/com/inputstick/init/InitManager.java b/src/java/InputStickAPI/src/com/inputstick/init/InitManager.java
index 7d8907ab..0cdd0c25 100644
--- a/src/java/InputStickAPI/src/com/inputstick/init/InitManager.java
+++ b/src/java/InputStickAPI/src/com/inputstick/init/InitManager.java
@@ -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;
+ mKey = key;
}
- public void init(InitManagerListener listener, PacketManager packetManager) {
+
+ 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);
+ }
+
+ }
+
}
diff --git a/src/java/JavaFileStorage/bin/javafilestorage.jar b/src/java/JavaFileStorage/bin/javafilestorage.jar
index 54ae0233..378ce962 100644
Binary files a/src/java/JavaFileStorage/bin/javafilestorage.jar and b/src/java/JavaFileStorage/bin/javafilestorage.jar differ
diff --git a/src/java/KP2AKdbLibrary/bin/kp2akdblibrary.jar b/src/java/KP2AKdbLibrary/bin/kp2akdblibrary.jar
index 0461a369..c2aa87d8 100644
Binary files a/src/java/KP2AKdbLibrary/bin/kp2akdblibrary.jar and b/src/java/KP2AKdbLibrary/bin/kp2akdblibrary.jar differ
diff --git a/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/PwDatabaseV3.java b/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/PwDatabaseV3.java
index a6f924fd..9164352e 100644
--- a/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/PwDatabaseV3.java
+++ b/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/PwDatabaseV3.java
@@ -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);
@@ -151,46 +155,40 @@ 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[] keyFileData = bao.toByteArray();
+
+ ByteArrayInputStream bin = new ByteArrayInputStream(keyFileData);
- byte[] key = loadXmlKeyFile(fileName);
- if ( key != null ) {
- return key;
- }
-
- FileInputStream fis;
- try {
- fis = new FileInputStream(keyfile);
- } catch (FileNotFoundException e) {
- throw new InvalidKeyFileException();
- }
- 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.");
}
@@ -514,11 +512,6 @@ public class PwDatabaseV3 {
public byte[] getPasswordKey(String key) throws IOException {
return getPasswordKey(key, "ISO-8859-1");
}
-
- protected byte[] loadXmlKeyFile(String fileName) {
- return null;
- }
-
public long getNumRounds() {
diff --git a/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/exception/InvalidDBSignatureException.java b/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/exception/InvalidDBSignatureException.java
index d2ac4aea..5ae3d1c3 100644
--- a/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/exception/InvalidDBSignatureException.java
+++ b/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/exception/InvalidDBSignatureException.java
@@ -26,7 +26,7 @@ public class InvalidDBSignatureException extends InvalidDBException {
private static final long serialVersionUID = -5358923878743513758L;
public InvalidDBSignatureException() {
- super();
+ super("Invalid database signature");
}
}
diff --git a/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/exception/InvalidKeyFileException.java b/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/exception/InvalidKeyFileException.java
index 11cd1d8b..38bf7c2f 100644
--- a/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/exception/InvalidKeyFileException.java
+++ b/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/exception/InvalidKeyFileException.java
@@ -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);
}
}
diff --git a/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/exception/InvalidPasswordException.java b/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/exception/InvalidPasswordException.java
index 69096e53..234e4da4 100644
--- a/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/exception/InvalidPasswordException.java
+++ b/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/exception/InvalidPasswordException.java
@@ -31,6 +31,6 @@ public class InvalidPasswordException extends InvalidDBException {
}
public InvalidPasswordException() {
- super();
+ super("Invalid key!");
}
}
diff --git a/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/exception/KeyFileEmptyException.java b/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/exception/KeyFileEmptyException.java
index 21a7a101..502e3da0 100644
--- a/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/exception/KeyFileEmptyException.java
+++ b/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/exception/KeyFileEmptyException.java
@@ -25,6 +25,6 @@ public class KeyFileEmptyException extends InvalidKeyFileException {
private static final long serialVersionUID = -1630780661204212325L;
public KeyFileEmptyException() {
- super();
+ super("key file is empty!");
}
}
diff --git a/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/load/ImporterV3.java b/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/load/ImporterV3.java
index 57d9f734..d20e45d2 100644
--- a/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/load/ImporterV3.java
+++ b/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/load/ImporterV3.java
@@ -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
diff --git a/src/java/Keepass2AndroidPluginSDK/bin/keepass2androidpluginsdk.jar b/src/java/Keepass2AndroidPluginSDK/bin/keepass2androidpluginsdk.jar
index e01d050a..65e768e1 100644
Binary files a/src/java/Keepass2AndroidPluginSDK/bin/keepass2androidpluginsdk.jar and b/src/java/Keepass2AndroidPluginSDK/bin/keepass2androidpluginsdk.jar differ
diff --git a/src/java/PluginInputStick/AndroidManifest.xml b/src/java/PluginInputStick/AndroidManifest.xml
index 78e5bf37..a083e28a 100644
--- a/src/java/PluginInputStick/AndroidManifest.xml
+++ b/src/java/PluginInputStick/AndroidManifest.xml
@@ -1,8 +1,8 @@
+ android:versionCode="3"
+ android:versionName="1.2" >
Host keyboard layout
- - English (United States) - en-US
- - German - de-DE
- - Polish - pl-PL
- - Russian - ru-RU
+ - English (US)
+ - German
+ - Polish
+ - Russian
+ - Slovak
+ - Portuguese (BR)
+ - Norwegian
+ - Swedish
+ - French
+ - Spanish
+ - English (Dvorak)
- - en-US
+ - en-US
- de-DE
- pl-PL
- ru-RU
+ - sk-SK
+ - pt-BR
+ - nb-NO
+ - sv-SE
+ - fr-FR
+ - es-ES
+ - en-DV
diff --git a/src/java/PluginInputStick/src/keepass2android/plugin/inputstick/ActionReceiver.java b/src/java/PluginInputStick/src/keepass2android/plugin/inputstick/ActionReceiver.java
index cfd4876d..f20f5b73 100644
--- a/src/java/PluginInputStick/src/keepass2android/plugin/inputstick/ActionReceiver.java
+++ b/src/java/PluginInputStick/src/keepass2android/plugin/inputstick/ActionReceiver.java
@@ -34,7 +34,7 @@ public class ActionReceiver extends keepass2android.pluginsdk.PluginActionBroadc
} catch (PluginAccessException e) {
e.printStackTrace();
}
- typeText(oe.getContext(), "");
+ //typeText(oe.getContext(), "");
}
@Override
diff --git a/src/java/android-filechooser/code/libs/android-support-v4.jar b/src/java/android-filechooser/code/libs/android-support-v4.jar
index 428bdbc0..187bdf48 100644
Binary files a/src/java/android-filechooser/code/libs/android-support-v4.jar and b/src/java/android-filechooser/code/libs/android-support-v4.jar differ
diff --git a/src/java/android-filechooser/code/project.zip b/src/java/android-filechooser/code/project.zip
index dae0d664..89f0cefb 100644
Binary files a/src/java/android-filechooser/code/project.zip and b/src/java/android-filechooser/code/project.zip differ
diff --git a/src/keepass2android/CreateDatabaseActivity.cs b/src/keepass2android/CreateDatabaseActivity.cs
index 8383d044..1a7eee87 100644
--- a/src/keepass2android/CreateDatabaseActivity.cs
+++ b/src/keepass2android/CreateDatabaseActivity.cs
@@ -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);
diff --git a/src/keepass2android/EntryEditActivity.cs b/src/keepass2android/EntryEditActivity.cs
index 52dc8bcb..a0a57357 100644
--- a/src/keepass2android/EntryEditActivity.cs
+++ b/src/keepass2android/EntryEditActivity.cs
@@ -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);
diff --git a/src/keepass2android/ExportDatabaseActivity.cs b/src/keepass2android/ExportDatabaseActivity.cs
index bca5cf63..ad942c5b 100644
--- a/src/keepass2android/ExportDatabaseActivity.cs
+++ b/src/keepass2android/ExportDatabaseActivity.cs
@@ -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);
diff --git a/src/keepass2android/FileStorageSelectionActivity.cs b/src/keepass2android/FileStorageSelectionActivity.cs
index 9f5d3e95..1dc83353 100644
--- a/src/keepass2android/FileStorageSelectionActivity.cs
+++ b/src/keepass2android/FileStorageSelectionActivity.cs
@@ -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))
diff --git a/src/keepass2android/PasswordActivity.cs b/src/keepass2android/PasswordActivity.cs
index 0dc25e57..2dca2922 100644
--- a/src/keepass2android/PasswordActivity.cs
+++ b/src/keepass2android/PasswordActivity.cs
@@ -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 _loadDbTask;
private IOConnectionInfo _ioConnection;
@@ -136,6 +137,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(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(Resource.Id.label_keyfilename).Tag = _keyFileOrProvider;
+ if (string.IsNullOrEmpty(_keyFileOrProvider))
+ {
+ FindViewById(Resource.Id.filestorage_label).Visibility = ViewStates.Gone;
+ FindViewById(Resource.Id.filestorage_logo).Visibility = ViewStates.Gone;
+ FindViewById(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(Resource.Id.filestorage_logo).SetImageDrawable(drawable);
+ FindViewById(Resource.Id.filestorage_logo).Visibility = ViewStates.Visible;
+
+
+ String title = App.Kp2a.GetResourceString("filestoragename_" + protocolId);
+ FindViewById(Resource.Id.filestorage_label).Text = title;
+ FindViewById(Resource.Id.filestorage_label).Visibility = ViewStates.Visible;
+
+ FindViewById(Resource.Id.label_keyfilename).Text = protocolSeparatorPos < 0 ?
+ displayPath :
+ displayPath.Substring(protocolSeparatorPos + 3);
+
+ }
+
+
private void LoadOtpFile()
{
new LoadingDialog