Commit cfb099010655a04a65c0f625f17e66a3a22efc74

Authored by Thanh Phạm

Merge branch 'develop' into 'master'

Develop

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 }
... ... @@ -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&lt;void&gt; {
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&lt;P extends Permission[]&gt;(
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&lt;P extends Permission[]&gt;(
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&lt;P extends Permission[]&gt;(
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&lt;P extends Permission[]&gt;(
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) {