@@ -3,6 +3,7 @@ package com.focusforceplus.app.util
33import android.app.AlarmManager
44import android.app.AppOpsManager
55import android.app.NotificationManager
6+ import android.content.ActivityNotFoundException
67import android.content.ComponentName
78import android.content.Context
89import android.content.Intent
@@ -20,11 +21,13 @@ object PermissionHelper {
2021
2122 /* * Opens the system page where the user can grant "Display over other apps". */
2223 fun openOverlaySettings (context : Context ) {
23- val intent = Intent (
24- Settings .ACTION_MANAGE_OVERLAY_PERMISSION ,
25- Uri .parse(" package:${context.packageName} " ),
26- ).addFlags(Intent .FLAG_ACTIVITY_NEW_TASK )
27- context.startActivity(intent)
24+ startSettingsSafely(
25+ context,
26+ Intent (
27+ Settings .ACTION_MANAGE_OVERLAY_PERMISSION ,
28+ Uri .parse(" package:${context.packageName} " ),
29+ ),
30+ )
2831 }
2932
3033 /* * True if the app is allowed to launch full-screen intents (alarm-style overlays). */
@@ -34,13 +37,17 @@ object PermissionHelper {
3437 return nm.canUseFullScreenIntent()
3538 }
3639
37- /* * Opens the system page where the user can grant USE_FULL_SCREEN_INTENT. */
40+ /* * Opens the system page where the user can grant USE_FULL_SCREEN_INTENT.
41+ * Not every device/OEM ships this dedicated screen, so we fall back gracefully. */
3842 fun openFullScreenIntentSettings (context : Context ) {
3943 if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .UPSIDE_DOWN_CAKE ) {
40- val intent = Intent (" android.settings.MANAGE_APP_USE_FULL_SCREEN_INTENTS" )
41- intent.data = Uri .parse(" package:${context.packageName} " )
42- intent.flags = Intent .FLAG_ACTIVITY_NEW_TASK
43- context.startActivity(intent)
44+ startSettingsSafely(
45+ context,
46+ Intent (" android.settings.MANAGE_APP_USE_FULL_SCREEN_INTENTS" )
47+ .setData(Uri .parse(" package:${context.packageName} " )),
48+ Intent (" android.settings.APP_NOTIFICATION_SETTINGS" )
49+ .putExtra(" android.provider.extra.APP_PACKAGE" , context.packageName),
50+ )
4451 }
4552 }
4653
@@ -56,11 +63,11 @@ object PermissionHelper {
5663 /* * Opens the system page where the user can grant SCHEDULE_EXACT_ALARM (Android 12 only). */
5764 fun openExactAlarmSettings (context : Context ) {
5865 if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .S ) {
59- val intent = Intent ( Settings . ACTION_REQUEST_SCHEDULE_EXACT_ALARM ). apply {
60- data = Uri .parse( " package: ${ context.packageName} " )
61- flags = Intent . FLAG_ACTIVITY_NEW_TASK
62- }
63- context.startActivity(intent )
66+ startSettingsSafely(
67+ context,
68+ Intent ( Settings . ACTION_REQUEST_SCHEDULE_EXACT_ALARM )
69+ .setData( Uri .parse( " package: ${context.packageName} " )),
70+ )
6471 }
6572 }
6673
@@ -93,10 +100,12 @@ object PermissionHelper {
93100 /* * Opens the system accessibility settings (the service must be enabled there).
94101 * Only call this AFTER the in-app prominent disclosure has been confirmed. */
95102 fun openAccessibilitySettings (context : Context ) {
96- context.startActivity(
97- Intent (Settings .ACTION_ACCESSIBILITY_SETTINGS )
98- .addFlags(Intent .FLAG_ACTIVITY_NEW_TASK )
99- )
103+ startSettingsSafely(context, Intent (Settings .ACTION_ACCESSIBILITY_SETTINGS ))
104+ }
105+
106+ /* * Opens this app's Android app-info page (where "Allow restricted settings" lives). */
107+ fun openAppDetailsSettings (context : Context ) {
108+ startSettingsSafely(context, appDetailsIntent(context))
100109 }
101110
102111 /* * True if Usage Access (PACKAGE_USAGE_STATS) has been granted. */
@@ -118,16 +127,12 @@ object PermissionHelper {
118127 /* * Opens the system Usage Access settings.
119128 * Only call this AFTER the in-app prominent disclosure has been confirmed. */
120129 fun openUsageAccessSettings (context : Context ) {
121- val direct = Intent (Settings .ACTION_USAGE_ACCESS_SETTINGS )
122- .setData(Uri .parse(" package:${context.packageName} " ))
123- .addFlags(Intent .FLAG_ACTIVITY_NEW_TASK )
124- runCatching { context.startActivity(direct) }.onFailure {
125- // Some OEMs reject the package URI form; fall back to the list page.
126- context.startActivity(
127- Intent (Settings .ACTION_USAGE_ACCESS_SETTINGS )
128- .addFlags(Intent .FLAG_ACTIVITY_NEW_TASK )
129- )
130- }
130+ startSettingsSafely(
131+ context,
132+ Intent (Settings .ACTION_USAGE_ACCESS_SETTINGS )
133+ .setData(Uri .parse(" package:${context.packageName} " )),
134+ Intent (Settings .ACTION_USAGE_ACCESS_SETTINGS ),
135+ )
131136 }
132137
133138 // ── Focus mode (Do Not Disturb) ───────────────────────────────────────────
@@ -140,10 +145,7 @@ object PermissionHelper {
140145
141146 /* * Opens the system page where the user can grant DND access. */
142147 fun openDndAccessSettings (context : Context ) {
143- context.startActivity(
144- Intent (Settings .ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS )
145- .addFlags(Intent .FLAG_ACTIVITY_NEW_TASK )
146- )
148+ startSettingsSafely(context, Intent (Settings .ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS ))
147149 }
148150
149151 // ── Battery optimization ──────────────────────────────────────────────────
@@ -158,9 +160,33 @@ object PermissionHelper {
158160 * We deliberately use the non-prompting list intent: REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
159161 * is restricted and per compliance guide (3.5) not to be forced. */
160162 fun openBatteryOptimizationSettings (context : Context ) {
161- context.startActivity(
162- Intent (Settings .ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS )
163- .addFlags(Intent .FLAG_ACTIVITY_NEW_TASK )
164- )
163+ startSettingsSafely(context, Intent (Settings .ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS ))
164+ }
165+
166+ // ── Internals ──────────────────────────────────────────────────────────────
167+
168+ private fun appDetailsIntent (context : Context ) = Intent (
169+ Settings .ACTION_APPLICATION_DETAILS_SETTINGS ,
170+ Uri .parse(" package:${context.packageName} " ),
171+ )
172+
173+ /* *
174+ * Tries each settings intent in turn, always falling back to the app-info page,
175+ * so a device that doesn't ship a particular settings screen never crashes the
176+ * app. Every candidate gets NEW_TASK since these are launched from non-activity
177+ * contexts too.
178+ */
179+ private fun startSettingsSafely (context : Context , vararg candidates : Intent ) {
180+ val all = candidates.toList() + appDetailsIntent(context)
181+ for (intent in all) {
182+ try {
183+ context.startActivity(intent.addFlags(Intent .FLAG_ACTIVITY_NEW_TASK ))
184+ return
185+ } catch (_: ActivityNotFoundException ) {
186+ // try the next candidate
187+ } catch (_: Exception ) {
188+ // some OEMs throw SecurityException etc. — try the next candidate
189+ }
190+ }
165191 }
166192}
0 commit comments