@klyftig/android-perms 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/android/build.gradle +19 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/expo/modules/sleepwiseandroidperms/SleepwiseAndroidPermsModule.kt +95 -0
- package/expo-module.config.json +6 -0
- package/index.ts +49 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @klyftig/android-perms
|
|
2
|
+
|
|
3
|
+
Expo native module (Android only): the alarm-permission probes and settings
|
|
4
|
+
deep links that notifee does not expose. Extracted from Sleepwise's
|
|
5
|
+
`modules/sleepwise-android-perms` (2026-09-15); the Kotlin identifier is still
|
|
6
|
+
`SleepwiseAndroidPerms`.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import {
|
|
10
|
+
canUseFullScreenIntent, // Android 14+: revocable for non-alarm-category apps
|
|
11
|
+
isNotificationPolicyAccessGranted, // DND bypass
|
|
12
|
+
canDrawOverlays, // SYSTEM_ALERT_WINDOW
|
|
13
|
+
getManufacturer, // for OEM battery-killer guidance
|
|
14
|
+
openAndroidSettings, // deep link to the matching system screen
|
|
15
|
+
} from '@klyftig/android-perms';
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`@klyftig/alarm-core`'s `getAlarmPermissionStatus()` / `openPermissionScreen`
|
|
19
|
+
compose these with notifee's own probes into one status object for onboarding
|
|
20
|
+
and the Settings "alarm health" rows.
|
|
21
|
+
|
|
22
|
+
Off-device (iOS / web / jest) every probe returns a safe default. Autolinks
|
|
23
|
+
from `node_modules`; needs a dev client.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
apply plugin: 'com.android.library'
|
|
2
|
+
apply plugin: 'kotlin-android'
|
|
3
|
+
|
|
4
|
+
group = 'expo.modules.sleepwiseandroidperms'
|
|
5
|
+
version = '0.1.0'
|
|
6
|
+
|
|
7
|
+
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
8
|
+
apply from: expoModulesCorePlugin
|
|
9
|
+
applyKotlinExpoModulesCorePlugin()
|
|
10
|
+
useCoreDependencies()
|
|
11
|
+
useDefaultAndroidSdkVersions()
|
|
12
|
+
useExpoPublishing()
|
|
13
|
+
|
|
14
|
+
android {
|
|
15
|
+
namespace "expo.modules.sleepwiseandroidperms"
|
|
16
|
+
defaultConfig {
|
|
17
|
+
minSdkVersion 24
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
package/android/src/main/java/expo/modules/sleepwiseandroidperms/SleepwiseAndroidPermsModule.kt
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
package expo.modules.sleepwiseandroidperms
|
|
2
|
+
|
|
3
|
+
import android.app.NotificationManager
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.net.Uri
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import android.provider.Settings
|
|
9
|
+
import expo.modules.kotlin.modules.Module
|
|
10
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The alarm grants notifee doesn't expose (Phase 3 guided permissions):
|
|
14
|
+
*
|
|
15
|
+
* - USE_FULL_SCREEN_INTENT: on Android 14+ (API 34) it is a per-app grant that
|
|
16
|
+
* Play can revoke for non-alarm apps; without it the full-screen ringing
|
|
17
|
+
* screen never appears over the lock screen (only a heads-up).
|
|
18
|
+
* - Notification-policy ("Do Not Disturb") access: without it the channel's
|
|
19
|
+
* bypassDnd stays false and a phone in DND swallows the alarm.
|
|
20
|
+
* - SYSTEM_ALERT_WINDOW (overlay): Alarmy asks for it on Android 10+ as an
|
|
21
|
+
* extra guarantee for drawing over other apps.
|
|
22
|
+
*
|
|
23
|
+
* Each has a status read + a deep link to the exact system screen.
|
|
24
|
+
*/
|
|
25
|
+
class SleepwiseAndroidPermsModule : Module() {
|
|
26
|
+
override fun definition() = ModuleDefinition {
|
|
27
|
+
Name("SleepwiseAndroidPerms")
|
|
28
|
+
|
|
29
|
+
Function("canUseFullScreenIntent") {
|
|
30
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
31
|
+
notificationManager().canUseFullScreenIntent()
|
|
32
|
+
} else {
|
|
33
|
+
true // auto-granted before API 34
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
Function("openFullScreenIntentSettings") {
|
|
38
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
39
|
+
startSettings(Intent(Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT).apply {
|
|
40
|
+
data = Uri.parse("package:${context.packageName}")
|
|
41
|
+
})
|
|
42
|
+
} else {
|
|
43
|
+
openAppDetails()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Function("isNotificationPolicyAccessGranted") {
|
|
48
|
+
notificationManager().isNotificationPolicyAccessGranted
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
Function("openNotificationPolicyAccessSettings") {
|
|
52
|
+
startSettings(Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
Function("canDrawOverlays") {
|
|
56
|
+
Settings.canDrawOverlays(context)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Function("openOverlaySettings") {
|
|
60
|
+
startSettings(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION).apply {
|
|
61
|
+
data = Uri.parse("package:${context.packageName}")
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
Function("openAppDetailsSettings") {
|
|
66
|
+
openAppDetails()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
Function("getManufacturer") {
|
|
70
|
+
Build.MANUFACTURER ?: ""
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private val context: Context
|
|
75
|
+
get() = requireNotNull(appContext.reactContext) { "React context unavailable" }
|
|
76
|
+
|
|
77
|
+
private fun notificationManager(): NotificationManager =
|
|
78
|
+
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
79
|
+
|
|
80
|
+
private fun openAppDetails() {
|
|
81
|
+
startSettings(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
|
82
|
+
data = Uri.parse("package:${context.packageName}")
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private fun startSettings(intent: Intent) {
|
|
87
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
88
|
+
try {
|
|
89
|
+
context.startActivity(intent)
|
|
90
|
+
} catch (_: Exception) {
|
|
91
|
+
// Some OEM builds lack the specific screen — fall back to the app's page.
|
|
92
|
+
if (intent.action != Settings.ACTION_APPLICATION_DETAILS_SETTINGS) openAppDetails()
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JS surface for `sleepwise-android-perms` — the alarm grants notifee doesn't
|
|
3
|
+
* expose. Android-only; every call degrades to a safe value elsewhere (and in
|
|
4
|
+
* jest), so the permissions helper never has to branch on platform itself.
|
|
5
|
+
*/
|
|
6
|
+
import { Platform } from 'react-native';
|
|
7
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
8
|
+
|
|
9
|
+
interface NativePerms {
|
|
10
|
+
canUseFullScreenIntent(): boolean;
|
|
11
|
+
openFullScreenIntentSettings(): void;
|
|
12
|
+
isNotificationPolicyAccessGranted(): boolean;
|
|
13
|
+
openNotificationPolicyAccessSettings(): void;
|
|
14
|
+
canDrawOverlays(): boolean;
|
|
15
|
+
openOverlaySettings(): void;
|
|
16
|
+
openAppDetailsSettings(): void;
|
|
17
|
+
getManufacturer(): string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let native: NativePerms | null = null;
|
|
21
|
+
if (Platform.OS === 'android') {
|
|
22
|
+
try {
|
|
23
|
+
native = requireNativeModule<NativePerms>('SleepwiseAndroidPerms');
|
|
24
|
+
} catch {
|
|
25
|
+
native = null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const isAndroidPermsAvailable = native != null;
|
|
30
|
+
|
|
31
|
+
/** null = unknown (not Android / module missing). */
|
|
32
|
+
export function canUseFullScreenIntent(): boolean | null {
|
|
33
|
+
return native ? native.canUseFullScreenIntent() : null;
|
|
34
|
+
}
|
|
35
|
+
export function isNotificationPolicyAccessGranted(): boolean | null {
|
|
36
|
+
return native ? native.isNotificationPolicyAccessGranted() : null;
|
|
37
|
+
}
|
|
38
|
+
export function canDrawOverlays(): boolean | null {
|
|
39
|
+
return native ? native.canDrawOverlays() : null;
|
|
40
|
+
}
|
|
41
|
+
export function getManufacturer(): string {
|
|
42
|
+
return native ? native.getManufacturer() : '';
|
|
43
|
+
}
|
|
44
|
+
export const openAndroidSettings = {
|
|
45
|
+
fullScreenIntent: () => native?.openFullScreenIntentSettings(),
|
|
46
|
+
notificationPolicy: () => native?.openNotificationPolicyAccessSettings(),
|
|
47
|
+
overlay: () => native?.openOverlaySettings(),
|
|
48
|
+
appDetails: () => native?.openAppDetailsSettings(),
|
|
49
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@klyftig/android-perms",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Android alarm-permission status + settings deep links notifee doesn't cover (full-screen intent, DND access, overlay, manufacturer)",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"types": "index.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"index.ts",
|
|
9
|
+
"expo-module.config.json",
|
|
10
|
+
"android/build.gradle",
|
|
11
|
+
"android/src",
|
|
12
|
+
"ios"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"expo",
|
|
16
|
+
"expo-module",
|
|
17
|
+
"react-native",
|
|
18
|
+
"alarm",
|
|
19
|
+
"klyftig"
|
|
20
|
+
],
|
|
21
|
+
"license": "UNLICENSED",
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"expo": "*",
|
|
24
|
+
"expo-modules-core": "*"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
}
|
|
29
|
+
}
|