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