diff --git a/src/Kp2aBusinessLogic/Io/NetFtpFileStorage.cs b/src/Kp2aBusinessLogic/Io/NetFtpFileStorage.cs index 11ef3824..06a2b115 100644 --- a/src/Kp2aBusinessLogic/Io/NetFtpFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/NetFtpFileStorage.cs @@ -292,8 +292,20 @@ namespace keepass2android.Io { using (var client = GetClient(ioc)) { + /* + * For some reason GetListing(path) does not always return the contents of the directory. + * However, calling SetWorkingDirectory(path) followed by GetListing(null, options) to + * list the contents of the working directory does consistently work. + * + * Similar behavior was confirmed using ncftp client. I suspect this is a strange + * bug/nuance in the server's implementation of the LIST command? + * + * [bug #2423] + */ + client.SetWorkingDirectory(IocToLocalPath(ioc)); + List files = new List(); - foreach (FtpListItem item in client.GetListing(IocToLocalPath(ioc), + foreach (FtpListItem item in client.GetListing(null, FtpListOption.SizeModify | FtpListOption.AllFiles)) { switch (item.Type) diff --git a/src/keepass2android/EntryActivity.cs b/src/keepass2android/EntryActivity.cs index ecc10dd6..868453d6 100644 --- a/src/keepass2android/EntryActivity.cs +++ b/src/keepass2android/EntryActivity.cs @@ -48,6 +48,8 @@ using KeePassLib.Serialization; using PluginTOTP; using File = Java.IO.File; using Uri = Android.Net.Uri; +using keepass2android.fileselect; +using Boolean = Java.Lang.Boolean; namespace keepass2android { @@ -554,21 +556,90 @@ namespace keepass2android } } - - internal void StartNotificationsService(bool activateKeyboard) - { - Intent showNotIntent = new Intent(this, typeof (CopyToClipboardService)); - showNotIntent.SetAction(Intents.ShowNotification); - showNotIntent.PutExtra(KeyEntry, new ElementAndDatabaseId(App.Kp2a.CurrentDb, Entry).FullId); - AppTask.PopulatePasswordAccessServiceIntent(showNotIntent); - showNotIntent.PutExtra(KeyActivateKeyboard, activateKeyboard); + public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults) + { + if (permissions.Length == 1 && permissions.First() == Android.Manifest.Permission.PostNotifications && + grantResults.First() == Permission.Granted) + { + StartNotificationsServiceAfterPermissionsCheck(requestCode == 1 /*requestCode is used to transfer this flag*/); + } - StartService(showNotIntent); - } + base.OnRequestPermissionsResult(requestCode, permissions, grantResults); + } + internal void StartNotificationsService(bool activateKeyboard) + { + if (PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean( + GetString(Resource.String.CopyToClipboardNotification_key), + Resources.GetBoolean(Resource.Boolean.CopyToClipboardNotification_default)) == false + && PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean( + GetString(Resource.String.UseKp2aKeyboard_key), + Resources.GetBoolean(Resource.Boolean.UseKp2aKeyboard_default)) == false) + { + //notifications are disabled + return; + } + + if ((int)Build.VERSION.SdkInt < 33 || CheckSelfPermission(Android.Manifest.Permission.PostNotifications) == + Permission.Granted) + { + StartNotificationsServiceAfterPermissionsCheck(activateKeyboard); + return; + } + + //user has not yet granted Android 13's POST_NOTIFICATONS permission for the app. + + //check if we should ask them to grant: + if (!ShouldShowRequestPermissionRationale(Android.Manifest.Permission.PostNotifications) //this menthod returns false if we haven't asked yet or if the user has denied permission too often + && PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean("RequestedPostNotificationsPermission", false))//use a preference to tell the difference between "haven't asked yet" and "have asked too often" + { + //user has denied permission before. Do not show the dialog. User must give permission in the Android App settings. + return; + } + + new AlertDialog.Builder(this) + .SetTitle(Resource.String.post_notifications_dialog_title) + .SetMessage(Resource.String.post_notifications_dialog_message) + .SetNegativeButton(Resource.String.post_notifications_dialog_disable, (sender, args) => + { + //disable this dialog for the future by disabling the notification preferences + var edit= PreferenceManager.GetDefaultSharedPreferences(this).Edit(); + edit.PutBoolean(GetString(Resource.String.CopyToClipboardNotification_key), false); + edit.PutBoolean(GetString(Resource.String.UseKp2aKeyboard_key), false); + edit.Commit(); + }) + .SetPositiveButton(Resource.String.post_notifications_dialog_allow, (sender, args) => + { + + //remember that we did ask for permission at least once: + var edit = PreferenceManager.GetDefaultSharedPreferences(this).Edit(); + edit.PutBoolean("RequestedPostNotificationsPermission", true); + edit.Commit(); + + //request permission. user must grant, we'll show notifications in the OnRequestPermissionResults() callback + Android.Support.V4.App.ActivityCompat.RequestPermissions(this, new[] { Android.Manifest.Permission.PostNotifications }, activateKeyboard ? 1 : 0 /*use requestCode to transfer the flag*/); - private String getDateTime(DateTime dt) + }) + .SetNeutralButton(Resource.String.post_notifications_dialog_notnow, (sender, args) => { }) + .Show(); + + + } + + private void StartNotificationsServiceAfterPermissionsCheck(bool activateKeyboard) + { + Intent showNotIntent = new Intent(this, typeof(CopyToClipboardService)); + showNotIntent.SetAction(Intents.ShowNotification); + showNotIntent.PutExtra(KeyEntry, new ElementAndDatabaseId(App.Kp2a.CurrentDb, Entry).FullId); + AppTask.PopulatePasswordAccessServiceIntent(showNotIntent); + showNotIntent.PutExtra(KeyActivateKeyboard, activateKeyboard); + + StartService(showNotIntent); + } + + + private String getDateTime(DateTime dt) { return dt.ToLocalTime().ToString("g", CultureInfo.CurrentUICulture); } diff --git a/src/keepass2android/GroupBaseActivity.cs b/src/keepass2android/GroupBaseActivity.cs index e0bdca09..0352100c 100644 --- a/src/keepass2android/GroupBaseActivity.cs +++ b/src/keepass2android/GroupBaseActivity.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Linq; using Android.App; using Android.Content; +using Android.Content.PM; using Android.OS; using Android.Runtime; using Android.Views; @@ -57,8 +58,9 @@ namespace keepass2android { Resource.Id.child_db_infotext, 13 }, { Resource.Id.fingerprint_infotext, 12 }, { Resource.Id.autofill_infotext, 11 }, - { Resource.Id.notification_info_android8_infotext, 10 }, - { Resource.Id.infotext, 9 }, + { Resource.Id.notification_permission_infotext, 10 }, + { Resource.Id.notification_info_android8_infotext, 9 }, + { Resource.Id.infotext, 8 }, { Resource.Id.select_other_entry, 20}, { Resource.Id.add_url_entry, 20}, }; @@ -273,6 +275,7 @@ namespace keepass2android UpdateFingerprintInfo(); UpdateAutofillInfo(); UpdateAndroid8NotificationInfo(); + UpdatePostNotificationsPermissionInfo(); UpdateInfotexts(); RefreshIfDirty(); @@ -280,6 +283,7 @@ namespace keepass2android SetSearchItemVisibility(); } + private void UpdateInfotexts() { @@ -385,6 +389,31 @@ namespace keepass2android hasCalledOtherActivity = false; } + + private void UpdatePostNotificationsPermissionInfo(bool hideForever=false) + { + const string prefsKey = "DidShowNotificationPermissionInfo"; + + bool canShowNotificationInfo = ((int)Build.VERSION.SdkInt >= 33) + && (!_prefs.GetBoolean(prefsKey, false) + && (CheckSelfPermission(Android.Manifest.Permission.PostNotifications) != + Permission.Granted) + && (ShouldShowRequestPermissionRationale(Android.Manifest.Permission.PostNotifications) //this menthod returns false if we haven't asked yet or if the user has denied permission too often + || !PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean("RequestedPostNotificationsPermission", false))//use a preference to tell the difference between "haven't asked yet" and "have asked too often" + ); + if ((canShowNotificationInfo) && hideForever) + { + _prefs.Edit().PutBoolean(prefsKey, true).Commit(); + canShowNotificationInfo = false; + } + if (canShowNotificationInfo) + { + RegisterInfoTextDisplay("NotificationPermissionInfo"); //this ensures that we don't show the general info texts too soon + } + UpdateBottomBarElementVisibility(Resource.Id.notification_permission_infotext, canShowNotificationInfo); + + } + private void UpdateAndroid8NotificationInfo(bool hideForever = false) { const string prefsKey = "DidShowAndroid8NotificationInfo"; @@ -606,6 +635,25 @@ namespace keepass2android } + if (FindViewById(Resource.Id.post_notification_button_allow) != null) + { + FindViewById(Resource.Id.post_notification_button_allow).Click += (sender, args) => + { + //remember that we did ask for permission at least once: + var edit = PreferenceManager.GetDefaultSharedPreferences(this).Edit(); + edit.PutBoolean("RequestedPostNotificationsPermission", true); + edit.Commit(); + + Android.Support.V4.App.ActivityCompat.RequestPermissions(this, new[] { Android.Manifest.Permission.PostNotifications }, 0); + UpdatePostNotificationsPermissionInfo(true); + }; + FindViewById(Resource.Id.post_notification_button_dont_show_again).Click += (sender, args) => + { + UpdatePostNotificationsPermissionInfo(true); + }; + + } + diff --git a/src/keepass2android/Properties/AndroidManifest_debug.xml b/src/keepass2android/Properties/AndroidManifest_debug.xml index 873fdc32..53d4914f 100644 --- a/src/keepass2android/Properties/AndroidManifest_debug.xml +++ b/src/keepass2android/Properties/AndroidManifest_debug.xml @@ -43,7 +43,7 @@ - + + diff --git a/src/keepass2android/Properties/AndroidManifest_net.xml b/src/keepass2android/Properties/AndroidManifest_net.xml index f79550f7..1c3c93ab 100644 --- a/src/keepass2android/Properties/AndroidManifest_net.xml +++ b/src/keepass2android/Properties/AndroidManifest_net.xml @@ -42,8 +42,8 @@ + - @@ -270,6 +270,10 @@ The scheme=file is still there for old OS devices. It's also queried by apps lik + + + + diff --git a/src/keepass2android/Properties/AndroidManifest_nonet.xml b/src/keepass2android/Properties/AndroidManifest_nonet.xml index f6726a5f..23787d46 100644 --- a/src/keepass2android/Properties/AndroidManifest_nonet.xml +++ b/src/keepass2android/Properties/AndroidManifest_nonet.xml @@ -40,7 +40,7 @@ - + + + + diff --git a/src/keepass2android/Resources/layout/group.xml b/src/keepass2android/Resources/layout/group.xml index 8ce97ea8..b4e973cd 100644 --- a/src/keepass2android/Resources/layout/group.xml +++ b/src/keepass2android/Resources/layout/group.xml @@ -245,6 +245,50 @@ style="@style/BottomBarButton" /> + + + + + + + + +