1 package host.exp.exponent.utils 2 3 import android.Manifest 4 import host.exp.exponent.kernel.ExperienceKey 5 import javax.inject.Inject 6 import host.exp.exponent.kernel.services.ExpoKernelServiceRegistry 7 import host.exp.exponent.experience.ReactNativeActivity 8 import android.content.pm.PackageManager 9 import android.app.AlertDialog 10 import android.content.DialogInterface 11 import android.os.Build 12 import android.provider.Settings 13 import com.facebook.react.modules.core.PermissionListener 14 import host.exp.exponent.di.NativeModuleDepsProvider 15 import host.exp.expoview.Exponent 16 import host.exp.expoview.R 17 import java.util.* 18 19 class ScopedPermissionsRequester(private val experienceKey: ExperienceKey) { 20 @Inject 21 lateinit var expoKernelServiceRegistry: ExpoKernelServiceRegistry 22 23 private var permissionListener: PermissionListener? = null 24 private var experienceName: String? = null 25 private var permissionsResult = mutableMapOf<String, Int>() 26 private val permissionsToRequestPerExperience = mutableListOf<String>() 27 private val permissionsToRequestGlobally = mutableListOf<String>() 28 private var permissionsAskedCount = 0 29 30 fun requestPermissions( 31 currentActivity: ReactNativeActivity, 32 experienceName: String?, 33 permissions: Array<String>, 34 listener: PermissionListener 35 ) { 36 permissionListener = listener 37 this.experienceName = experienceName 38 permissionsResult = mutableMapOf() 39 40 for (permission in permissions) { 41 val globalStatus = currentActivity.checkSelfPermission(permission) 42 if (globalStatus == PackageManager.PERMISSION_DENIED) { 43 permissionsToRequestGlobally.add(permission) 44 } else if (!expoKernelServiceRegistry.permissionsKernelService.hasGrantedPermissions( 45 permission, 46 experienceKey 47 ) 48 ) { 49 permissionsToRequestPerExperience.add(permission) 50 } else { 51 permissionsResult[permission] = PackageManager.PERMISSION_GRANTED 52 } 53 } 54 55 if (permissionsToRequestPerExperience.isEmpty() && permissionsToRequestGlobally.isEmpty()) { 56 callPermissionsListener() 57 return 58 } 59 60 permissionsAskedCount = permissionsToRequestPerExperience.size 61 62 if (permissionsToRequestPerExperience.isNotEmpty()) { 63 requestExperienceAndGlobalPermissions(permissionsToRequestPerExperience[permissionsAskedCount - 1]) 64 } else if (permissionsToRequestGlobally.isNotEmpty()) { 65 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 66 currentActivity.requestPermissions( 67 permissionsToRequestGlobally.toTypedArray(), 68 EXPONENT_PERMISSIONS_REQUEST 69 ) 70 } else { 71 val result = IntArray(permissionsToRequestGlobally.size) 72 Arrays.fill(result, PackageManager.PERMISSION_DENIED) 73 onRequestPermissionsResult(permissionsToRequestGlobally.toTypedArray(), result) 74 } 75 } 76 } 77 78 fun onRequestPermissionsResult(permissions: Array<String>, grantResults: IntArray): Boolean { 79 if (permissionListener == null) { 80 // sometimes onRequestPermissionsResult is called multiple times if the first permission 81 // is rejected... 82 return true 83 } 84 85 if (grantResults.isNotEmpty()) { 86 for (i in grantResults.indices) { 87 if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { 88 expoKernelServiceRegistry.permissionsKernelService.grantScopedPermissions( 89 permissions[i], experienceKey 90 ) 91 } 92 permissionsResult[permissions[i]] = grantResults[i] 93 } 94 } 95 96 return callPermissionsListener() 97 } 98 99 private fun callPermissionsListener(): Boolean { 100 val permissions = permissionsResult.keys.toTypedArray() 101 val result = IntArray(permissions.size) 102 for (i in permissions.indices) { 103 result[i] = permissionsResult[permissions[i]]!! 104 } 105 return permissionListener!!.onRequestPermissionsResult( 106 EXPONENT_PERMISSIONS_REQUEST, 107 permissions, 108 result 109 ) 110 } 111 112 private fun requestExperienceAndGlobalPermissions(permission: String) { 113 val activity = Exponent.instance.currentActivity 114 val builder = AlertDialog.Builder(activity) 115 val onClickListener = PermissionsDialogOnClickListener(permission) 116 builder 117 .setMessage( 118 activity!!.getString( 119 R.string.experience_needs_permissions, 120 experienceName, 121 activity.getString(permissionToResId(permission)) 122 ) 123 ) 124 .setPositiveButton(R.string.allow_experience_permissions, onClickListener) 125 .setNegativeButton(R.string.deny_experience_permissions, onClickListener) 126 .show() 127 } 128 129 private fun permissionToResId(permission: String): Int { 130 return when (permission) { 131 Manifest.permission.CAMERA -> R.string.perm_camera 132 Manifest.permission.READ_CONTACTS -> R.string.perm_contacts_read 133 Manifest.permission.WRITE_CONTACTS -> R.string.perm_contacts_write 134 Manifest.permission.READ_EXTERNAL_STORAGE -> R.string.perm_camera_roll_read 135 Manifest.permission.WRITE_EXTERNAL_STORAGE -> R.string.perm_camera_roll_write 136 Manifest.permission.ACCESS_MEDIA_LOCATION -> R.string.perm_access_media_location 137 Manifest.permission.RECORD_AUDIO -> R.string.perm_audio_recording 138 Settings.ACTION_MANAGE_WRITE_SETTINGS -> R.string.perm_system_brightness 139 Manifest.permission.READ_CALENDAR -> R.string.perm_calendar_read 140 Manifest.permission.WRITE_CALENDAR -> R.string.perm_calendar_write 141 Manifest.permission.ACCESS_FINE_LOCATION -> R.string.perm_fine_location 142 Manifest.permission.ACCESS_COARSE_LOCATION -> R.string.perm_coarse_location 143 Manifest.permission.ACCESS_BACKGROUND_LOCATION -> R.string.perm_background_location 144 else -> -1 145 } 146 } 147 148 private inner class PermissionsDialogOnClickListener(private val permission: String) : DialogInterface.OnClickListener { 149 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() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 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