Compare View
Commits (2)
-
…t confuse at app start.
-
Develop
Changes
Showing 3 changed files Side-by-side Diff
android/src/main/java/com/reactnativecommunity/rnpermissions/RNPermissionsModule.java
1 | 1 | package com.reactnativecommunity.rnpermissions; |
2 | 2 | |
3 | 3 | import android.Manifest; |
4 | +import android.app.Activity; | |
4 | 5 | import android.content.Context; |
5 | 6 | import android.content.Intent; |
6 | 7 | import android.content.SharedPreferences; |
... | ... | @@ -9,7 +10,9 @@ import android.provider.Settings; |
9 | 10 | |
10 | 11 | import androidx.core.app.NotificationManagerCompat; |
11 | 12 | |
13 | +import com.facebook.react.bridge.ActivityEventListener; | |
12 | 14 | import com.facebook.react.bridge.Arguments; |
15 | +import com.facebook.react.bridge.BaseActivityEventListener; | |
13 | 16 | import com.facebook.react.bridge.Promise; |
14 | 17 | import com.facebook.react.bridge.ReactApplicationContext; |
15 | 18 | import com.facebook.react.bridge.ReactContextBaseJavaModule; |
... | ... | @@ -21,6 +24,7 @@ import com.facebook.react.module.annotations.ReactModule; |
21 | 24 | |
22 | 25 | import java.util.HashMap; |
23 | 26 | import java.util.Map; |
27 | +import android.os.Build; | |
24 | 28 | |
25 | 29 | import javax.annotation.Nullable; |
26 | 30 | |
... | ... | @@ -30,8 +34,30 @@ public class RNPermissionsModule extends ReactContextBaseJavaModule { |
30 | 34 | private static final String ERROR_INVALID_ACTIVITY = "E_INVALID_ACTIVITY"; |
31 | 35 | public static final String MODULE_NAME = "RNPermissions"; |
32 | 36 | private static final String SETTING_NAME = "@RNPermissions:NonRequestables"; |
33 | - | |
34 | - private static final String[][] PERMISSIONS = new String[][] { | |
37 | + private static final int OVERLAY_PERMISSION_CODE = 1009; | |
38 | + | |
39 | + private Promise mPickerPromise; | |
40 | + | |
41 | + private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() { | |
42 | + @Override | |
43 | + public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) { | |
44 | + if (requestCode == OVERLAY_PERMISSION_CODE) { | |
45 | + if (mPickerPromise != null) { | |
46 | + final WritableMap output = Arguments.createMap(); | |
47 | + final WritableMap settings = Arguments.createMap(); | |
48 | + output.putMap("settings", settings); | |
49 | + if (resultCode == Activity.RESULT_CANCELED) { | |
50 | + output.putString("status", "blocked"); | |
51 | + } else if (resultCode == Activity.RESULT_OK) { | |
52 | + output.putString("status", "granted"); | |
53 | + } | |
54 | + mPickerPromise.resolve(output); | |
55 | + } | |
56 | + } | |
57 | + } | |
58 | + }; | |
59 | + | |
60 | + private static final String[][] PERMISSIONS = new String[][] { | |
35 | 61 | { "ACCEPT_HANDOVER", "android.permission.ACCEPT_HANDOVER" }, |
36 | 62 | { "ACCESS_BACKGROUND_LOCATION", "android.permission.ACCESS_BACKGROUND_LOCATION" }, |
37 | 63 | { "ACCESS_COARSE_LOCATION", "android.permission.ACCESS_COARSE_LOCATION" }, |
... | ... | @@ -61,6 +87,7 @@ public class RNPermissionsModule extends ReactContextBaseJavaModule { |
61 | 87 | { "WRITE_CALL_LOG", "android.permission.WRITE_CALL_LOG" }, |
62 | 88 | { "WRITE_CONTACTS", "android.permission.WRITE_CONTACTS" }, |
63 | 89 | { "WRITE_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE" }, |
90 | + { "MANAGE_OVERLAY_PERMISSION", "android.permission.MANAGE_OVERLAY_PERMISSION" }, | |
64 | 91 | }; |
65 | 92 | |
66 | 93 | private final SharedPreferences sharedPrefs; |
... | ... | @@ -68,6 +95,8 @@ public class RNPermissionsModule extends ReactContextBaseJavaModule { |
68 | 95 | public RNPermissionsModule(ReactApplicationContext reactContext) { |
69 | 96 | super(reactContext); |
70 | 97 | sharedPrefs = reactContext.getSharedPreferences(SETTING_NAME, Context.MODE_PRIVATE); |
98 | + // Add the listener for `onActivityResult` | |
99 | + reactContext.addActivityEventListener(mActivityEventListener); | |
71 | 100 | } |
72 | 101 | |
73 | 102 | @Override |
... | ... | @@ -163,4 +192,38 @@ public class RNPermissionsModule extends ReactContextBaseJavaModule { |
163 | 192 | promise.reject(ERROR_INVALID_ACTIVITY, e); |
164 | 193 | } |
165 | 194 | } |
195 | + | |
196 | + | |
197 | + @ReactMethod | |
198 | + public void checkOrRequestOverlayPermission(final Promise promise) { | |
199 | +// Check if Android M or higher | |
200 | + final ReactApplicationContext reactContext = getReactApplicationContext(); | |
201 | + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M && !Settings.canDrawOverlays(reactContext)) { | |
202 | + // Store the promise to resolve/reject when picker returns data | |
203 | + try { | |
204 | + Activity currentActivity = getCurrentActivity(); | |
205 | + if (currentActivity == null) { | |
206 | + promise.reject("E_ACTIVITY_DOES_NOT_EXIST", "Activity doesn't exist"); | |
207 | + return; | |
208 | + } | |
209 | + mPickerPromise = promise; | |
210 | + final String packageName = reactContext.getPackageName(); | |
211 | + // Show alert dialog to the user saying a separate permission is needed | |
212 | + // Launch the settings activity if the user prefers | |
213 | + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, | |
214 | + Uri.parse(packageName)); | |
215 | + currentActivity.startActivityForResult(intent, OVERLAY_PERMISSION_CODE); | |
216 | + } catch(Exception e) { | |
217 | + mPickerPromise.reject("ACTION_MANAGE_OVERLAY_PERMISSION_ERROR", e); | |
218 | + mPickerPromise = null; | |
219 | + } | |
220 | + }else{ | |
221 | + //default is granted :D | |
222 | + final WritableMap output = Arguments.createMap(); | |
223 | + final WritableMap settings = Arguments.createMap(); | |
224 | + output.putString("status", "granted"); | |
225 | + output.putMap("settings", settings); | |
226 | + promise.resolve(output); | |
227 | + } | |
228 | + } | |
166 | 229 | } |
src/constants.ts
... | ... | @@ -28,6 +28,7 @@ export const ANDROID = Object.freeze({ |
28 | 28 | WRITE_CALL_LOG: 'android.permission.WRITE_CALL_LOG' as const, |
29 | 29 | WRITE_CONTACTS: 'android.permission.WRITE_CONTACTS' as const, |
30 | 30 | WRITE_EXTERNAL_STORAGE: 'android.permission.WRITE_EXTERNAL_STORAGE' as const, |
31 | + MANAGE_OVERLAY_PERMISSION: 'android.permission.MANAGE_OVERLAY_PERMISSION' as const, | |
31 | 32 | }); |
32 | 33 | |
33 | 34 | export const IOS = Object.freeze({ |
src/module.android.ts
... | ... | @@ -5,16 +5,17 @@ import { |
5 | 5 | PermissionStatus as CoreStatus, |
6 | 6 | Rationale, |
7 | 7 | } from 'react-native'; |
8 | -import {RESULTS} from './constants'; | |
9 | -import {Contract} from './contract'; | |
10 | -import {NotificationsResponse, Permission, PermissionStatus} from './types'; | |
11 | -import {uniq} from './utils'; | |
8 | +import { RESULTS, PERMISSIONS } from './constants'; | |
9 | +import { Contract } from './contract'; | |
10 | +import { NotificationsResponse, Permission, PermissionStatus } from './types'; | |
11 | +import { uniq } from './utils'; | |
12 | 12 | |
13 | 13 | const RNP: { |
14 | 14 | available: Permission[]; |
15 | 15 | |
16 | 16 | checkNotifications: () => Promise<NotificationsResponse>; |
17 | 17 | openSettings: () => Promise<true>; |
18 | + checkOrRequestOverlayPermission: () => Promise<true>; | |
18 | 19 | getNonRequestables: () => Promise<Permission[]>; |
19 | 20 | isNonRequestable: (permission: Permission) => Promise<boolean>; |
20 | 21 | setNonRequestable: (permission: Permission) => Promise<true>; |
... | ... | @@ -39,17 +40,23 @@ async function openSettings(): Promise<void> { |
39 | 40 | } |
40 | 41 | |
41 | 42 | async function check(permission: Permission): Promise<PermissionStatus> { |
42 | - if (!RNP.available.includes(permission)) { | |
43 | - return RESULTS.UNAVAILABLE; | |
44 | - } | |
43 | + if (permission == PERMISSIONS.ANDROID.MANAGE_OVERLAY_PERMISSION) { | |
44 | + return (await RNP.checkOrRequestOverlayPermission()) | |
45 | + ? RESULTS.BLOCKED | |
46 | + : RESULTS.DENIED; | |
47 | + } else { | |
48 | + if (!RNP.available.includes(permission)) { | |
49 | + return RESULTS.UNAVAILABLE; | |
50 | + } | |
45 | 51 | |
46 | - if (await Core.check(permission as CorePermission)) { | |
47 | - return RESULTS.GRANTED; | |
48 | - } | |
52 | + if (await Core.check(permission as CorePermission)) { | |
53 | + return RESULTS.GRANTED; | |
54 | + } | |
49 | 55 | |
50 | - return (await RNP.isNonRequestable(permission)) | |
51 | - ? RESULTS.BLOCKED | |
52 | - : RESULTS.DENIED; | |
56 | + return (await RNP.isNonRequestable(permission)) | |
57 | + ? RESULTS.BLOCKED | |
58 | + : RESULTS.DENIED; | |
59 | + } | |
53 | 60 | } |
54 | 61 | |
55 | 62 | async function request( |
... | ... | @@ -90,7 +97,7 @@ function splitByAvailability<P extends Permission[]>( |
90 | 97 | } |
91 | 98 | } |
92 | 99 | |
93 | - return {unavailable, available}; | |
100 | + return { unavailable, available }; | |
94 | 101 | } |
95 | 102 | |
96 | 103 | function checkNotifications(): Promise<NotificationsResponse> { |
... | ... | @@ -101,7 +108,7 @@ async function checkMultiple<P extends Permission[]>( |
101 | 108 | permissions: P, |
102 | 109 | ): Promise<Record<P[number], PermissionStatus>> { |
103 | 110 | const dedup = uniq(permissions); |
104 | - const {unavailable: output, available} = splitByAvailability(dedup); | |
111 | + const { unavailable: output, available } = splitByAvailability(dedup); | |
105 | 112 | const blocklist = await RNP.getNonRequestables(); |
106 | 113 | |
107 | 114 | await Promise.all( |
... | ... | @@ -111,8 +118,8 @@ async function checkMultiple<P extends Permission[]>( |
111 | 118 | output[permission] = granted |
112 | 119 | ? RESULTS.GRANTED |
113 | 120 | : blocklist.includes(permission) |
114 | - ? RESULTS.BLOCKED | |
115 | - : RESULTS.DENIED; | |
121 | + ? RESULTS.BLOCKED | |
122 | + : RESULTS.DENIED; | |
116 | 123 | }), |
117 | 124 | ); |
118 | 125 | |
... | ... | @@ -124,7 +131,7 @@ async function requestMultiple<P extends Permission[]>( |
124 | 131 | ): Promise<Record<P[number], PermissionStatus>> { |
125 | 132 | const toSetAsNonRequestable: Permission[] = []; |
126 | 133 | const dedup = uniq(permissions); |
127 | - const {unavailable: output, available} = splitByAvailability(dedup); | |
134 | + const { unavailable: output, available } = splitByAvailability(dedup); | |
128 | 135 | const statuses = await Core.requestMultiple(available as CorePermission[]); |
129 | 136 | |
130 | 137 | for (const permission in statuses) { |