diff --git a/src/KeePassLib2Android/KeePassLib2Android.csproj b/src/KeePassLib2Android/KeePassLib2Android.csproj
index 9f86c555..2c82809f 100644
--- a/src/KeePassLib2Android/KeePassLib2Android.csproj
+++ b/src/KeePassLib2Android/KeePassLib2Android.csproj
@@ -20,7 +20,7 @@
full
False
bin\Debug
- DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;INCLUDE_KEYTRANSFORM
+ DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;INCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;INCLUDE_KEYTRANSFORM
prompt
4
False
diff --git a/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs b/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs
index d840b096..cb08da30 100644
--- a/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs
+++ b/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs
@@ -143,6 +143,18 @@ namespace keepass2android.Io
{
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
diff --git a/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs b/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs
index df44549e..05a75a94 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;
@@ -290,5 +291,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..50b2edd6 100644
--- a/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs
+++ b/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs
@@ -542,6 +542,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 31a68ae3..a731f3c5 100644
--- a/src/Kp2aBusinessLogic/Io/IFileStorage.cs
+++ b/src/Kp2aBusinessLogic/Io/IFileStorage.cs
@@ -149,6 +149,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/Kp2aBusinessLogic.csproj b/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj
index 6c7ad4b7..616cb62b 100644
--- a/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj
+++ b/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj
@@ -20,7 +20,7 @@
full
false
bin\Debug\
- TRACE;DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;INCLUDE_KEYTRANSFORM
+ TRACE;DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;INCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;INCLUDE_KEYTRANSFORM
prompt
4
diff --git a/src/Kp2aUnitTests/Kp2aUnitTests.csproj b/src/Kp2aUnitTests/Kp2aUnitTests.csproj
index c4ff09f9..f93d034a 100644
--- a/src/Kp2aUnitTests/Kp2aUnitTests.csproj
+++ b/src/Kp2aUnitTests/Kp2aUnitTests.csproj
@@ -67,6 +67,7 @@
+
diff --git a/src/Kp2aUnitTests/MainActivity.cs b/src/Kp2aUnitTests/MainActivity.cs
index 62f0cc66..62098646 100644
--- a/src/Kp2aUnitTests/MainActivity.cs
+++ b/src/Kp2aUnitTests/MainActivity.cs
@@ -20,7 +20,10 @@ namespace Kp2aUnitTests
// Run all tests from this assembly
//runner.AddTests(Assembly.GetExecutingAssembly());
//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdb1WithKeyfileByDirectCall"));
- runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdb1WithKeyfileOnly"));
+ //runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdb1WithKeyfileOnly"));
+
+
+ runner.AddTests(new List { typeof(TestBuiltInFileStorage) });
//runner.AddTests(new List { typeof(TestSynchronizeCachedDatabase)});
//runner.AddTests(typeof(TestLoadDb).GetMethod("LoadErrorWithCertificateTrustFailure"));
//runner.AddTests(typeof(TestLoadDb).GetMethod("LoadWithAcceptedCertificateTrustFailure"));
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/java/KP2AKdbLibrary/src/com/keepassdroid/database/load/ImporterV3.java b/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/load/ImporterV3.java
index bc319b7d..45361d4a 100644
--- a/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/load/ImporterV3.java
+++ b/src/java/KP2AKdbLibrary/src/com/keepassdroid/database/load/ImporterV3.java
@@ -167,7 +167,7 @@ public class ImporterV3 {
hdr.loadFromFile(filebuf, 0 );
if( (hdr.signature1 != PwDbHeader.PWM_DBSIG_1) || (hdr.signature2 != PwDbHeaderV3.DBSIG_2) ) {
- throw new InvalidDBSignatureException();
+ throw new InvalidDBSignatureException("Invalid database signature!");
}
if( !hdr.matchesVersion() ) {
@@ -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/keepass2android/Resources/Resource.designer.cs b/src/keepass2android/Resources/Resource.designer.cs
index 98fcc299..97ab81ac 100644
--- a/src/keepass2android/Resources/Resource.designer.cs
+++ b/src/keepass2android/Resources/Resource.designer.cs
@@ -3296,6 +3296,9 @@ namespace keepass2android
// aapt resource value: 0x7f070204
public const int BinaryDirectory_title = 2131165700;
+ // aapt resource value: 0x7f0702c5
+ public const int CancelReadOnly = 2131165893;
+
// aapt resource value: 0x7f07026a
public const int CannotMoveGroupHere = 2131165802;
@@ -3305,56 +3308,56 @@ namespace keepass2android
// aapt resource value: 0x7f0702bb
public const int CertificateWarning = 2131165883;
+ // aapt resource value: 0x7f0702d8
+ public const int ChangeLog = 2131165912;
+
+ // aapt resource value: 0x7f0702d7
+ public const int ChangeLog_0_7 = 2131165911;
+
+ // aapt resource value: 0x7f0702d5
+ public const int ChangeLog_0_8 = 2131165909;
+
+ // aapt resource value: 0x7f0702d4
+ public const int ChangeLog_0_8_1 = 2131165908;
+
+ // aapt resource value: 0x7f0702d3
+ public const int ChangeLog_0_8_2 = 2131165907;
+
+ // aapt resource value: 0x7f0702d2
+ public const int ChangeLog_0_8_3 = 2131165906;
+
+ // aapt resource value: 0x7f0702d1
+ public const int ChangeLog_0_8_4 = 2131165905;
+
// aapt resource value: 0x7f0702d0
- public const int ChangeLog = 2131165904;
+ public const int ChangeLog_0_8_5 = 2131165904;
// aapt resource value: 0x7f0702cf
- public const int ChangeLog_0_7 = 2131165903;
-
- // aapt resource value: 0x7f0702cd
- public const int ChangeLog_0_8 = 2131165901;
-
- // aapt resource value: 0x7f0702cc
- public const int ChangeLog_0_8_1 = 2131165900;
-
- // aapt resource value: 0x7f0702cb
- public const int ChangeLog_0_8_2 = 2131165899;
-
- // aapt resource value: 0x7f0702ca
- public const int ChangeLog_0_8_3 = 2131165898;
-
- // aapt resource value: 0x7f0702c9
- public const int ChangeLog_0_8_4 = 2131165897;
-
- // aapt resource value: 0x7f0702c8
- public const int ChangeLog_0_8_5 = 2131165896;
-
- // aapt resource value: 0x7f0702c7
- public const int ChangeLog_0_8_6 = 2131165895;
-
- // aapt resource value: 0x7f0702c6
- public const int ChangeLog_0_9 = 2131165894;
-
- // aapt resource value: 0x7f0702c5
- public const int ChangeLog_0_9_1 = 2131165893;
-
- // aapt resource value: 0x7f0702c4
- public const int ChangeLog_0_9_2 = 2131165892;
-
- // aapt resource value: 0x7f0702c3
- public const int ChangeLog_0_9_3 = 2131165891;
-
- // aapt resource value: 0x7f0702c2
- public const int ChangeLog_0_9_3_r5 = 2131165890;
-
- // aapt resource value: 0x7f0702c1
- public const int ChangeLog_0_9_4 = 2131165889;
+ public const int ChangeLog_0_8_6 = 2131165903;
// aapt resource value: 0x7f0702ce
- public const int ChangeLog_keptDonate = 2131165902;
+ public const int ChangeLog_0_9 = 2131165902;
- // aapt resource value: 0x7f0702bf
- public const int ChangeLog_title = 2131165887;
+ // aapt resource value: 0x7f0702cd
+ public const int ChangeLog_0_9_1 = 2131165901;
+
+ // aapt resource value: 0x7f0702cc
+ public const int ChangeLog_0_9_2 = 2131165900;
+
+ // aapt resource value: 0x7f0702cb
+ public const int ChangeLog_0_9_3 = 2131165899;
+
+ // aapt resource value: 0x7f0702ca
+ public const int ChangeLog_0_9_3_r5 = 2131165898;
+
+ // aapt resource value: 0x7f0702c9
+ public const int ChangeLog_0_9_4 = 2131165897;
+
+ // aapt resource value: 0x7f0702d6
+ public const int ChangeLog_keptDonate = 2131165910;
+
+ // aapt resource value: 0x7f0702c7
+ public const int ChangeLog_title = 2131165895;
// aapt resource value: 0x7f070112
public const int CheckForFileChangesOnSave_key = 2131165458;
@@ -3380,9 +3383,21 @@ namespace keepass2android
// aapt resource value: 0x7f070226
public const int ClearOfflineCache_title = 2131165734;
+ // aapt resource value: 0x7f0702c4
+ public const int ClickOkToSelectLocation = 2131165892;
+
+ // aapt resource value: 0x7f0702c2
+ public const int CopyFileRequired = 2131165890;
+
+ // aapt resource value: 0x7f0702c3
+ public const int CopyFileRequiredForEditing = 2131165891;
+
// aapt resource value: 0x7f070117
public const int CopyToClipboardNotification_key = 2131165463;
+ // aapt resource value: 0x7f0702c6
+ public const int CopyingFile = 2131165894;
+
// aapt resource value: 0x7f07025d
public const int CouldNotLoadFromRemote = 2131165789;
@@ -3437,6 +3452,15 @@ namespace keepass2android
// aapt resource value: 0x7f070104
public const int FileHandling_prefs_key = 2131165444;
+ // aapt resource value: 0x7f0702c0
+ public const int FileIsReadOnly = 2131165888;
+
+ // aapt resource value: 0x7f0702c1
+ public const int FileIsReadOnlyOnKitkat = 2131165889;
+
+ // aapt resource value: 0x7f0702bf
+ public const int FileIsTemporarilyAvailable = 2131165887;
+
// aapt resource value: 0x7f07017a
public const int FileNotFound = 2131165562;
@@ -3524,8 +3548,8 @@ namespace keepass2android
// aapt resource value: 0x7f070234
public const int PreloadDatabaseEnabled_title = 2131165748;
- // aapt resource value: 0x7f0702c0
- public const int PreviewWarning = 2131165888;
+ // aapt resource value: 0x7f0702c8
+ public const int PreviewWarning = 2131165896;
// aapt resource value: 0x7f070105
public const int QuickUnlockDefaultEnabled_key = 2131165445;
@@ -4169,11 +4193,11 @@ namespace keepass2android
// aapt resource value: 0x7f070145
public const int brackets = 2131165509;
- // aapt resource value: 0x7f0702d3
- public const int browser_intall_text = 2131165907;
+ // aapt resource value: 0x7f0702db
+ public const int browser_intall_text = 2131165915;
- // aapt resource value: 0x7f0702d4
- public const int building_search_idx = 2131165908;
+ // aapt resource value: 0x7f0702dc
+ public const int building_search_idx = 2131165916;
// aapt resource value: 0x7f070285
public const int button_change_location = 2131165829;
@@ -4259,14 +4283,14 @@ namespace keepass2android
// aapt resource value: 0x7f0700ec
public const int db_key = 2131165420;
- // aapt resource value: 0x7f0702d5
- public const int decrypting_db = 2131165909;
+ // aapt resource value: 0x7f0702dd
+ public const int decrypting_db = 2131165917;
- // aapt resource value: 0x7f0702d6
- public const int decrypting_entry = 2131165910;
+ // aapt resource value: 0x7f0702de
+ public const int decrypting_entry = 2131165918;
- // aapt resource value: 0x7f0702d7
- public const int default_checkbox = 2131165911;
+ // aapt resource value: 0x7f0702df
+ public const int default_checkbox = 2131165919;
// aapt resource value: 0x7f0700de
public const int default_file_path = 2131165406;
@@ -4289,8 +4313,8 @@ namespace keepass2android
// aapt resource value: 0x7f0700f3
public const int design_key = 2131165427;
- // aapt resource value: 0x7f0702d1
- public const int design_title = 2131165905;
+ // aapt resource value: 0x7f0702d9
+ public const int design_title = 2131165913;
// aapt resource value: 0x7f070152
public const int digits = 2131165522;
@@ -4352,8 +4376,8 @@ namespace keepass2android
// aapt resource value: 0x7f070156
public const int entry_accessed = 2131165526;
- // aapt resource value: 0x7f0702d8
- public const int entry_and_or = 2131165912;
+ // aapt resource value: 0x7f0702e0
+ public const int entry_and_or = 2131165920;
// aapt resource value: 0x7f070168
public const int entry_binaries = 2131165544;
@@ -4409,8 +4433,8 @@ namespace keepass2android
// aapt resource value: 0x7f07028d
public const int error_adding_keyfile = 2131165837;
- // aapt resource value: 0x7f0702d9
- public const int error_arc4 = 2131165913;
+ // aapt resource value: 0x7f0702e1
+ public const int error_arc4 = 2131165921;
// aapt resource value: 0x7f070169
public const int error_can_not_handle_uri = 2131165545;
@@ -4424,8 +4448,8 @@ namespace keepass2android
// aapt resource value: 0x7f07016c
public const int error_database_exists = 2131165548;
- // aapt resource value: 0x7f0702d2
- public const int error_database_settings = 2131165906;
+ // aapt resource value: 0x7f0702da
+ public const int error_database_settings = 2131165914;
// aapt resource value: 0x7f07016d
public const int error_database_settinoverrgs = 2131165549;
@@ -4454,8 +4478,8 @@ namespace keepass2android
// aapt resource value: 0x7f070174
public const int error_nopass = 2131165556;
- // aapt resource value: 0x7f0702da
- public const int error_out_of_memory = 2131165914;
+ // aapt resource value: 0x7f0702e2
+ public const int error_out_of_memory = 2131165922;
// aapt resource value: 0x7f070175
public const int error_pass_gen_type = 2131165557;
@@ -4466,8 +4490,8 @@ namespace keepass2android
// aapt resource value: 0x7f070177
public const int error_rounds_not_number = 2131165559;
- // aapt resource value: 0x7f0702db
- public const int error_rounds_too_large = 2131165915;
+ // aapt resource value: 0x7f0702e3
+ public const int error_rounds_too_large = 2131165923;
// aapt resource value: 0x7f07020f
public const int error_string_key = 2131165711;
@@ -4655,11 +4679,11 @@ namespace keepass2android
// aapt resource value: 0x7f0701d6
public const int insert_element_here = 2131165654;
- // aapt resource value: 0x7f0702dc
- public const int install_from_market = 2131165916;
+ // aapt resource value: 0x7f0702e4
+ public const int install_from_market = 2131165924;
- // aapt resource value: 0x7f0702dd
- public const int install_from_website = 2131165917;
+ // aapt resource value: 0x7f0702e5
+ public const int install_from_website = 2131165925;
// aapt resource value: 0x7f07018c
public const int invalid_algorithm = 2131165580;
@@ -4874,8 +4898,8 @@ namespace keepass2android
// aapt resource value: 0x7f0701a6
public const int menu_hide_password = 2131165606;
- // aapt resource value: 0x7f0702de
- public const int menu_homepage = 2131165918;
+ // aapt resource value: 0x7f0702e6
+ public const int menu_homepage = 2131165926;
// aapt resource value: 0x7f0701a7
public const int menu_lock = 2131165607;
diff --git a/src/keepass2android/Resources/values/strings.xml b/src/keepass2android/Resources/values/strings.xml
index 6abd3d65..5b3f891e 100644
--- a/src/keepass2android/Resources/values/strings.xml
+++ b/src/keepass2android/Resources/values/strings.xml
@@ -475,6 +475,17 @@
Sorry! Keepass2Android was killed by the Android OS! For security reasons, Keepass2Android did not persist your selected credentials on disk, so you need to re-open your database. Note: This should happen only very rarely. If it does, please drop me a message at crocoapps@gmail.com.
+
+ The file is only temporarily available for Keepass2Android.
+ The file you selected is read-only.
+ The file you selected is read-only for Keepass2Android due to restrictions on Android 4.4+.
+ To use it, you must copy it to another location.
+ To edit it, you must copy the file to another location.
+ Click OK to select a location where the file should be copied.
+ Cancel, open read-only.
+
+ Copying file...
+
Change log
Please note! This is a preview release and might come with some flaws! If you experience *anything* unexpected, please let me know (on Codeplex or by email).
diff --git a/src/keepass2android/SelectStorageLocationActivity.cs b/src/keepass2android/SelectStorageLocationActivity.cs
index 691fa4de..12a8b96b 100644
--- a/src/keepass2android/SelectStorageLocationActivity.cs
+++ b/src/keepass2android/SelectStorageLocationActivity.cs
@@ -9,6 +9,7 @@ using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
+using Group.Pals.Android.Lib.UI.Filechooser.Utils.UI;
using KeePassLib.Serialization;
using keepass2android.Io;
using Environment = Android.OS.Environment;
@@ -16,11 +17,24 @@ using Environment = Android.OS.Environment;
namespace keepass2android
{
[Activity(Label = "")]
- public class SelectStorageLocationActivity : Activity
+ public class SelectStorageLocationActivity : Activity, IDialogInterfaceOnDismissListener
{
private ActivityDesign _design;
private bool _isRecreated;
- private const int RequestCodeFileStorageSelection = 983713;
+ private IOConnectionInfo _selectedIoc;
+ private const string BundleKeySelectedIoc = "BundleKeySelectedIoc";
+ private const int RequestCodeFileStorageSelectionForPrimarySelect = 983713;
+ private const int RequestCodeFileStorageSelectionForCopyToWritableLocation = 983714;
+ private const int RequestCodeFileFileBrowseForWritableLocation = 983715;
+
+ public enum WritableRequirements
+ {
+ ReadOnly = 0,
+ WriteDesired = 1,
+ WriteDemanded = 2
+ }
+
+ public const string ExtraKeyWritableRequirements = "EXTRA_KEY_WRITABLE_REQUIREMENTS";
public SelectStorageLocationActivity()
{
@@ -36,12 +50,12 @@ namespace keepass2android
Kp2aLog.Log("SelectStorageLocationActivity.OnCreate");
- IsForSave = Intent.GetBooleanExtra(FileStorageSetupDefs.ExtraIsForSave, false);
- if (IsForSave)
+
+ if (IsStorageSelectionForSave)
{
throw new Exception("save is not yet implemented. In StartSelectFile, no handler for onCreate is passed.");
}
-
+
bool allowThirdPartyGet = Intent.GetBooleanExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, false);
bool allowThirdPartySend = Intent.GetBooleanExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, false);
if (bundle == null)
@@ -49,6 +63,9 @@ namespace keepass2android
else
{
State = (Bundle)bundle.Clone();
+ var selectedIocString = bundle.GetString(BundleKeySelectedIoc, null);
+ if (selectedIocString != null)
+ _selectedIoc = IOConnectionInfo.UnserializeFromString(selectedIocString);
_isRecreated = true;
}
@@ -57,7 +74,7 @@ namespace keepass2android
Intent intent = new Intent(this, typeof(FileStorageSelectionActivity));
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, allowThirdPartyGet);
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, allowThirdPartySend);
- StartActivityForResult(intent, RequestCodeFileStorageSelection);
+ StartActivityForResult(intent, RequestCodeFileStorageSelectionForPrimarySelect);
}
@@ -65,7 +82,10 @@ namespace keepass2android
protected Bundle State { get; set; }
- protected bool IsForSave { get; set; }
+ protected bool IsStorageSelectionForSave
+ {
+ get { return Intent.GetBooleanExtra(FileStorageSetupDefs.ExtraIsForSave, false); }
+ }
protected override void OnSaveInstanceState(Bundle outState)
@@ -73,6 +93,8 @@ namespace keepass2android
base.OnSaveInstanceState(outState);
outState.PutAll(State);
+ if (_selectedIoc != null)
+ outState.PutString(BundleKeySelectedIoc, IOConnectionInfo.SerializeToString(_selectedIoc));
}
protected override void OnResume()
@@ -84,8 +106,14 @@ namespace keepass2android
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
- if (requestCode == RequestCodeFileStorageSelection)
+ if ((requestCode == RequestCodeFileStorageSelectionForPrimarySelect) || ((requestCode == RequestCodeFileStorageSelectionForCopyToWritableLocation)))
{
+ int browseRequestCode = Intents.RequestCodeFileBrowseForOpen;
+ if (requestCode == RequestCodeFileStorageSelectionForCopyToWritableLocation)
+ {
+ browseRequestCode = RequestCodeFileFileBrowseForWritableLocation;
+ }
+
if (resultCode == KeePass.ExitFileStorageSelectionOk)
{
@@ -97,45 +125,55 @@ namespace keepass2android
}
else
{
+ bool isForSave = (requestCode == RequestCodeFileStorageSelectionForPrimarySelect) ?
+ IsStorageSelectionForSave : true;
+
App.Kp2a.GetFileStorage(protocolId).StartSelectFile(new FileStorageSetupInitiatorActivity(this,
OnActivityResult,
defaultPath =>
- {
+ {
if (defaultPath.StartsWith("sftp://"))
- Util.ShowSftpDialog(this, OnReceivedSftpData, ReturnCancel);
+ Util.ShowSftpDialog(this, filename => OnReceivedSftpData(filename, browseRequestCode, isForSave), ReturnCancel);
else
- Util.ShowFilenameDialog(this, OnOpenButton, null, ReturnCancel, false, defaultPath, GetString(Resource.String.enter_filename_details_url),
- Intents.RequestCodeFileBrowseForOpen);
+ //todo oncreate nur wenn for save?
+ Util.ShowFilenameDialog(this, filename => OnOpenButton(filename, browseRequestCode),
+ filename => OnOpenButton(filename, browseRequestCode),
+ ReturnCancel, false, defaultPath, GetString(Resource.String.enter_filename_details_url),
+ browseRequestCode);
}
- ), false, 0, protocolId);
+ ), isForSave, browseRequestCode, protocolId);
}
}
else
{
- if (resultCode == (Result)FileStorageResults.FileChooserPrepared)
- {
- IOConnectionInfo ioc = new IOConnectionInfo();
- PasswordActivity.SetIoConnectionFromIntent(ioc, data);
-#if !EXCLUDE_FILECHOOSER
- StartFileChooser(ioc.Path);
-#else
- ReturnIoc(new IOConnectionInfo { Path = "/mnt/sdcard/keepass/yubi.kdbx" });
-#endif
- return;
- }
- if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE")))
- {
- Toast.MakeText(this, data.GetStringExtra("EXTRA_ERROR_MESSAGE"), ToastLength.Long).Show();
- }
ReturnCancel();
}
}
-
- if (requestCode == Intents.RequestCodeFileBrowseForOpen)
+
+ if ((requestCode == Intents.RequestCodeFileBrowseForOpen) || (requestCode == RequestCodeFileFileBrowseForWritableLocation))
{
+ if (resultCode == (Result)FileStorageResults.FileChooserPrepared)
+ {
+ IOConnectionInfo ioc = new IOConnectionInfo();
+ PasswordActivity.SetIoConnectionFromIntent(ioc, data);
+#if !EXCLUDE_FILECHOOSER
+ bool isForSave = (requestCode == RequestCodeFileFileBrowseForWritableLocation) ?
+ true : IsStorageSelectionForSave ;
+
+ StartFileChooser(ioc.Path, requestCode, isForSave);
+#else
+ IocSelected(new IOConnectionInfo { Path = "/mnt/sdcard/keepass/yubi.kdbx" }, requestCode);
+#endif
+ return;
+ }
+ if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE")))
+ {
+ Toast.MakeText(this, data.GetStringExtra("EXTRA_ERROR_MESSAGE"), ToastLength.Long).Show();
+ }
+
if (resultCode == Result.Ok)
{
string filename = Util.IntentToFilename(data, this);
@@ -152,13 +190,13 @@ namespace keepass2android
Path = filename
};
- ReturnIoc(ioc);
+ IocSelected(ioc, requestCode);
}
else
{
if (data.Data.Scheme == "content")
{
- ReturnIoc(IOConnectionInfo.FromPath(data.DataString));
+ IocSelected(IOConnectionInfo.FromPath(data.DataString), requestCode);
}
else
@@ -189,28 +227,154 @@ namespace keepass2android
Finish();
}
- private void ReturnIoc(IOConnectionInfo ioc)
+ private void IocSelected(IOConnectionInfo ioc, int requestCode)
+ {
+ if (requestCode == RequestCodeFileFileBrowseForWritableLocation)
+ {
+ IocForCopySelected(ioc);
+ }
+ else if (requestCode == Intents.RequestCodeFileBrowseForOpen)
+ {
+ PrimaryIocSelected(ioc);
+ }
+ else
+ {
+#if DEBUG
+ throw new Exception("invalid request code!");
+#endif
+ }
+
+
+
+ }
+
+ private void IocForCopySelected(IOConnectionInfo targetIoc)
+ {
+ new keepass2android.Utils.SimpleLoadingDialog(this, GetString(Resource.String.CopyingFile), false,
+ () =>
+ {
+ IOConnectionInfo sourceIoc = _selectedIoc;
+
+ try
+ {
+ CopyFile(targetIoc, sourceIoc);
+ }
+ catch (Exception e)
+ {
+ return () =>
+ {
+ Toast.MakeText(this, App.Kp2a.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message, ToastLength.Long).Show();
+ ReturnCancel();
+ };
+ }
+
+
+ return () => {ReturnOk(targetIoc); };
+ }
+ ).Execute(new Object[] {});
+ }
+
+ private static void CopyFile(IOConnectionInfo targetIoc, IOConnectionInfo sourceIoc)
+ {
+ IFileStorage sourceStorage = App.Kp2a.GetFileStorage(sourceIoc);
+ IFileStorage targetStorage = App.Kp2a.GetFileStorage(targetIoc);
+
+ using (
+ var writeTransaction = targetStorage.OpenWriteTransaction(targetIoc,
+ App.Kp2a.GetBooleanPreference(
+ PreferenceKey.UseFileTransactions)))
+ {
+ using (var writeStream = writeTransaction.OpenFile())
+ {
+ sourceStorage.OpenFileForRead(sourceIoc).CopyTo(writeStream);
+ }
+ writeTransaction.CommitWrite();
+ }
+ }
+
+ private void PrimaryIocSelected(IOConnectionInfo ioc)
+ {
+ if (!App.Kp2a.GetFileStorage(ioc).IsPermanentLocation(ioc))
+ {
+ new AlertDialog.Builder(this)
+ .SetPositiveButton(Android.Resource.String.Ok, (sender, args) => { MoveToWritableLocation(ioc); })
+ .SetMessage(Resources.GetString(Resource.String.FileIsTemporarilyAvailable) + " "
+ + Resources.GetString(Resource.String.CopyFileRequired) + " "
+ + Resources.GetString(Resource.String.ClickOkToSelectLocation))
+ .SetCancelable(false)
+ .SetNegativeButton(Android.Resource.String.Cancel, (sender, args) => { ReturnCancel(); })
+ //.SetOnDismissListener(this)
+ .Create()
+ .Show();
+ return;
+ }
+ var filestorage = App.Kp2a.GetFileStorage(ioc);
+
+ if ((RequestedWritableRequirements != WritableRequirements.ReadOnly) && (filestorage.IsReadOnly(ioc)))
+ {
+ string readOnlyExplanation = Resources.GetString(Resource.String.FileIsReadOnly);
+ BuiltInFileStorage builtInFileStorage = filestorage as BuiltInFileStorage;
+ if (builtInFileStorage != null)
+ {
+ if (builtInFileStorage.IsReadOnlyBecauseKitkatRestrictions(ioc))
+ readOnlyExplanation = Resources.GetString(Resource.String.FileIsReadOnlyOnKitkat);
+ }
+ new AlertDialog.Builder(this)
+ .SetPositiveButton(Android.Resource.String.Ok, (sender, args) => { MoveToWritableLocation(ioc); })
+ .SetCancelable(false)
+ .SetNegativeButton(Android.Resource.String.Cancel, (sender, args) => { ReturnCancel(); })
+ //.SetOnDismissListener(this)
+ .SetMessage(readOnlyExplanation + " "
+ + (RequestedWritableRequirements == WritableRequirements.WriteDemanded ?
+ Resources.GetString(Resource.String.CopyFileRequired)
+ : Resources.GetString(Resource.String.CopyFileRequiredForEditing))
+ + " "
+ + Resources.GetString(Resource.String.ClickOkToSelectLocation))
+ .Create()
+ .Show();
+ return;
+ }
+ ReturnOk(ioc);
+ }
+
+ private void ReturnOk(IOConnectionInfo ioc)
{
Intent intent = new Intent();
PasswordActivity.PutIoConnectionToIntent(ioc, intent);
SetResult(Result.Ok, intent);
Finish();
+ }
+
+ private WritableRequirements RequestedWritableRequirements
+ {
+ get { return (WritableRequirements) Intent.GetIntExtra(ExtraKeyWritableRequirements, (int)WritableRequirements.ReadOnly); }
+ }
+
+ private void MoveToWritableLocation(IOConnectionInfo ioc)
+ {
+ _selectedIoc = ioc;
+
+ Intent intent = new Intent(this, typeof(FileStorageSelectionActivity));
+ intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, false);
+ intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, false);
+
+ StartActivityForResult(intent, RequestCodeFileStorageSelectionForCopyToWritableLocation);
}
- private bool OnReceivedSftpData(string filename)
+ private bool OnReceivedSftpData(string filename, int requestCode, bool isForSave)
{
IOConnectionInfo ioc = new IOConnectionInfo { Path = filename };
#if !EXCLUDE_FILECHOOSER
- StartFileChooser(ioc.Path);
+ StartFileChooser(ioc.Path, requestCode, isForSave);
#else
- ReturnIoc(ioc);
+ IocSelected(ioc, requestCode);
#endif
return true;
}
#if !EXCLUDE_FILECHOOSER
- private void StartFileChooser(string defaultPath)
+ private void StartFileChooser(string defaultPath, int requestCode, bool forSave)
{
Kp2aLog.Log("FSA: defaultPath="+defaultPath);
string fileProviderAuthority = FileChooserFileProvider.TheAuthority;
@@ -221,24 +385,35 @@ namespace keepass2android
Intent i = Keepass2android.Kp2afilechooser.Kp2aFileChooserBridge.GetLaunchFileChooserIntent(this, fileProviderAuthority,
defaultPath);
- StartActivityForResult(i, Intents.RequestCodeFileBrowseForOpen);
+
+ if (forSave)
+ {
+ i.PutExtra("group.pals.android.lib.ui.filechooser.FileChooserActivity.save_dialog", true);
+ i.PutExtra("group.pals.android.lib.ui.filechooser.FileChooserActivity.default_file_ext", "kdbx");
+ }
+ StartActivityForResult(i, requestCode);
}
#endif
- private bool OnOpenButton(String fileName)
+ private bool OnOpenButton(String fileName, int requestCode)
{
+
IOConnectionInfo ioc = new IOConnectionInfo
{
Path = fileName
};
- ReturnIoc(ioc);
+ IocSelected(ioc, requestCode);
return true;
}
+ public void OnDismiss(IDialogInterface dialog)
+ {
+// ReturnCancel();
+ }
}
diff --git a/src/keepass2android/Utils/LoadingDialog.cs b/src/keepass2android/Utils/LoadingDialog.cs
index 459c6d4c..274557b0 100644
--- a/src/keepass2android/Utils/LoadingDialog.cs
+++ b/src/keepass2android/Utils/LoadingDialog.cs
@@ -9,13 +9,13 @@ using Object = Java.Lang.Object;
namespace keepass2android.Utils
{
- public class LoadingDialog : AsyncTask
+ public class LoadingDialog : AsyncTask
{
private readonly Context _context;
private readonly string _message;
private readonly bool _cancelable;
- readonly Func