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>
<Optimize>False</Optimize>
<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>
<WarningLevel>4</WarningLevel>
<ConsolePause>False</ConsolePause>

View File

@ -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

View File

@ -4,6 +4,7 @@ using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security;
using Android.Content;
using Android.OS;
using Java.Security.Cert;
@ -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;
}
}
}
}

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

View File

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

View File

@ -20,7 +20,7 @@
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<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>
<WarningLevel>4</WarningLevel>
</PropertyGroup>

View File

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

View File

@ -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<Type> { typeof(TestBuiltInFileStorage) });
//runner.AddTests(new List<Type> { typeof(TestSynchronizeCachedDatabase)});
//runner.AddTests(typeof(TestLoadDb).GetMethod("LoadErrorWithCertificateTrustFailure"));
//runner.AddTests(typeof(TestLoadDb).GetMethod("LoadWithAcceptedCertificateTrustFailure"));

View File

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

View File

@ -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

View File

@ -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;

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="FileIsTemporarilyAvailable">The file is only temporarily available for Keepass2Android.</string>
<string name="FileIsReadOnly">The file you selected is read-only.</string>
<string name="FileIsReadOnlyOnKitkat">The file you selected is read-only for Keepass2Android due to restrictions on Android 4.4+.</string>
<string name="CopyFileRequired">To use it, you must copy it to another location.</string>
<string name="CopyFileRequiredForEditing">To edit it, you must copy the file to another location.</string>
<string name="ClickOkToSelectLocation">Click OK to select a location where the file should be copied.</string>
<string name="CancelReadOnly">Cancel, open read-only.</string>
<string name="CopyingFile">Copying file...</string>
<string name="ChangeLog_title">Change log</string>
<string name="PreviewWarning">Please note! This is a preview release and might come with some flaws! If you experience *anything* unexpected, please let me know (on Codeplex or by email).</string>

View File

@ -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();
}
}

View File

@ -9,13 +9,13 @@ using Object = Java.Lang.Object;
namespace keepass2android.Utils
{
public class LoadingDialog<TParams, TProgress, TResult> : AsyncTask<TParams, TProgress, TResult>
public class LoadingDialog<TParams, TProgress, TResult> : AsyncTask<TParams, TProgress, TResult>
{
private readonly Context _context;
private readonly string _message;
private readonly bool _cancelable;
readonly Func<Object[], Object> _doInBackground;
readonly Action<Object> _onPostExecute;
private readonly Func<Object[], Object> _doInBackground;
private readonly Action<Object> _onPostExecute;
private ProgressDialog mDialog;
/**
@ -29,13 +29,14 @@ namespace keepass2android.Utils
private Exception mLastException;
public LoadingDialog(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
public LoadingDialog(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
}
public LoadingDialog(Context context, string message, bool cancelable, Func<Object[], Object> doInBackground,
Action<Object> onPostExecute)
public LoadingDialog(Context context, string message, bool cancelable, Func<Object[], Object> doInBackground,
Action<Object> onPostExecute)
{
_context = context;
_message = message;
@ -58,7 +59,8 @@ namespace keepass2android.Utils
}
}
public LoadingDialog(Context context, bool cancelable, Func<Object[], Object> doInBackground, Action<Object> onPostExecute)
public LoadingDialog(Context context, bool cancelable, Func<Object[], Object> doInBackground,
Action<Object> onPostExecute)
{
_message = context.GetString(Resource.String.loading);
_context = context;
@ -89,32 +91,41 @@ namespace keepass2android.Utils
}
}
, mDelayTime);
, mDelayTime);
}
/**
* If you override this method, you must call {@code super.onCancelled()} at
* beginning of the method.
*/
protected override void OnCancelled() {
protected override void OnCancelled()
{
DoFinish();
base.OnCancelled();
}// onCancelled()
}
private void DoFinish() {
// onCancelled()
private void DoFinish()
{
mFinished = true;
try {
try
{
/*
* Sometime the activity has been finished before we dismiss this
* dialog, it will raise error.
*/
mDialog.Dismiss();
} catch (Exception e)
}
catch (Exception e)
{
Kp2aLog.Log(e.ToString());
}
}// doFinish()
}
// doFinish()
/**
@ -124,18 +135,26 @@ namespace keepass2android.Utils
* @param t
* {@link Throwable}
*/
protected void SetLastException(Exception e) {
protected void SetLastException(Exception e)
{
mLastException = e;
}// setLastException()
}
// setLastException()
/**
* Gets last exception.
*
* @return {@link Throwable}
*/
protected Exception GetLastException() {
protected Exception GetLastException()
{
return mLastException;
}// getLastException()
}
// getLastException()
protected override Object DoInBackground(params Object[] @params)
@ -151,14 +170,47 @@ namespace keepass2android.Utils
protected override void OnPostExecute(Object result)
{
DoFinish();
if (_onPostExecute != null)
_onPostExecute(result);
}
}
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)
{
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.SetView(activity.LayoutInflater.Inflate(Resource.Layout.file_selection_filename, null));
if (onCancel != null)
builder.SetOnCancelListener(new CancelListener(onCancel));
Dialog dialog = builder.Create();
dialog.Show();
@ -308,11 +327,13 @@ namespace keepass2android
cancelButton.Click += delegate
{
dialog.Dismiss();
if (onCancel != null)
onCancel();
};
if (onCancel != null)
dialog.SetOnDismissListener(new DismissListener(onCancel));
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;
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.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, true);
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, false);
intent.PutExtra(SelectStorageLocationActivity.ExtraKeyWritableRequirements, (int) SelectStorageLocationActivity.WritableRequirements.WriteDesired);
intent.PutExtra(FileStorageSetupDefs.ExtraIsForSave, false);
StartActivityForResult(intent, RequestCodeSelectIoc);

View File

@ -30,7 +30,7 @@
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<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>
<WarningLevel>4</WarningLevel>
<ConsolePause>False</ConsolePause>