1 package host.exp.exponent.utils
2 
3 import android.Manifest
4 import android.app.AlertDialog
5 import android.content.DialogInterface
6 import android.content.pm.PackageManager
7 import android.provider.Settings
8 import com.facebook.react.modules.core.PermissionListener
9 import host.exp.exponent.di.NativeModuleDepsProvider
10 import host.exp.exponent.experience.ReactNativeActivity
11 import host.exp.exponent.kernel.ExperienceKey
12 import host.exp.exponent.kernel.services.ExpoKernelServiceRegistry
13 import host.exp.expoview.Exponent
14 import host.exp.expoview.R
15 import javax.inject.Inject
16 
17 class ScopedPermissionsRequester(private val experienceKey: ExperienceKey) {
18   @Inject
19   lateinit var expoKernelServiceRegistry: ExpoKernelServiceRegistry
20 
21   private var permissionListener: PermissionListener? = null
22   private var experienceName: String? = null
23   private var permissionsResult = mutableMapOf<String, Int>()
24   private val permissionsToRequestPerExperience = mutableListOf<String>()
25   private val permissionsToRequestGlobally = mutableListOf<String>()
26   private var permissionsAskedCount = 0
27 
requestPermissionsnull28   fun requestPermissions(
29     currentActivity: ReactNativeActivity,
30     experienceName: String?,
31     permissions: Array<String>,
32     listener: PermissionListener
33   ) {
34     permissionListener = listener
35     this.experienceName = experienceName
36     permissionsResult = mutableMapOf()
37 
38     for (permission in permissions) {
39       if (permission == null) {
40         continue
41       }
42       val globalStatus = currentActivity.checkSelfPermission(permission)
43       if (globalStatus == PackageManager.PERMISSION_DENIED) {
44         permissionsToRequestGlobally.add(permission)
45       } else if (!expoKernelServiceRegistry.permissionsKernelService.hasGrantedPermissions(
46           permission,
47           experienceKey
48         )
49       ) {
50         permissionsToRequestPerExperience.add(permission)
51       } else {
52         permissionsResult[permission] = PackageManager.PERMISSION_GRANTED
53       }
54     }
55 
56     if (permissionsToRequestPerExperience.isEmpty() && permissionsToRequestGlobally.isEmpty()) {
57       callPermissionsListener()
58       return
59     }
60 
61     permissionsAskedCount = permissionsToRequestPerExperience.size
62 
63     if (permissionsToRequestPerExperience.isNotEmpty()) {
64       requestExperienceAndGlobalPermissions(permissionsToRequestPerExperience[permissionsAskedCount - 1])
65     } else if (permissionsToRequestGlobally.isNotEmpty()) {
66       currentActivity.requestPermissions(
67         permissionsToRequestGlobally.toTypedArray(),
68         EXPONENT_PERMISSIONS_REQUEST
69       )
70     }
71   }
72 
onRequestPermissionsResultnull73   fun onRequestPermissionsResult(permissions: Array<String>, grantResults: IntArray): Boolean {
74     if (permissionListener == null) {
75       // sometimes onRequestPermissionsResult is called multiple times if the first permission
76       // is rejected...
77       return true
78     }
79 
80     if (grantResults.isNotEmpty()) {
81       for (i in grantResults.indices) {
82         if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
83           expoKernelServiceRegistry.permissionsKernelService.grantScopedPermissions(
84             permissions[i], experienceKey
85           )
86         }
87         permissionsResult[permissions[i]] = grantResults[i]
88       }
89     }
90 
91     return callPermissionsListener()
92   }
93 
callPermissionsListenernull94   private fun callPermissionsListener(): Boolean {
95     val permissions = permissionsResult.keys.toTypedArray()
96     val result = IntArray(permissions.size)
97     for (i in permissions.indices) {
98       result[i] = permissionsResult[permissions[i]]!!
99     }
100     return permissionListener!!.onRequestPermissionsResult(
101       EXPONENT_PERMISSIONS_REQUEST,
102       permissions,
103       result
104     )
105   }
106 
requestExperienceAndGlobalPermissionsnull107   private fun requestExperienceAndGlobalPermissions(permission: String) {
108     val activity = Exponent.instance.currentActivity
109     val builder = AlertDialog.Builder(activity)
110     val onClickListener = PermissionsDialogOnClickListener(permission)
111     builder
112       .setMessage(
113         activity!!.getString(
114           R.string.experience_needs_permissions,
115           experienceName,
116           activity.getString(permissionToResId(permission))
117         )
118       )
119       .setPositiveButton(R.string.allow_experience_permissions, onClickListener)
120       .setNegativeButton(R.string.deny_experience_permissions, onClickListener)
121       .show()
122   }
123 
permissionToResIdnull124   private fun permissionToResId(permission: String): Int {
125     return when (permission) {
126       Manifest.permission.CAMERA -> R.string.perm_camera
127       Manifest.permission.READ_CONTACTS -> R.string.perm_contacts_read
128       Manifest.permission.WRITE_CONTACTS -> R.string.perm_contacts_write
129       Manifest.permission.READ_EXTERNAL_STORAGE -> R.string.perm_camera_roll_read
130       Manifest.permission.WRITE_EXTERNAL_STORAGE -> R.string.perm_camera_roll_write
131       Manifest.permission.ACCESS_MEDIA_LOCATION -> R.string.perm_access_media_location
132       Manifest.permission.RECORD_AUDIO -> R.string.perm_audio_recording
133       Settings.ACTION_MANAGE_WRITE_SETTINGS -> R.string.perm_system_brightness
134       Manifest.permission.READ_CALENDAR -> R.string.perm_calendar_read
135       Manifest.permission.WRITE_CALENDAR -> R.string.perm_calendar_write
136       Manifest.permission.ACCESS_FINE_LOCATION -> R.string.perm_fine_location
137       Manifest.permission.ACCESS_COARSE_LOCATION -> R.string.perm_coarse_location
138       Manifest.permission.ACCESS_BACKGROUND_LOCATION -> R.string.perm_background_location
139       Manifest.permission.READ_PHONE_STATE -> R.string.perm_read_phone_state
140       Manifest.permission.READ_MEDIA_IMAGES -> R.string.perm_read_media_images
141       Manifest.permission.READ_MEDIA_VIDEO -> R.string.perm_read_media_videos
142       Manifest.permission.READ_MEDIA_AUDIO -> R.string.perm_read_media_audio
143       Manifest.permission.POST_NOTIFICATIONS -> R.string.perm_notifications
144       else -> -1
145     }
146   }
147 
148   private inner class PermissionsDialogOnClickListener(private val permission: String) : DialogInterface.OnClickListener {
onClicknull149     override fun onClick(dialog: DialogInterface, which: Int) {
150       permissionsAskedCount -= 1
151       when (which) {
152         DialogInterface.BUTTON_POSITIVE -> {
153           expoKernelServiceRegistry.permissionsKernelService.grantScopedPermissions(
154             permission,
155             this@ScopedPermissionsRequester.experienceKey
156           )
157           permissionsResult[permission] = PackageManager.PERMISSION_GRANTED
158         }
159         DialogInterface.BUTTON_NEGATIVE -> {
160           expoKernelServiceRegistry.permissionsKernelService.revokeScopedPermissions(
161             permission,
162             experienceKey
163           )
164           permissionsResult[permission] = PackageManager.PERMISSION_DENIED
165         }
166       }
167 
168       if (permissionsAskedCount > 0) {
169         requestExperienceAndGlobalPermissions(permissionsToRequestPerExperience[permissionsAskedCount - 1])
170       } else if (permissionsToRequestGlobally.isNotEmpty()) {
171         Exponent.instance.currentActivity!!.requestPermissions(
172           permissionsToRequestGlobally.toTypedArray(),
173           EXPONENT_PERMISSIONS_REQUEST
174         )
175       } else {
176         callPermissionsListener()
177       }
178     }
179   }
180 
181   companion object {
182     const val EXPONENT_PERMISSIONS_REQUEST = 13
183   }
184 
185   init {
186     NativeModuleDepsProvider.instance.inject(ScopedPermissionsRequester::class.java, this)
187   }
188 }
189