diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsFragment.kt index da7acdbc82..33a8effb38 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/notifications/NotificationsSettingsFragment.kt @@ -106,7 +106,7 @@ class NotificationsSettingsFragment : DSLSettingsFragment(R.string.preferences__ textId = R.string.NotificationSettingsFragment__to_enable_notifications, actionId = R.string.NotificationSettingsFragment__turn_on, onClick = { - TurnOnNotificationsBottomSheet().show(childFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) + TurnOnNotificationsBottomSheet.turnOnSystemNotificationsFragment(requireContext()).show(childFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) } ) ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/megaphone/Megaphones.java b/app/src/main/java/org/thoughtcrime/securesms/megaphone/Megaphones.java index df5e011a43..ad8767664f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/megaphone/Megaphones.java +++ b/app/src/main/java/org/thoughtcrime/securesms/megaphone/Megaphones.java @@ -8,6 +8,7 @@ import android.os.Build; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import androidx.core.app.NotificationManagerCompat; import com.annimon.stream.Stream; @@ -109,6 +110,7 @@ public final class Megaphones { put(Event.PINS_FOR_ALL, new PinsForAllSchedule()); put(Event.CLIENT_DEPRECATED, SignalStore.misc().isClientDeprecated() ? ALWAYS : NEVER); put(Event.NOTIFICATIONS, shouldShowNotificationsMegaphone(context) ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(30)) : NEVER); + put(Event.GRANT_FULL_SCREEN_INTENT, shouldShowGrantFullScreenIntentPermission(context) ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(3)) : NEVER); put(Event.SMS_EXPORT, new SmsExportReminderSchedule(context)); put(Event.BACKUP_SCHEDULE_PERMISSION, shouldShowBackupSchedulePermissionMegaphone(context) ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(3)) : NEVER); put(Event.ONBOARDING, shouldShowOnboardingMegaphone(context) ? ALWAYS : NEVER); @@ -151,6 +153,8 @@ public final class Megaphones { return buildSmsExportMegaphone(context); case SET_UP_YOUR_USERNAME: return buildSetUpYourUsernameMegaphone(context); + case GRANT_FULL_SCREEN_INTENT: + return buildGrantFullScreenIntentPermission(context); default: throw new IllegalArgumentException("Event not handled!"); @@ -233,7 +237,7 @@ public final class Megaphones { .setImage(R.drawable.megaphone_notifications_64) .setActionButton(R.string.NotificationsMegaphone_turn_on, (megaphone, controller) -> { if (Build.VERSION.SDK_INT >= 26) { - controller.onMegaphoneDialogFragmentRequested(new TurnOnNotificationsBottomSheet()); + controller.onMegaphoneDialogFragmentRequested(TurnOnNotificationsBottomSheet.turnOnSystemNotificationsFragment(context)); } else { controller.onMegaphoneNavigationRequested(AppSettingsActivity.notifications(context)); } @@ -387,6 +391,20 @@ public final class Megaphones { .build(); } + public static @NonNull Megaphone buildGrantFullScreenIntentPermission(@NonNull Context context) { + return new Megaphone.Builder(Event.GRANT_FULL_SCREEN_INTENT, Megaphone.Style.BASIC) + .setTitle(R.string.GrantFullScreenIntentPermission_megaphone_title) + .setBody(R.string.GrantFullScreenIntentPermission_megaphone_body) + .setImage(R.drawable.calling_64) + .setActionButton(R.string.GrantFullScreenIntentPermission_megaphone_turn_on, (megaphone, controller) -> { + controller.onMegaphoneDialogFragmentRequested(TurnOnNotificationsBottomSheet.turnOnFullScreenIntentFragment(context)); + }) + .setSecondaryButton(R.string.SetUpYourUsername__not_now, (megaphone, controller) -> { + controller.onMegaphoneCompleted(Event.GRANT_FULL_SCREEN_INTENT); + }) + .build(); + } + private static boolean shouldShowDonateMegaphone(@NonNull Context context, @NonNull Event event, @NonNull Map records) { long timeSinceLastDonatePrompt = timeSinceLastDonatePrompt(event, records); @@ -461,6 +479,10 @@ public final class Megaphones { (System.currentTimeMillis() - phoneNumberDiscoveryDisabledAt) >= TimeUnit.DAYS.toMillis(3); } + private static boolean shouldShowGrantFullScreenIntentPermission(@NonNull Context context) { + return Build.VERSION.SDK_INT >= 34 && !NotificationManagerCompat.from(context).canUseFullScreenIntent(); + } + @WorkerThread private static boolean shouldShowRemoteMegaphone(@NonNull Map records) { boolean canShowLocalDonate = timeSinceLastDonatePrompt(Event.REMOTE_MEGAPHONE, records) > MIN_TIME_BETWEEN_DONATE_MEGAPHONES; @@ -502,7 +524,8 @@ public final class Megaphones { REMOTE_MEGAPHONE("remote_megaphone"), BACKUP_SCHEDULE_PERMISSION("backup_schedule_permission"), SMS_EXPORT("sms_export"), - SET_UP_YOUR_USERNAME("set_up_your_username"); + SET_UP_YOUR_USERNAME("set_up_your_username"), + GRANT_FULL_SCREEN_INTENT("grant_full_screen_intent"); private final String key; diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/TurnOnNotificationsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/TurnOnNotificationsBottomSheet.kt index 025ca41a4b..13ab601885 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/TurnOnNotificationsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/TurnOnNotificationsBottomSheet.kt @@ -5,9 +5,12 @@ package org.thoughtcrime.securesms.notifications +import android.content.Context import android.content.Intent +import android.net.Uri import android.os.Build import android.provider.Settings +import androidx.annotation.RequiresApi import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -32,6 +35,8 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.core.os.BundleCompat +import androidx.core.os.bundleOf import org.signal.core.ui.BottomSheets import org.signal.core.ui.Buttons import org.signal.core.ui.theme.SignalTheme @@ -44,27 +49,67 @@ private const val PLACEHOLDER = "__TOGGLE_PLACEHOLDER__" /** * Sheet explaining how to turn on notifications and providing an action to do so. */ -class TurnOnNotificationsBottomSheet : ComposeBottomSheetDialogFragment() { +class TurnOnNotificationsBottomSheet private constructor() : ComposeBottomSheetDialogFragment() { + + companion object { + private const val ARG_TITLE = "argument.title_res" + private const val ARG_SUBTITLE = "argument.subtitle_res" + private const val ARG_STEP2 = "argument.step2_res" + private const val ARG_SETTINGS_INTENT = "argument.settings_intent" + + @JvmStatic + fun turnOnSystemNotificationsFragment(context: Context): ComposeBottomSheetDialogFragment { + return TurnOnNotificationsBottomSheet().apply { + arguments = bundleOf( + ARG_TITLE to R.string.TurnOnNotificationsBottomSheet__turn_on_notifications, + ARG_SUBTITLE to R.string.TurnOnNotificationsBottomSheet__to_receive_notifications, + ARG_STEP2 to R.string.TurnOnNotificationsBottomSheet__2_s_turn_on_notifications, + ARG_SETTINGS_INTENT to getNotificationsSettingsIntent(context) + ) + } + } + + @JvmStatic + @RequiresApi(34) + fun turnOnFullScreenIntentFragment(context: Context): ComposeBottomSheetDialogFragment { + return TurnOnNotificationsBottomSheet().apply { + arguments = bundleOf( + ARG_TITLE to R.string.GrantFullScreenIntentPermission_bottomsheet_title, + ARG_SUBTITLE to R.string.GrantFullScreenIntentPermission_bottomsheet_subtitle, + ARG_STEP2 to R.string.GrantFullScreenIntentPermission_bottomsheet_step2, + ARG_SETTINGS_INTENT to Intent(Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT, Uri.parse("package:" + context.packageName)) + ) + } + } + + private fun getNotificationsSettingsIntent(context: Context): Intent { + return if (Build.VERSION.SDK_INT >= 26 && !NotificationChannels.getInstance().isMessageChannelEnabled) { + Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply { + putExtra(Settings.EXTRA_CHANNEL_ID, NotificationChannels.getInstance().messagesChannel) + putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) + } + } else if (Build.VERSION.SDK_INT >= 26 && (!NotificationChannels.getInstance().areNotificationsEnabled() || !NotificationChannels.getInstance().isMessagesChannelGroupEnabled)) { + Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { + putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) + } + } else { + AppSettingsActivity.notifications(context) + } + } + } @Composable override fun SheetContent() { - TurnOnNotificationsSheetContent(this::goToSystemNotificationSettings) + TurnOnNotificationsSheetContent( + titleRes = requireArguments().getInt(ARG_TITLE), + subtitleRes = requireArguments().getInt(ARG_SUBTITLE), + step2Res = requireArguments().getInt(ARG_STEP2), + onGoToSettingsClicked = this::goToSettings + ) } - private fun goToSystemNotificationSettings() { - if (Build.VERSION.SDK_INT >= 26 && !NotificationChannels.getInstance().isMessageChannelEnabled) { - val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS) - intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationChannels.getInstance().messagesChannel) - intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName) - startActivity(intent) - } else if (Build.VERSION.SDK_INT >= 26 && (!NotificationChannels.getInstance().areNotificationsEnabled() || !NotificationChannels.getInstance().isMessagesChannelGroupEnabled)) { - val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) - intent.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName) - startActivity(intent) - } else { - startActivity(AppSettingsActivity.notifications(requireContext())) - } - + private fun goToSettings() { + startActivity(BundleCompat.getParcelable(requireArguments(), ARG_SETTINGS_INTENT, Intent::class.java)!!) dismissAllowingStateLoss() } } @@ -74,13 +119,20 @@ class TurnOnNotificationsBottomSheet : ComposeBottomSheetDialogFragment() { private fun TurnOnNotificationsSheetContentPreview() { SignalTheme(isDarkMode = false) { Surface { - TurnOnNotificationsSheetContent {} + TurnOnNotificationsSheetContent( + titleRes = R.string.TurnOnNotificationsBottomSheet__turn_on_notifications, + subtitleRes = R.string.TurnOnNotificationsBottomSheet__to_receive_notifications, + step2Res = R.string.TurnOnNotificationsBottomSheet__2_s_turn_on_notifications + ) {} } } } @Composable private fun TurnOnNotificationsSheetContent( + titleRes: Int, + subtitleRes: Int, + step2Res: Int, onGoToSettingsClicked: () -> Unit ) { Column( @@ -93,7 +145,7 @@ private fun TurnOnNotificationsSheetContent( ) Text( - text = stringResource(R.string.TurnOnNotificationsBottomSheet__turn_on_notifications), + text = stringResource(titleRes), style = MaterialTheme.typography.headlineSmall, modifier = Modifier .align(Alignment.CenterHorizontally) @@ -101,7 +153,7 @@ private fun TurnOnNotificationsSheetContent( ) Text( - text = stringResource(R.string.TurnOnNotificationsBottomSheet__to_receive_notifications), + text = stringResource(subtitleRes), style = MaterialTheme.typography.bodyMedium, modifier = Modifier .align(Alignment.CenterHorizontally) @@ -113,7 +165,7 @@ private fun TurnOnNotificationsSheetContent( modifier = Modifier.padding(bottom = 32.dp) ) - val step2String = stringResource(id = R.string.TurnOnNotificationsBottomSheet__2_s_turn_on_notifications, PLACEHOLDER) + val step2String = stringResource(id = step2Res, PLACEHOLDER) val (step2Text, step2InlineContent) = remember(step2String) { val parts = step2String.split(PLACEHOLDER) val annotatedString = buildAnnotatedString { diff --git a/app/src/main/res/drawable/calling_64.xml b/app/src/main/res/drawable/calling_64.xml new file mode 100644 index 0000000000..54cebb7d79 --- /dev/null +++ b/app/src/main/res/drawable/calling_64.xml @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4b89368cbd..3a8700484b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6366,5 +6366,18 @@ Approve + + Turn on full screen notifications? + + Never miss a call from your contacts and groups. + + Turn on + + Turn on full screen notifications + + To receive call notifications from your contacts and groups: + + 2. %1$s Allow full screen notifications +