SelectStorageLocationActivity handles read-only/temporary cases (first implementation, untested)

This commit is contained in:
Philipp Crocoll 2014-11-18 06:24:35 +01:00
parent 49c4fa5b05
commit 2593119dec
17 changed files with 547 additions and 150 deletions

View File

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

View File

@ -143,6 +143,18 @@ namespace keepass2android.Io
{ {
throw new NotImplementedException(); 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 class AndroidContentWriteTransaction : IWriteTransaction

View File

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

View File

@ -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) private void StoreFilePath(IOConnectionInfo folderPath, string filename, IOConnectionInfo res)
{ {
File.WriteAllText(CachedFilePath(GetPseudoIoc(folderPath, filename)) + ".filepath", res.Path); File.WriteAllText(CachedFilePath(GetPseudoIoc(folderPath, filename)) + ".filepath", res.Path);

View File

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

View File

@ -20,7 +20,7 @@
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath> <OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;EXCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;INCLUDE_KEYTRANSFORM</DefineConstants> <DefineConstants>TRACE;DEBUG;EXCLUDE_TWOFISH;EXCLUDE_KEYBOARD;INCLUDE_FILECHOOSER;EXCLUDE_JAVAFILESTORAGE;INCLUDE_KEYTRANSFORM</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>

View File

@ -67,6 +67,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="TestAndroidContentFileStorage.cs" />
<Compile Include="TestIntentsAndBundles.cs" /> <Compile Include="TestIntentsAndBundles.cs" />
<Compile Include="ProgressDialogStub.cs" /> <Compile Include="ProgressDialogStub.cs" />
<Compile Include="TestBase.cs" /> <Compile Include="TestBase.cs" />

View File

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

View File

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

View File

@ -167,7 +167,7 @@ public class ImporterV3 {
hdr.loadFromFile(filebuf, 0 ); hdr.loadFromFile(filebuf, 0 );
if( (hdr.signature1 != PwDbHeader.PWM_DBSIG_1) || (hdr.signature2 != PwDbHeaderV3.DBSIG_2) ) { if( (hdr.signature1 != PwDbHeader.PWM_DBSIG_1) || (hdr.signature2 != PwDbHeaderV3.DBSIG_2) ) {
throw new InvalidDBSignatureException(); throw new InvalidDBSignatureException("Invalid database signature!");
} }
if( !hdr.matchesVersion() ) { if( !hdr.matchesVersion() ) {
@ -230,7 +230,7 @@ public class ImporterV3 {
} catch (IllegalBlockSizeException e1) { } catch (IllegalBlockSizeException e1) {
throw new IOException("Invalid block size"); throw new IOException("Invalid block size");
} catch (BadPaddingException e1) { } catch (BadPaddingException e1) {
throw new InvalidPasswordException(); throw new InvalidPasswordException("Invalid key!");
} }
// Copy decrypted data for testing // Copy decrypted data for testing
@ -251,7 +251,7 @@ public class ImporterV3 {
if( ! Arrays.equals(hash, hdr.contentsHash) ) { if( ! Arrays.equals(hash, hdr.contentsHash) ) {
Log.w("KeePassDroid","Database file did not decrypt correctly. (checksum code is broken)"); Log.w("KeePassDroid","Database file did not decrypt correctly. (checksum code is broken)");
throw new InvalidPasswordException(); throw new InvalidPasswordException("Invalid key!");
} }
// Import all groups // Import all groups

View File

@ -3296,6 +3296,9 @@ namespace keepass2android
// aapt resource value: 0x7f070204 // aapt resource value: 0x7f070204
public const int BinaryDirectory_title = 2131165700; public const int BinaryDirectory_title = 2131165700;
// aapt resource value: 0x7f0702c5
public const int CancelReadOnly = 2131165893;
// aapt resource value: 0x7f07026a // aapt resource value: 0x7f07026a
public const int CannotMoveGroupHere = 2131165802; public const int CannotMoveGroupHere = 2131165802;
@ -3305,56 +3308,56 @@ namespace keepass2android
// aapt resource value: 0x7f0702bb // aapt resource value: 0x7f0702bb
public const int CertificateWarning = 2131165883; 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 // aapt resource value: 0x7f0702d0
public const int ChangeLog = 2131165904; public const int ChangeLog_0_8_5 = 2131165904;
// aapt resource value: 0x7f0702cf // aapt resource value: 0x7f0702cf
public const int ChangeLog_0_7 = 2131165903; public const int ChangeLog_0_8_6 = 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;
// aapt resource value: 0x7f0702ce // aapt resource value: 0x7f0702ce
public const int ChangeLog_keptDonate = 2131165902; public const int ChangeLog_0_9 = 2131165902;
// aapt resource value: 0x7f0702bf // aapt resource value: 0x7f0702cd
public const int ChangeLog_title = 2131165887; 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 // aapt resource value: 0x7f070112
public const int CheckForFileChangesOnSave_key = 2131165458; public const int CheckForFileChangesOnSave_key = 2131165458;
@ -3380,9 +3383,21 @@ namespace keepass2android
// aapt resource value: 0x7f070226 // aapt resource value: 0x7f070226
public const int ClearOfflineCache_title = 2131165734; 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 // aapt resource value: 0x7f070117
public const int CopyToClipboardNotification_key = 2131165463; public const int CopyToClipboardNotification_key = 2131165463;
// aapt resource value: 0x7f0702c6
public const int CopyingFile = 2131165894;
// aapt resource value: 0x7f07025d // aapt resource value: 0x7f07025d
public const int CouldNotLoadFromRemote = 2131165789; public const int CouldNotLoadFromRemote = 2131165789;
@ -3437,6 +3452,15 @@ namespace keepass2android
// aapt resource value: 0x7f070104 // aapt resource value: 0x7f070104
public const int FileHandling_prefs_key = 2131165444; 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 // aapt resource value: 0x7f07017a
public const int FileNotFound = 2131165562; public const int FileNotFound = 2131165562;
@ -3524,8 +3548,8 @@ namespace keepass2android
// aapt resource value: 0x7f070234 // aapt resource value: 0x7f070234
public const int PreloadDatabaseEnabled_title = 2131165748; public const int PreloadDatabaseEnabled_title = 2131165748;
// aapt resource value: 0x7f0702c0 // aapt resource value: 0x7f0702c8
public const int PreviewWarning = 2131165888; public const int PreviewWarning = 2131165896;
// aapt resource value: 0x7f070105 // aapt resource value: 0x7f070105
public const int QuickUnlockDefaultEnabled_key = 2131165445; public const int QuickUnlockDefaultEnabled_key = 2131165445;
@ -4169,11 +4193,11 @@ namespace keepass2android
// aapt resource value: 0x7f070145 // aapt resource value: 0x7f070145
public const int brackets = 2131165509; public const int brackets = 2131165509;
// aapt resource value: 0x7f0702d3 // aapt resource value: 0x7f0702db
public const int browser_intall_text = 2131165907; public const int browser_intall_text = 2131165915;
// aapt resource value: 0x7f0702d4 // aapt resource value: 0x7f0702dc
public const int building_search_idx = 2131165908; public const int building_search_idx = 2131165916;
// aapt resource value: 0x7f070285 // aapt resource value: 0x7f070285
public const int button_change_location = 2131165829; public const int button_change_location = 2131165829;
@ -4259,14 +4283,14 @@ namespace keepass2android
// aapt resource value: 0x7f0700ec // aapt resource value: 0x7f0700ec
public const int db_key = 2131165420; public const int db_key = 2131165420;
// aapt resource value: 0x7f0702d5 // aapt resource value: 0x7f0702dd
public const int decrypting_db = 2131165909; public const int decrypting_db = 2131165917;
// aapt resource value: 0x7f0702d6 // aapt resource value: 0x7f0702de
public const int decrypting_entry = 2131165910; public const int decrypting_entry = 2131165918;
// aapt resource value: 0x7f0702d7 // aapt resource value: 0x7f0702df
public const int default_checkbox = 2131165911; public const int default_checkbox = 2131165919;
// aapt resource value: 0x7f0700de // aapt resource value: 0x7f0700de
public const int default_file_path = 2131165406; public const int default_file_path = 2131165406;
@ -4289,8 +4313,8 @@ namespace keepass2android
// aapt resource value: 0x7f0700f3 // aapt resource value: 0x7f0700f3
public const int design_key = 2131165427; public const int design_key = 2131165427;
// aapt resource value: 0x7f0702d1 // aapt resource value: 0x7f0702d9
public const int design_title = 2131165905; public const int design_title = 2131165913;
// aapt resource value: 0x7f070152 // aapt resource value: 0x7f070152
public const int digits = 2131165522; public const int digits = 2131165522;
@ -4352,8 +4376,8 @@ namespace keepass2android
// aapt resource value: 0x7f070156 // aapt resource value: 0x7f070156
public const int entry_accessed = 2131165526; public const int entry_accessed = 2131165526;
// aapt resource value: 0x7f0702d8 // aapt resource value: 0x7f0702e0
public const int entry_and_or = 2131165912; public const int entry_and_or = 2131165920;
// aapt resource value: 0x7f070168 // aapt resource value: 0x7f070168
public const int entry_binaries = 2131165544; public const int entry_binaries = 2131165544;
@ -4409,8 +4433,8 @@ namespace keepass2android
// aapt resource value: 0x7f07028d // aapt resource value: 0x7f07028d
public const int error_adding_keyfile = 2131165837; public const int error_adding_keyfile = 2131165837;
// aapt resource value: 0x7f0702d9 // aapt resource value: 0x7f0702e1
public const int error_arc4 = 2131165913; public const int error_arc4 = 2131165921;
// aapt resource value: 0x7f070169 // aapt resource value: 0x7f070169
public const int error_can_not_handle_uri = 2131165545; public const int error_can_not_handle_uri = 2131165545;
@ -4424,8 +4448,8 @@ namespace keepass2android
// aapt resource value: 0x7f07016c // aapt resource value: 0x7f07016c
public const int error_database_exists = 2131165548; public const int error_database_exists = 2131165548;
// aapt resource value: 0x7f0702d2 // aapt resource value: 0x7f0702da
public const int error_database_settings = 2131165906; public const int error_database_settings = 2131165914;
// aapt resource value: 0x7f07016d // aapt resource value: 0x7f07016d
public const int error_database_settinoverrgs = 2131165549; public const int error_database_settinoverrgs = 2131165549;
@ -4454,8 +4478,8 @@ namespace keepass2android
// aapt resource value: 0x7f070174 // aapt resource value: 0x7f070174
public const int error_nopass = 2131165556; public const int error_nopass = 2131165556;
// aapt resource value: 0x7f0702da // aapt resource value: 0x7f0702e2
public const int error_out_of_memory = 2131165914; public const int error_out_of_memory = 2131165922;
// aapt resource value: 0x7f070175 // aapt resource value: 0x7f070175
public const int error_pass_gen_type = 2131165557; public const int error_pass_gen_type = 2131165557;
@ -4466,8 +4490,8 @@ namespace keepass2android
// aapt resource value: 0x7f070177 // aapt resource value: 0x7f070177
public const int error_rounds_not_number = 2131165559; public const int error_rounds_not_number = 2131165559;
// aapt resource value: 0x7f0702db // aapt resource value: 0x7f0702e3
public const int error_rounds_too_large = 2131165915; public const int error_rounds_too_large = 2131165923;
// aapt resource value: 0x7f07020f // aapt resource value: 0x7f07020f
public const int error_string_key = 2131165711; public const int error_string_key = 2131165711;
@ -4655,11 +4679,11 @@ namespace keepass2android
// aapt resource value: 0x7f0701d6 // aapt resource value: 0x7f0701d6
public const int insert_element_here = 2131165654; public const int insert_element_here = 2131165654;
// aapt resource value: 0x7f0702dc // aapt resource value: 0x7f0702e4
public const int install_from_market = 2131165916; public const int install_from_market = 2131165924;
// aapt resource value: 0x7f0702dd // aapt resource value: 0x7f0702e5
public const int install_from_website = 2131165917; public const int install_from_website = 2131165925;
// aapt resource value: 0x7f07018c // aapt resource value: 0x7f07018c
public const int invalid_algorithm = 2131165580; public const int invalid_algorithm = 2131165580;
@ -4874,8 +4898,8 @@ namespace keepass2android
// aapt resource value: 0x7f0701a6 // aapt resource value: 0x7f0701a6
public const int menu_hide_password = 2131165606; public const int menu_hide_password = 2131165606;
// aapt resource value: 0x7f0702de // aapt resource value: 0x7f0702e6
public const int menu_homepage = 2131165918; public const int menu_homepage = 2131165926;
// aapt resource value: 0x7f0701a7 // aapt resource value: 0x7f0701a7
public const int menu_lock = 2131165607; public const int menu_lock = 2131165607;

View File

@ -475,6 +475,17 @@
<string name="killed_by_os">Sorry! Keepass2Android was killed by the Android OS! For security reasons, Keepass2Android did not persist your selected credentials on disk, so you need to re-open your database. Note: This should happen only very rarely. If it does, please drop me a message at crocoapps@gmail.com.</string> <string name="killed_by_os">Sorry! Keepass2Android was killed by the Android OS! For security reasons, Keepass2Android did not persist your selected credentials on disk, so you need to re-open your database. Note: This should happen only very rarely. If it does, please drop me a message at crocoapps@gmail.com.</string>
<string name="FileIsTemporarilyAvailable">The file is only temporarily available for Keepass2Android.</string>
<string name="FileIsReadOnly">The file you selected is read-only.</string>
<string name="FileIsReadOnlyOnKitkat">The file you selected is read-only for Keepass2Android due to restrictions on Android 4.4+.</string>
<string name="CopyFileRequired">To use it, you must copy it to another location.</string>
<string name="CopyFileRequiredForEditing">To edit it, you must copy the file to another location.</string>
<string name="ClickOkToSelectLocation">Click OK to select a location where the file should be copied.</string>
<string name="CancelReadOnly">Cancel, open read-only.</string>
<string name="CopyingFile">Copying file...</string>
<string name="ChangeLog_title">Change log</string> <string name="ChangeLog_title">Change log</string>
<string name="PreviewWarning">Please note! This is a preview release and might come with some flaws! If you experience *anything* unexpected, please let me know (on Codeplex or by email).</string> <string name="PreviewWarning">Please note! This is a preview release and might come with some flaws! If you experience *anything* unexpected, please let me know (on Codeplex or by email).</string>

View File

@ -9,6 +9,7 @@ using Android.OS;
using Android.Runtime; using Android.Runtime;
using Android.Views; using Android.Views;
using Android.Widget; using Android.Widget;
using Group.Pals.Android.Lib.UI.Filechooser.Utils.UI;
using KeePassLib.Serialization; using KeePassLib.Serialization;
using keepass2android.Io; using keepass2android.Io;
using Environment = Android.OS.Environment; using Environment = Android.OS.Environment;
@ -16,11 +17,24 @@ using Environment = Android.OS.Environment;
namespace keepass2android namespace keepass2android
{ {
[Activity(Label = "")] [Activity(Label = "")]
public class SelectStorageLocationActivity : Activity public class SelectStorageLocationActivity : Activity, IDialogInterfaceOnDismissListener
{ {
private ActivityDesign _design; private ActivityDesign _design;
private bool _isRecreated; 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() public SelectStorageLocationActivity()
{ {
@ -36,8 +50,8 @@ namespace keepass2android
Kp2aLog.Log("SelectStorageLocationActivity.OnCreate"); 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."); throw new Exception("save is not yet implemented. In StartSelectFile, no handler for onCreate is passed.");
} }
@ -49,6 +63,9 @@ namespace keepass2android
else else
{ {
State = (Bundle)bundle.Clone(); State = (Bundle)bundle.Clone();
var selectedIocString = bundle.GetString(BundleKeySelectedIoc, null);
if (selectedIocString != null)
_selectedIoc = IOConnectionInfo.UnserializeFromString(selectedIocString);
_isRecreated = true; _isRecreated = true;
} }
@ -57,7 +74,7 @@ namespace keepass2android
Intent intent = new Intent(this, typeof(FileStorageSelectionActivity)); Intent intent = new Intent(this, typeof(FileStorageSelectionActivity));
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, allowThirdPartyGet); intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, allowThirdPartyGet);
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, allowThirdPartySend); intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, allowThirdPartySend);
StartActivityForResult(intent, RequestCodeFileStorageSelection); StartActivityForResult(intent, RequestCodeFileStorageSelectionForPrimarySelect);
} }
@ -65,7 +82,10 @@ namespace keepass2android
protected Bundle State { get; set; } 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) protected override void OnSaveInstanceState(Bundle outState)
@ -73,6 +93,8 @@ namespace keepass2android
base.OnSaveInstanceState(outState); base.OnSaveInstanceState(outState);
outState.PutAll(State); outState.PutAll(State);
if (_selectedIoc != null)
outState.PutString(BundleKeySelectedIoc, IOConnectionInfo.SerializeToString(_selectedIoc));
} }
protected override void OnResume() protected override void OnResume()
@ -84,8 +106,14 @@ namespace keepass2android
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{ {
base.OnActivityResult(requestCode, resultCode, 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) if (resultCode == KeePass.ExitFileStorageSelectionOk)
{ {
@ -97,31 +125,47 @@ namespace keepass2android
} }
else else
{ {
bool isForSave = (requestCode == RequestCodeFileStorageSelectionForPrimarySelect) ?
IsStorageSelectionForSave : true;
App.Kp2a.GetFileStorage(protocolId).StartSelectFile(new FileStorageSetupInitiatorActivity(this, App.Kp2a.GetFileStorage(protocolId).StartSelectFile(new FileStorageSetupInitiatorActivity(this,
OnActivityResult, OnActivityResult,
defaultPath => defaultPath =>
{ {
if (defaultPath.StartsWith("sftp://")) if (defaultPath.StartsWith("sftp://"))
Util.ShowSftpDialog(this, OnReceivedSftpData, ReturnCancel); Util.ShowSftpDialog(this, filename => OnReceivedSftpData(filename, browseRequestCode, isForSave), ReturnCancel);
else else
Util.ShowFilenameDialog(this, OnOpenButton, null, ReturnCancel, false, defaultPath, GetString(Resource.String.enter_filename_details_url), //todo oncreate nur wenn for save?
Intents.RequestCodeFileBrowseForOpen); 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 else
{
ReturnCancel();
}
}
if ((requestCode == Intents.RequestCodeFileBrowseForOpen) || (requestCode == RequestCodeFileFileBrowseForWritableLocation))
{ {
if (resultCode == (Result)FileStorageResults.FileChooserPrepared) if (resultCode == (Result)FileStorageResults.FileChooserPrepared)
{ {
IOConnectionInfo ioc = new IOConnectionInfo(); IOConnectionInfo ioc = new IOConnectionInfo();
PasswordActivity.SetIoConnectionFromIntent(ioc, data); PasswordActivity.SetIoConnectionFromIntent(ioc, data);
#if !EXCLUDE_FILECHOOSER #if !EXCLUDE_FILECHOOSER
StartFileChooser(ioc.Path); bool isForSave = (requestCode == RequestCodeFileFileBrowseForWritableLocation) ?
true : IsStorageSelectionForSave ;
StartFileChooser(ioc.Path, requestCode, isForSave);
#else #else
ReturnIoc(new IOConnectionInfo { Path = "/mnt/sdcard/keepass/yubi.kdbx" }); IocSelected(new IOConnectionInfo { Path = "/mnt/sdcard/keepass/yubi.kdbx" }, requestCode);
#endif #endif
return; return;
} }
@ -129,13 +173,7 @@ namespace keepass2android
{ {
Toast.MakeText(this, data.GetStringExtra("EXTRA_ERROR_MESSAGE"), ToastLength.Long).Show(); Toast.MakeText(this, data.GetStringExtra("EXTRA_ERROR_MESSAGE"), ToastLength.Long).Show();
} }
ReturnCancel();
}
}
if (requestCode == Intents.RequestCodeFileBrowseForOpen)
{
if (resultCode == Result.Ok) if (resultCode == Result.Ok)
{ {
string filename = Util.IntentToFilename(data, this); string filename = Util.IntentToFilename(data, this);
@ -152,13 +190,13 @@ namespace keepass2android
Path = filename Path = filename
}; };
ReturnIoc(ioc); IocSelected(ioc, requestCode);
} }
else else
{ {
if (data.Data.Scheme == "content") if (data.Data.Scheme == "content")
{ {
ReturnIoc(IOConnectionInfo.FromPath(data.DataString)); IocSelected(IOConnectionInfo.FromPath(data.DataString), requestCode);
} }
else else
@ -189,28 +227,154 @@ namespace keepass2android
Finish(); 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(); Intent intent = new Intent();
PasswordActivity.PutIoConnectionToIntent(ioc, intent); PasswordActivity.PutIoConnectionToIntent(ioc, intent);
SetResult(Result.Ok, intent); SetResult(Result.Ok, intent);
Finish(); 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 }; IOConnectionInfo ioc = new IOConnectionInfo { Path = filename };
#if !EXCLUDE_FILECHOOSER #if !EXCLUDE_FILECHOOSER
StartFileChooser(ioc.Path); StartFileChooser(ioc.Path, requestCode, isForSave);
#else #else
ReturnIoc(ioc); IocSelected(ioc, requestCode);
#endif #endif
return true; return true;
} }
#if !EXCLUDE_FILECHOOSER #if !EXCLUDE_FILECHOOSER
private void StartFileChooser(string defaultPath) private void StartFileChooser(string defaultPath, int requestCode, bool forSave)
{ {
Kp2aLog.Log("FSA: defaultPath="+defaultPath); Kp2aLog.Log("FSA: defaultPath="+defaultPath);
string fileProviderAuthority = FileChooserFileProvider.TheAuthority; string fileProviderAuthority = FileChooserFileProvider.TheAuthority;
@ -221,24 +385,35 @@ namespace keepass2android
Intent i = Keepass2android.Kp2afilechooser.Kp2aFileChooserBridge.GetLaunchFileChooserIntent(this, fileProviderAuthority, Intent i = Keepass2android.Kp2afilechooser.Kp2aFileChooserBridge.GetLaunchFileChooserIntent(this, fileProviderAuthority,
defaultPath); 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 #endif
private bool OnOpenButton(String fileName) private bool OnOpenButton(String fileName, int requestCode)
{ {
IOConnectionInfo ioc = new IOConnectionInfo IOConnectionInfo ioc = new IOConnectionInfo
{ {
Path = fileName Path = fileName
}; };
ReturnIoc(ioc); IocSelected(ioc, requestCode);
return true; return true;
} }
public void OnDismiss(IDialogInterface dialog)
{
// ReturnCancel();
}
} }

View File

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

View File

@ -267,10 +267,29 @@ namespace keepass2android
} }
} }
class CancelListener: Java.Lang.Object, IDialogInterfaceOnCancelListener
{
private readonly Action _onCancel;
public CancelListener(Action onCancel)
{
_onCancel = onCancel;
}
public void OnCancel(IDialogInterface dialog)
{
_onCancel();
}
}
public static void ShowFilenameDialog(Activity activity, FileSelectedHandler onOpen, FileSelectedHandler onCreate, Action onCancel, bool showBrowseButton, string defaultFilename, string detailsText, int requestCodeBrowse) public static void ShowFilenameDialog(Activity activity, FileSelectedHandler onOpen, FileSelectedHandler onCreate, Action onCancel, bool showBrowseButton, string defaultFilename, string detailsText, int requestCodeBrowse)
{ {
AlertDialog.Builder builder = new AlertDialog.Builder(activity); AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.SetView(activity.LayoutInflater.Inflate(Resource.Layout.file_selection_filename, null)); builder.SetView(activity.LayoutInflater.Inflate(Resource.Layout.file_selection_filename, null));
if (onCancel != null)
builder.SetOnCancelListener(new CancelListener(onCancel));
Dialog dialog = builder.Create(); Dialog dialog = builder.Create();
dialog.Show(); dialog.Show();
@ -308,11 +327,13 @@ namespace keepass2android
cancelButton.Click += delegate cancelButton.Click += delegate
{ {
dialog.Dismiss(); dialog.Dismiss();
if (onCancel != null)
onCancel();
}; };
if (onCancel != null)
dialog.SetOnDismissListener(new DismissListener(onCancel));
ImageButton browseButton = (ImageButton) dialog.FindViewById(Resource.Id.browse_button); ImageButton browseButton = (ImageButton) dialog.FindViewById(Resource.Id.browse_button);
@ -324,7 +345,7 @@ namespace keepass2android
{ {
string filename = ((EditText) dialog.FindViewById(Resource.Id.file_filename)).Text; string filename = ((EditText) dialog.FindViewById(Resource.Id.file_filename)).Text;
Util.ShowBrowseDialog(activity, requestCodeBrowse, onCreate != null); ShowBrowseDialog(activity, requestCodeBrowse, onCreate != null);
}; };

View File

@ -133,6 +133,7 @@ namespace keepass2android
Intent intent = new Intent(this, typeof(SelectStorageLocationActivity)); Intent intent = new Intent(this, typeof(SelectStorageLocationActivity));
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, true); intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, true);
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, false); intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, false);
intent.PutExtra(SelectStorageLocationActivity.ExtraKeyWritableRequirements, (int) SelectStorageLocationActivity.WritableRequirements.WriteDesired);
intent.PutExtra(FileStorageSetupDefs.ExtraIsForSave, false); intent.PutExtra(FileStorageSetupDefs.ExtraIsForSave, false);
StartActivityForResult(intent, RequestCodeSelectIoc); StartActivityForResult(intent, RequestCodeSelectIoc);

View File

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