From 64e265b2be91c92403b87c0c402f21465b32f2bc Mon Sep 17 00:00:00 2001 From: Philipp Crocoll Date: Wed, 13 Jan 2016 05:13:02 +0100 Subject: [PATCH] display reason why file is read only when first opening a database --- .../Io/AndroidContentStorage.cs | 12 +++++++-- .../Io/BuiltInFileStorage.cs | 12 ++++++++- .../Io/CachingFileStorage.cs | 4 +-- src/Kp2aBusinessLogic/Io/IFileStorage.cs | 11 +++++++- src/Kp2aBusinessLogic/Io/JavaFileStorage.cs | 2 +- .../Io/OfflineSwitchableFileStorage.cs | 4 +-- src/Kp2aBusinessLogic/UiStringKey.cs | 5 +++- src/Kp2aBusinessLogic/database/Database.cs | 16 ++++++++++-- src/keepass2android/GroupActivity.cs | 26 ++++++++++++++++++- .../Resources/values/strings.xml | 7 +++++ 10 files changed, 86 insertions(+), 13 deletions(-) diff --git a/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs b/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs index 5bf85f3e..893b250b 100644 --- a/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs +++ b/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs @@ -206,12 +206,15 @@ namespace keepass2android.Io return _ctx.ContentResolver.PersistedUriPermissions.Any(p => p.Uri.ToString().Equals(ioc.Path)); } - public bool IsReadOnly(IOConnectionInfo ioc) + + public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null) { //on pre-Kitkat devices, we can't write content:// files if (!IsKitKatOrLater) { Kp2aLog.Log("File is read-only because we're not on KitKat or later."); + if (reason != null) + reason.Result = UiStringKey.ReadOnlyReason_PreKitKat; return true; } @@ -226,7 +229,12 @@ namespace keepass2android.Io { int flags = cursor.GetInt(cursor.GetColumnIndex(DocumentsContract.Document.ColumnFlags)); Kp2aLog.Log("File flags: " + flags); - return (flags & (long) DocumentContractFlags.SupportsWrite) == 0; + if ((flags & (long) DocumentContractFlags.SupportsWrite) == 0) + { + if (reason != null) + reason.Result = UiStringKey.ReadOnlyReason_ReadOnlyFlag; + return true; + } } } finally diff --git a/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs b/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs index 45a7e010..7481da6f 100644 --- a/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs @@ -370,14 +370,24 @@ namespace keepass2android.Io } - public bool IsReadOnly(IOConnectionInfo ioc) + public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null) { if (ioc.IsLocalFile()) { if (IsLocalFileFlaggedReadOnly(ioc)) + { + if (reason != null) + reason.Result = UiStringKey.ReadOnlyReason_ReadOnlyFlag; return true; + } + if (IsReadOnlyBecauseKitkatRestrictions(ioc)) + { + if (reason != null) + reason.Result = UiStringKey.ReadOnlyReason_ReadOnlyKitKat; return true; + } + return false; } diff --git a/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs b/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs index 890751be..e9735f83 100644 --- a/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs @@ -556,11 +556,11 @@ namespace keepass2android.Io return _cachedStorage.IsPermanentLocation(ioc); } - public bool IsReadOnly(IOConnectionInfo ioc) + public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null) { //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); + return _cachedStorage.IsReadOnly(ioc, reason); } private void StoreFilePath(IOConnectionInfo folderPath, string filename, IOConnectionInfo res) diff --git a/src/Kp2aBusinessLogic/Io/IFileStorage.cs b/src/Kp2aBusinessLogic/Io/IFileStorage.cs index 1ffc6ece..31d2b9c1 100644 --- a/src/Kp2aBusinessLogic/Io/IFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/IFileStorage.cs @@ -15,6 +15,11 @@ namespace keepass2android.Io FileChooserPrepared = FullFilenameSelected + 1, FileUsagePrepared = FileChooserPrepared + 1 } + + public class OptionalOut + { + public T Result { get; set; } + } public static class FileStorageSetupDefs { @@ -165,10 +170,14 @@ namespace keepass2android.Io /// Does not require to exist forever! bool IsPermanentLocation(IOConnectionInfo ioc); + + /// /// Should return true if the file cannot be written. /// - bool IsReadOnly(IOConnectionInfo ioc); + bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null ); + + } public interface IPermissionRequestingFileStorage diff --git a/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs b/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs index 4ec59226..fa584281 100644 --- a/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs @@ -289,7 +289,7 @@ namespace keepass2android.Io return true; } - public bool IsReadOnly(IOConnectionInfo ioc) + public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null) { return false; //TODO implement. note, however, that we MAY return false even if it's read-only } diff --git a/src/Kp2aBusinessLogic/Io/OfflineSwitchableFileStorage.cs b/src/Kp2aBusinessLogic/Io/OfflineSwitchableFileStorage.cs index 70f9529b..bf93653e 100644 --- a/src/Kp2aBusinessLogic/Io/OfflineSwitchableFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/OfflineSwitchableFileStorage.cs @@ -176,9 +176,9 @@ namespace keepass2android.Io return _baseStorage.IsPermanentLocation(ioc); } - public bool IsReadOnly(IOConnectionInfo ioc) + public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null) { - return _baseStorage.IsReadOnly(ioc); + return _baseStorage.IsReadOnly(ioc, reason); } public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode, diff --git a/src/Kp2aBusinessLogic/UiStringKey.cs b/src/Kp2aBusinessLogic/UiStringKey.cs index 58e6f989..8cf39166 100644 --- a/src/Kp2aBusinessLogic/UiStringKey.cs +++ b/src/Kp2aBusinessLogic/UiStringKey.cs @@ -81,6 +81,9 @@ namespace keepass2android TemplateTitle_Membership, TemplateGroupName, AskAddTemplatesTitle, - AskAddTemplatesMessage + AskAddTemplatesMessage, + ReadOnlyReason_PreKitKat, + ReadOnlyReason_ReadOnlyFlag, + ReadOnlyReason_ReadOnlyKitKat } } diff --git a/src/Kp2aBusinessLogic/database/Database.cs b/src/Kp2aBusinessLogic/database/Database.cs index f25b0f12..7714055c 100644 --- a/src/Kp2aBusinessLogic/database/Database.cs +++ b/src/Kp2aBusinessLogic/database/Database.cs @@ -153,12 +153,24 @@ namespace keepass2android public static string GetFingerprintPrefKey(IOConnectionInfo ioc) { - SHA256Managed sha256 = new SHA256Managed(); - string iocAsHexString = MemUtil.ByteArrayToHexString(sha256.ComputeHash(Encoding.Unicode.GetBytes(ioc.Path.ToCharArray()))); + var iocAsHexString = IocAsHexString(ioc); return "kp2a_ioc_" + iocAsHexString; } + public string IocAsHexString() + { + return IocAsHexString(Ioc); + } + + private static string IocAsHexString(IOConnectionInfo ioc) + { + SHA256Managed sha256 = new SHA256Managed(); + string iocAsHexString = + MemUtil.ByteArrayToHexString(sha256.ComputeHash(Encoding.Unicode.GetBytes(ioc.Path.ToCharArray()))); + return iocAsHexString; + } + public static string GetFingerprintModePrefKey(IOConnectionInfo ioc) { return GetFingerprintPrefKey(ioc) + "_mode"; diff --git a/src/keepass2android/GroupActivity.cs b/src/keepass2android/GroupActivity.cs index 84ec9c29..5ae6be4c 100644 --- a/src/keepass2android/GroupActivity.cs +++ b/src/keepass2android/GroupActivity.cs @@ -35,6 +35,7 @@ using Android.Preferences; using Android.Runtime; using Android.Support.V4.View; using Android.Support.V7.App; +using keepass2android.Io; using KeePassLib.Security; using AlertDialog = Android.App.AlertDialog; using Object = Java.Lang.Object; @@ -255,9 +256,32 @@ namespace keepass2android SetGroupTitle(); SetGroupIcon(); - + FragmentManager.FindFragmentById(Resource.Id.list_fragment).ListAdapter = new PwGroupListAdapter(this, Group); Log.Warn(Tag, "Finished creating group"); + + var ioc = App.Kp2a.GetDb().Ioc; + OptionalOut reason = new OptionalOut(); + + if (App.Kp2a.GetFileStorage(ioc).IsReadOnly(ioc, reason)) + { + bool hasShownReadOnlyReason = + PreferenceManager.GetDefaultSharedPreferences(this) + .GetBoolean(App.Kp2a.GetDb().IocAsHexString() + "_readonlyreason", false); + if (!hasShownReadOnlyReason) + { + var b = new AlertDialog.Builder(this); + b.SetTitle(Resource.String.FileReadOnlyTitle); + b.SetMessage(GetString(Resource.String.FileReadOnlyMessagePre) + " " + App.Kp2a.GetResourceString(reason.Result)); + b.SetPositiveButton(Android.Resource.String.Ok, + (sender, args) => + { + PreferenceManager.GetDefaultSharedPreferences(this). + Edit().PutBoolean(App.Kp2a.GetDb().IocAsHexString() + "_readonlyreason", true).Commit(); + }); + b.Show(); + } + } } diff --git a/src/keepass2android/Resources/values/strings.xml b/src/keepass2android/Resources/values/strings.xml index d99a81f3..b0d301a5 100644 --- a/src/keepass2android/Resources/values/strings.xml +++ b/src/keepass2android/Resources/values/strings.xml @@ -573,6 +573,13 @@ Click OK to select a location where the file should be copied. Cancel, open read-only. + Database is read-only + Keepass2Android has opened the current database in read-only mode. + + It seems like you opened the file from an external app. This way does not support writing. If you want to make changes to the database, please close the database and select Change database. Then open the file from one of the available options if possible. + The read-only flag is set. Remove this flag if you want to make changes to the database. + Writing is not possible because of restrictions introduced in Android KitKat. If you want to make changes to the database, close the database and select Change database. Then open the file using System file picker. + Add icon from file... Copying file...