diff --git a/src/keepass2android-app/EntryActivity.cs b/src/keepass2android-app/EntryActivity.cs index b6bc8495..f470b596 100644 --- a/src/keepass2android-app/EntryActivity.cs +++ b/src/keepass2android-app/EntryActivity.cs @@ -56,6 +56,8 @@ using Android.Util; using AndroidX.Core.Content; using Google.Android.Material.Dialog; using keepass2android; +using AndroidX.Core.App; +using Google.Android.Material.Snackbar; namespace keepass2android { @@ -571,13 +573,41 @@ namespace keepass2android if (permissions.Length == 1 && permissions.First() == Android.Manifest.Permission.PostNotifications && grantResults.First() == Permission.Granted) { - StartNotificationsServiceAfterPermissionsCheck(requestCode == 1 /*requestCode is used to transfer this flag*/); + if (!CopyToClipboardService.HasEntryNotificationPermissions(this, false)) + { + Intent intent = new Intent(); + if (Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O) + { + intent.SetAction(Android.Provider.Settings.ActionAppNotificationSettings); + intent.PutExtra(Android.Provider.Settings.ExtraAppPackage, PackageName); + } + else if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop) + { + intent.SetAction(Android.Provider.Settings.ActionAppNotificationSettings); + intent.PutExtra("app_package", PackageName); + intent.PutExtra("app_uid", ApplicationInfo.Uid); + } + else + { + intent.SetAction(Android.Provider.Settings.ActionApplicationDetailsSettings); + intent.AddCategory(Intent.CategoryDefault); + intent.SetData(Uri.Parse("package:" + PackageName)); + } + StartActivity(intent); + } + else + { + Kp2aLog.Log($"StartNotificationsServiceAfterPermissionsCheck(activateKeyboard: requestCode == {requestCode}"); + StartNotificationsServiceAfterPermissionsCheck(requestCode == 1 /*requestCode is used to transfer this flag*/); + } + } base.OnRequestPermissionsResult(requestCode, permissions, grantResults); } internal void StartNotificationsService(bool activateKeyboard) { + Kp2aLog.Log($"StartNotificationsService. ActivateKeyboard={activateKeyboard}"); if (PreferenceManager.GetDefaultSharedPreferences(this).GetBoolean( GetString(Resource.String.CopyToClipboardNotification_key), Resources.GetBoolean(Resource.Boolean.CopyToClipboardNotification_default)) == false @@ -585,14 +615,18 @@ namespace keepass2android GetString(Resource.String.UseKp2aKeyboard_key), Resources.GetBoolean(Resource.Boolean.UseKp2aKeyboard_default)) == false) { - //notifications are disabled + //notifications are disabled + Kp2aLog.Log($"StartNotificationsService. Notifications disabled. Returning."); return; } - if ((int)Build.VERSION.SdkInt < 33 || CheckSelfPermission(Android.Manifest.Permission.PostNotifications) == - Permission.Granted) - { - StartNotificationsServiceAfterPermissionsCheck(activateKeyboard); + + + if ((int)Build.VERSION.SdkInt < 33 || CopyToClipboardService.HasEntryNotificationPermissions(this, activateKeyboard)) + { + Kp2aLog.Log($"StartNotificationsService. Permissions ok. activateKeyboard={activateKeyboard}"); + + StartNotificationsServiceAfterPermissionsCheck(activateKeyboard); return; } @@ -602,16 +636,40 @@ namespace keepass2android 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. + Kp2aLog.Log($"StartNotificationsService. Permissions not granted. Showing snackbar."); + //user has denied permission before. Do not show the dialog. User must give permission in the Android App settings. + var snackbar = Snackbar + .Make(SnackbarAnchorView, Resource.String.post_notifications_snackbar, + Snackbar.LengthIndefinite); + snackbar.SetTextMaxLines(10); + if ((int)Build.VERSION.SdkInt >= 23) + { + + snackbar.SetBackgroundTint(App.Context.GetColor(Resource.Color.md_theme_inverseSurface)); + snackbar.SetTextColor(App.Context.GetColor(Resource.Color.md_theme_inverseOnSurface)); + } + + snackbar.SetAction(Resource.String.post_notifications_snackbar_config, view => { ShowNotificationPermissionsDialog(activateKeyboard); }); + + snackbar.Show(); + return; } + + ShowNotificationPermissionsDialog(activateKeyboard); + + } + + private void ShowNotificationPermissionsDialog(bool activateKeyboard) + { + Kp2aLog.Log($"ShowNotificationPermissionsDialog"); new MaterialAlertDialogBuilder(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 + //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); @@ -632,8 +690,6 @@ namespace keepass2android }) .SetNeutralButton(Resource.String.post_notifications_dialog_notnow, (sender, args) => { }) .Show(); - - } private void StartNotificationsServiceAfterPermissionsCheck(bool activateKeyboard) diff --git a/src/keepass2android-app/EntryEditActivity.cs b/src/keepass2android-app/EntryEditActivity.cs index a9e6d351..b4847a89 100644 --- a/src/keepass2android-app/EntryEditActivity.cs +++ b/src/keepass2android-app/EntryEditActivity.cs @@ -337,6 +337,7 @@ namespace keepass2android if (PreferenceManager.GetDefaultSharedPreferences(this) .GetBoolean(GetString(Resource.String.UseKp2aKeyboardInKp2a_key), false)) { + Kp2aLog.Log("Activating keyboard in EntryEditActivity due to UseKp2aKeyboardInKp2a"); CopyToClipboardService.ActivateKeyboard(this); } } diff --git a/src/keepass2android-app/PasswordActivity.cs b/src/keepass2android-app/PasswordActivity.cs index 64fc1a04..7e54b89f 100644 --- a/src/keepass2android-app/PasswordActivity.cs +++ b/src/keepass2android-app/PasswordActivity.cs @@ -1578,7 +1578,8 @@ namespace keepass2android if (PreferenceManager.GetDefaultSharedPreferences(this) .GetBoolean(GetString(Resource.String.UseKp2aKeyboardInKp2a_key), false)) { - CopyToClipboardService.ActivateKeyboard(this); + Kp2aLog.Log("Activating keyboard in PasswordActivity due to UseKp2aKeyboardInKp2a"); + CopyToClipboardService.ActivateKeyboard(this); } DonateReminder.ShowDonateReminderIfAppropriate(this); diff --git a/src/keepass2android-app/Resources/values/strings.xml b/src/keepass2android-app/Resources/values/strings.xml index 74bf9262..dc8e6101 100644 --- a/src/keepass2android-app/Resources/values/strings.xml +++ b/src/keepass2android-app/Resources/values/strings.xml @@ -1224,6 +1224,9 @@ Keepass2Android has detected biometric hardware. Do you want to enable Biometric Unlock for this database? Allow notifications Keepass2Android can show notifications with buttons to copy values like passwords and TOTPs to clipboard, or to bring up the built-in keyboard. This is useful to transfer values into other apps without switching to Keepass2Android repeatedly. Do you want to enable such notifications? + Cannot make entry available through notification. No permission granted. + Configure + Allow notifications Disable this feature Not now diff --git a/src/keepass2android-app/SelectCurrentDbActivity.cs b/src/keepass2android-app/SelectCurrentDbActivity.cs index 6be80475..5e97e2d0 100644 --- a/src/keepass2android-app/SelectCurrentDbActivity.cs +++ b/src/keepass2android-app/SelectCurrentDbActivity.cs @@ -328,6 +328,7 @@ namespace keepass2android if (prefs.GetBoolean("kp2a_switch_rooted", false)) { activationCondition = ActivationCondition.Always; + Kp2aLog.Log("Will activate keyboard because SearchUrlTask opened with ActionSend and kp2a_switch_rooted"); } else { @@ -336,6 +337,7 @@ namespace keepass2android if (prefs.GetBoolean(this.GetString(Resource.String.OpenKp2aKeyboardAutomatically_key), this.Resources.GetBoolean(Resource.Boolean.OpenKp2aKeyboardAutomatically_default))) { activationCondition = ActivationCondition.Always; + Kp2aLog.Log("Will activate keyboard because SearchUrlTask opened with ActionSend and OpenKp2aKeyboardAutomatically"); } } diff --git a/src/keepass2android-app/app/AppTask.cs b/src/keepass2android-app/app/AppTask.cs index 7cb617bd..2ba1db09 100644 --- a/src/keepass2android-app/app/AppTask.cs +++ b/src/keepass2android-app/app/AppTask.cs @@ -355,6 +355,10 @@ namespace keepass2android activity.GetString(Resource.String .OpenKp2aKeyboardAutomaticallyOnlyAfterSearch_key), false); + Kp2aLog.Log($"AppTask.CompleteOnCreateEntryActivity. ActivateKeyboard={activateKeyboard}, kp2a_switch_rooted={prefs.GetBoolean("kp2a_switch_rooted", false)}, OpenKp2aKeyboardAutomaticallyOnlyAfterSearch={prefs.GetBoolean( + activity.GetString(Resource.String + .OpenKp2aKeyboardAutomaticallyOnlyAfterSearch_key), false)}"); + activity.StartNotificationsService(activateKeyboard); } @@ -622,6 +626,7 @@ namespace keepass2android bool isTotpEntry = totpPluginAdapter != null; bool activateKeyboard = ActivateKeyboard == ActivationCondition.Always || (ActivateKeyboard == ActivationCondition.WhenTotp && isTotpEntry); + Kp2aLog.Log($"activateKeyboard == {activateKeyboard}. Task.Activate=={ActivateKeyboard}, isTotpEntry={isTotpEntry}"); if ((ShowUserNotifications == ActivationCondition.Always) || ((ShowUserNotifications == ActivationCondition.WhenTotp) && isTotpEntry) diff --git a/src/keepass2android-app/services/CopyToClipboardService.cs b/src/keepass2android-app/services/CopyToClipboardService.cs index bcf62df7..70c87301 100644 --- a/src/keepass2android-app/services/CopyToClipboardService.cs +++ b/src/keepass2android-app/services/CopyToClipboardService.cs @@ -878,6 +878,37 @@ namespace keepass2android { get { return PackageName + "/keepass2android.softkeyboard.KP2AKeyboard"; } } + + private static bool IsChannelPermissionGranted(Context context, string channelId) + { + if (Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O) + { + if (!string.IsNullOrEmpty(channelId)) + { + NotificationManager? manager = + (NotificationManager?)context.GetSystemService(Context.NotificationService)!; + NotificationChannel? channel = manager?.GetNotificationChannel(channelId); + return channel?.Importance != Android.App.NotificationImportance.None; + } + + return false; + } + + return true; + } + + public static bool HasEntryNotificationPermissions(Context context, bool activateKeyboard) + { + if (!NotificationManagerCompat.From(context).AreNotificationsEnabled()) + { + return false; + } + + return IsChannelPermissionGranted(context, App.NotificationChannelIdEntry); + } + + + } [BroadcastReceiver(Permission = "keepass2android." + AppNames.PackagePart + ".permission.CopyToClipboard")]