From 5ab3ffccf3f085cdc3194bebdcaa953277fd6179 Mon Sep 17 00:00:00 2001 From: Maurits van Beusekom Date: Mon, 23 Jul 2018 17:35:01 +0200 Subject: [PATCH] Implement API to request permissions on Android --- .../PermissionHandlerPlugin.kt | 585 +++++++++++------- .../baseflow/permissionhandler/utils/Codec.kt | 18 +- .../.idea/caches/build_file_checksums.ser | Bin 655 -> 655 bytes example/ios/Runner.xcodeproj/project.pbxproj | 60 ++ .../contents.xcworkspacedata | 3 + example/lib/main.dart | 7 + lib/permission_handler.dart | 17 +- lib/utils/codec.dart | 17 + 8 files changed, 474 insertions(+), 233 deletions(-) diff --git a/android/src/main/kotlin/com/baseflow/permissionhandler/PermissionHandlerPlugin.kt b/android/src/main/kotlin/com/baseflow/permissionhandler/PermissionHandlerPlugin.kt index d3a113d7f..81d274028 100644 --- a/android/src/main/kotlin/com/baseflow/permissionhandler/PermissionHandlerPlugin.kt +++ b/android/src/main/kotlin/com/baseflow/permissionhandler/PermissionHandlerPlugin.kt @@ -2,9 +2,11 @@ package com.baseflow.permissionhandler import android.Manifest import android.content.Context +import android.content.Intent import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.os.Build +import android.support.v4.app.ActivityCompat import android.support.v4.content.ContextCompat import android.util.Log import com.baseflow.permissionhandler.data.PermissionGroup @@ -14,267 +16,388 @@ import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.PluginRegistry import io.flutter.plugin.common.PluginRegistry.Registrar -class PermissionHandlerPlugin(private val registrar: Registrar, private var requestedPermissions: MutableList? = null): MethodCallHandler { +class PermissionHandlerPlugin(private val registrar: Registrar, private var requestedPermissions: MutableList? = null) : MethodCallHandler { + private var mRequestResults = mutableMapOf() + private var mResult: Result? = null - companion object { - @JvmStatic private val logTag = "permissions_handler" + companion object { + const val permissionCode = 25 - @JvmStatic - fun registerWith(registrar: Registrar) { - val channel = MethodChannel(registrar.messenger(), "flutter.baseflow.com/permissions/methods") - channel.setMethodCallHandler(PermissionHandlerPlugin(registrar)) + @JvmStatic + private val mLogTag = "permissions_handler" + + @JvmStatic + fun registerWith(registrar: Registrar) { + val channel = MethodChannel(registrar.messenger(), "flutter.baseflow.com/permissions/methods") + val instance = PermissionHandlerPlugin(registrar) + channel.setMethodCallHandler(instance) + + registrar.addRequestPermissionsResultListener(PluginRegistry.RequestPermissionsResultListener { id, permissions, grantResults -> + if (id == permissionCode) { + instance.handlePermissionsRequest(permissions, grantResults) + + return@RequestPermissionsResultListener true + } + + return@RequestPermissionsResultListener false + }) + } + + @JvmStatic + fun parseManifestName(permission: String): PermissionGroup { + + when (permission) { + Manifest.permission.READ_CALENDAR, + Manifest.permission.WRITE_CALENDAR -> + return PermissionGroup.CALENDAR + Manifest.permission.CAMERA -> + return PermissionGroup.CAMERA + Manifest.permission.READ_CONTACTS, + Manifest.permission.WRITE_CONTACTS, + Manifest.permission.GET_ACCOUNTS -> + return PermissionGroup.CONTACTS + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION -> + return PermissionGroup.LOCATION + Manifest.permission.RECORD_AUDIO -> + return PermissionGroup.MICROPHONE + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.CALL_PHONE, + Manifest.permission.READ_CALL_LOG, + Manifest.permission.WRITE_CALL_LOG, + Manifest.permission.ADD_VOICEMAIL, + Manifest.permission.USE_SIP, + Manifest.permission.PROCESS_OUTGOING_CALLS -> + return PermissionGroup.PHONE + Manifest.permission.BODY_SENSORS -> + return PermissionGroup.SENSORS + Manifest.permission.SEND_SMS, + Manifest.permission.RECEIVE_SMS, + Manifest.permission.READ_SMS, + Manifest.permission.RECEIVE_WAP_PUSH, + Manifest.permission.RECEIVE_MMS -> + return PermissionGroup.SMS + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE -> + return PermissionGroup.STORAGE + } + + return PermissionGroup.UNKNOWN + } } - @JvmStatic - fun parseManifestName(permission: String) : PermissionGroup - { - - when (permission) { - Manifest.permission.READ_CALENDAR, - Manifest.permission.WRITE_CALENDAR -> - return PermissionGroup.CALENDAR - Manifest.permission.CAMERA -> - return PermissionGroup.CAMERA - Manifest.permission.READ_CONTACTS, - Manifest.permission.WRITE_CONTACTS, - Manifest.permission.GET_ACCOUNTS -> - return PermissionGroup.CONTACTS - Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION -> - return PermissionGroup.LOCATION - Manifest.permission.RECORD_AUDIO -> - return PermissionGroup.MICROPHONE - Manifest.permission.READ_PHONE_STATE, - Manifest.permission.CALL_PHONE, - Manifest.permission.READ_CALL_LOG, - Manifest.permission.WRITE_CALL_LOG, - Manifest.permission.ADD_VOICEMAIL, - Manifest.permission.USE_SIP, - Manifest.permission.PROCESS_OUTGOING_CALLS -> - return PermissionGroup.PHONE - Manifest.permission.BODY_SENSORS -> - return PermissionGroup.SENSORS - Manifest.permission.SEND_SMS, - Manifest.permission.RECEIVE_SMS, - Manifest.permission.READ_SMS, - Manifest.permission.RECEIVE_WAP_PUSH, - Manifest.permission.RECEIVE_MMS -> - return PermissionGroup.SMS - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE -> - return PermissionGroup.STORAGE - } - - return PermissionGroup.UNKNOWN + override fun onMethodCall(call: MethodCall, result: Result) { + when { + call.method == "checkPermissionStatus" -> { + val permission = Codec.decodePermissionGroup(call.arguments) + val permissionStatus = checkPermissionStatus(permission) + handleSuccess(permissionStatus, result) + } + call.method == "requestPermissions" -> { + if (mResult != null) { + result.error( + "ERROR_ALREADY_REQUESTED_PERMISSIONS", + "A request for permissions is already running, please wait for it to finish before doing another request (note that you can request multiple permissions at the same time).", + null) + } + + mResult = result + val permissions = Codec.decodePermissionGroups(call.arguments) + requestPermissions(permissions) + } + call.method == "openAppSettings" -> { + val isOpen = openAppSettings() + result.success(isOpen) + } + else -> result.notImplemented() + } + } + + private fun checkPermissionStatus(permission: PermissionGroup): PermissionStatus { + val names = getManifestNames(permission) + + if (names == null) { + Log.d(mLogTag, "No android specific permissions needed for: $permission") + + return PermissionStatus.GRANTED + } + + //if no permissions were found then there is an issue and permission is not set in Android manifest + if (names.count() == 0) { + Log.d(mLogTag, "No permissions found in manifest for: $permission") + return PermissionStatus.UNKNOWN + } + + val context: Context? = registrar.activity() ?: registrar.activeContext() + if (context == null) { + Log.d(mLogTag, "Unable to detect current Activity or App Context.") + return PermissionStatus.UNKNOWN + } + + val targetsMOrHigher = context.applicationInfo.targetSdkVersion >= android.os.Build.VERSION_CODES.M + + for (name in names) { + if (targetsMOrHigher && ContextCompat.checkSelfPermission(context, name) != PackageManager.PERMISSION_GRANTED) { + return PermissionStatus.DENIED + } + } + + return PermissionStatus.GRANTED } - } - - override fun onMethodCall(call: MethodCall, result: Result) { - if (call.method == "checkPermissionStatus") { - val permission = Codec.decodePermissionGroup(call.arguments) - checkPermissionStatus(permission, result) - } else { - result.notImplemented() + + private fun requestPermissions(permissions: Array) { + if (registrar.activity() == null) { + Log.d(mLogTag, "Unable to detect current Activity.") + + for (permission in permissions) { + mRequestResults[permission] = PermissionStatus.UNKNOWN + } + + processResult() + return + } + + val permissionsToRequest = mutableListOf() + for (permission in permissions) { + val permissionStatus = checkPermissionStatus(permission) + + if (permissionStatus != PermissionStatus.GRANTED) { + val names = getManifestNames(permission) + + //check to see if we can find manifest names + //if we can't add as unknown and continue + if (names == null || names.isEmpty()) { + if (!mRequestResults.containsKey(permission)) { + mRequestResults[permission] = PermissionStatus.UNKNOWN + } + + continue + } + + names.let { permissionsToRequest.addAll(it) } + } else { + if (!mRequestResults.containsKey(permission)) { + mRequestResults[permission] = PermissionStatus.GRANTED + } + } + } + + ActivityCompat.requestPermissions( + registrar.activity(), + permissionsToRequest.toTypedArray(), + permissionCode) } - } - private fun checkPermissionStatus(permission: PermissionGroup, result: Result) - { - val names = getManifestNames(permission) + private fun handlePermissionsRequest(permissions: Array, grantResults: IntArray) { + if (mResult == null) { + return + } + + for (i in permissions.indices) { + val permission = parseManifestName(permissions[i]) + if (permission == PermissionGroup.UNKNOWN) + continue + + if (permission == PermissionGroup.MICROPHONE) { + if (!mRequestResults.containsKey(PermissionGroup.SPEECH)) { + mRequestResults[PermissionGroup.SPEECH] = grantResults[i].toPermissionStatus() + } + } else if (permission == PermissionGroup.LOCATION) { + if (!mRequestResults.containsKey(PermissionGroup.LOCATION_ALWAYS)) { + mRequestResults[PermissionGroup.LOCATION_ALWAYS] = grantResults[i].toPermissionStatus() + } + + if (!mRequestResults.containsKey(PermissionGroup.LOCATION_WHEN_IN_USE)) { + mRequestResults[PermissionGroup.LOCATION_WHEN_IN_USE] = grantResults[i].toPermissionStatus() + } + } + + if (!mRequestResults.containsKey(permission)) { + mRequestResults[permission] = grantResults[i].toPermissionStatus() + } - if (names == null) - { - Log.d(logTag, "No android specific permissions needed for: $permission") - handleSuccess(PermissionStatus.GRANTED, result) - return + } + + processResult() } - //if no permissions were found then there is an issue and persmission is not set in Android manifest - if (names.count() == 0) - { - Log.d(logTag, "No permissions found in manifest for: $permission") - handleSuccess(PermissionStatus.UNKNOWN, result) - return + private fun Int.toPermissionStatus(): PermissionStatus { + return if (this == PackageManager.PERMISSION_GRANTED) PermissionStatus.GRANTED else PermissionStatus.DENIED } - val context: Context? = registrar.activity() ?: registrar.activeContext() - if (context == null) - { - Log.d(logTag, "Unable to detect current Activity or App Context. Please ensure Plugin.CurrentActivity is installed in your Android project and your Application class is registering with Application.IActivityLifecycleCallbacks.") - handleSuccess(PermissionStatus.UNKNOWN, result) - return + private fun processResult() { + mResult?.success(Codec.encodePermissionRequestResult(mRequestResults)) + + mRequestResults.clear() + mResult = null } - val targetsMOrHigher = context.applicationInfo.targetSdkVersion >= android.os.Build.VERSION_CODES.M + private fun openAppSettings(): Boolean { + val context: Context? = registrar.activity() ?: registrar.activeContext() + if (context == null) { + Log.d(mLogTag, "Unable to detect current Activity or App Context.") + return false + } + + try { + var settingsIntent = Intent() + settingsIntent.action = android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS + settingsIntent.addCategory(Intent.CATEGORY_DEFAULT) + settingsIntent.data = android.net.Uri.parse("package:" + context.packageName) + settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) + settingsIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + + context.startActivity(settingsIntent) - for (name in names) - { - if (targetsMOrHigher && ContextCompat.checkSelfPermission(context, name) != PackageManager.PERMISSION_GRANTED) { - handleSuccess(PermissionStatus.DENIED, result) - return - } + return true + } catch(ex: Exception) { + return false + } } - handleSuccess(PermissionStatus.GRANTED, result) - } - - private fun getManifestNames(permission: PermissionGroup) : List? - { - val permissionNames : MutableList = mutableListOf() - - when(permission) { - PermissionGroup.CALENDAR -> - { - if(hasPermissionInManifest(Manifest.permission.READ_CALENDAR)) - permissionNames.add(Manifest.permission.READ_CALENDAR) - if(hasPermissionInManifest(Manifest.permission.WRITE_CALENDAR)) - permissionNames.add(Manifest.permission.WRITE_CALENDAR) - } - PermissionGroup.CAMERA -> - { - if(hasPermissionInManifest(Manifest.permission.CAMERA)) - permissionNames.add(Manifest.permission.CAMERA) - } - PermissionGroup.CONTACTS -> - { - if(hasPermissionInManifest(Manifest.permission.READ_CONTACTS)) - permissionNames.add(Manifest.permission.READ_CONTACTS) - - if(hasPermissionInManifest(Manifest.permission.WRITE_CONTACTS)) - permissionNames.add(Manifest.permission.WRITE_CONTACTS) - - if(hasPermissionInManifest(Manifest.permission.GET_ACCOUNTS)) - permissionNames.add(Manifest.permission.GET_ACCOUNTS) - } - PermissionGroup.LOCATION_ALWAYS, - PermissionGroup.LOCATION_WHEN_IN_USE, - PermissionGroup.LOCATION -> - { - if(hasPermissionInManifest(Manifest.permission.ACCESS_COARSE_LOCATION)) - permissionNames.add(Manifest.permission.ACCESS_COARSE_LOCATION) - - - if(hasPermissionInManifest(Manifest.permission.ACCESS_FINE_LOCATION)) - permissionNames.add(Manifest.permission.ACCESS_FINE_LOCATION) - } - - PermissionGroup.SPEECH, - PermissionGroup.MICROPHONE -> - { - if(hasPermissionInManifest(Manifest.permission.RECORD_AUDIO)) - permissionNames.add(Manifest.permission.RECORD_AUDIO) - - } - - PermissionGroup.PHONE -> - { - if(hasPermissionInManifest(Manifest.permission.READ_PHONE_STATE)) - permissionNames.add(Manifest.permission.READ_PHONE_STATE) - - if(hasPermissionInManifest(Manifest.permission.CALL_PHONE)) - permissionNames.add(Manifest.permission.CALL_PHONE) - - if(hasPermissionInManifest(Manifest.permission.READ_CALL_LOG)) - permissionNames.add(Manifest.permission.READ_CALL_LOG) - - if(hasPermissionInManifest(Manifest.permission.WRITE_CALL_LOG)) - permissionNames.add(Manifest.permission.WRITE_CALL_LOG) - - if(hasPermissionInManifest(Manifest.permission.ADD_VOICEMAIL)) - permissionNames.add(Manifest.permission.ADD_VOICEMAIL) - - if(hasPermissionInManifest(Manifest.permission.USE_SIP)) - permissionNames.add(Manifest.permission.USE_SIP) - - if(hasPermissionInManifest(Manifest.permission.PROCESS_OUTGOING_CALLS)) - permissionNames.add(Manifest.permission.PROCESS_OUTGOING_CALLS) - } - PermissionGroup.SENSORS -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { - if (hasPermissionInManifest(Manifest.permission.BODY_SENSORS)) { - permissionNames.add(Manifest.permission.BODY_SENSORS) - } + private fun getManifestNames(permission: PermissionGroup): List? { + val permissionNames: MutableList = mutableListOf() + + when (permission) { + PermissionGroup.CALENDAR -> { + if (hasPermissionInManifest(Manifest.permission.READ_CALENDAR)) + permissionNames.add(Manifest.permission.READ_CALENDAR) + if (hasPermissionInManifest(Manifest.permission.WRITE_CALENDAR)) + permissionNames.add(Manifest.permission.WRITE_CALENDAR) + } + PermissionGroup.CAMERA -> { + if (hasPermissionInManifest(Manifest.permission.CAMERA)) + permissionNames.add(Manifest.permission.CAMERA) + } + PermissionGroup.CONTACTS -> { + if (hasPermissionInManifest(Manifest.permission.READ_CONTACTS)) + permissionNames.add(Manifest.permission.READ_CONTACTS) + + if (hasPermissionInManifest(Manifest.permission.WRITE_CONTACTS)) + permissionNames.add(Manifest.permission.WRITE_CONTACTS) + + if (hasPermissionInManifest(Manifest.permission.GET_ACCOUNTS)) + permissionNames.add(Manifest.permission.GET_ACCOUNTS) + } + PermissionGroup.LOCATION_ALWAYS, + PermissionGroup.LOCATION_WHEN_IN_USE, + PermissionGroup.LOCATION -> { + if (hasPermissionInManifest(Manifest.permission.ACCESS_COARSE_LOCATION)) + permissionNames.add(Manifest.permission.ACCESS_COARSE_LOCATION) + + + if (hasPermissionInManifest(Manifest.permission.ACCESS_FINE_LOCATION)) + permissionNames.add(Manifest.permission.ACCESS_FINE_LOCATION) + } + + PermissionGroup.SPEECH, + PermissionGroup.MICROPHONE -> { + if (hasPermissionInManifest(Manifest.permission.RECORD_AUDIO)) + permissionNames.add(Manifest.permission.RECORD_AUDIO) + + } + + PermissionGroup.PHONE -> { + if (hasPermissionInManifest(Manifest.permission.READ_PHONE_STATE)) + permissionNames.add(Manifest.permission.READ_PHONE_STATE) + + if (hasPermissionInManifest(Manifest.permission.CALL_PHONE)) + permissionNames.add(Manifest.permission.CALL_PHONE) + + if (hasPermissionInManifest(Manifest.permission.READ_CALL_LOG)) + permissionNames.add(Manifest.permission.READ_CALL_LOG) + + if (hasPermissionInManifest(Manifest.permission.WRITE_CALL_LOG)) + permissionNames.add(Manifest.permission.WRITE_CALL_LOG) + + if (hasPermissionInManifest(Manifest.permission.ADD_VOICEMAIL)) + permissionNames.add(Manifest.permission.ADD_VOICEMAIL) + + if (hasPermissionInManifest(Manifest.permission.USE_SIP)) + permissionNames.add(Manifest.permission.USE_SIP) + + if (hasPermissionInManifest(Manifest.permission.PROCESS_OUTGOING_CALLS)) + permissionNames.add(Manifest.permission.PROCESS_OUTGOING_CALLS) + } + PermissionGroup.SENSORS -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { + if (hasPermissionInManifest(Manifest.permission.BODY_SENSORS)) { + permissionNames.add(Manifest.permission.BODY_SENSORS) + } + } + } + + PermissionGroup.SMS -> { + if (hasPermissionInManifest(Manifest.permission.SEND_SMS)) + permissionNames.add(Manifest.permission.SEND_SMS) + + if (hasPermissionInManifest(Manifest.permission.RECEIVE_SMS)) + permissionNames.add(Manifest.permission.RECEIVE_SMS) + + if (hasPermissionInManifest(Manifest.permission.READ_SMS)) + permissionNames.add(Manifest.permission.READ_SMS) + + if (hasPermissionInManifest(Manifest.permission.RECEIVE_WAP_PUSH)) + permissionNames.add(Manifest.permission.RECEIVE_WAP_PUSH) + + if (hasPermissionInManifest(Manifest.permission.RECEIVE_MMS)) + permissionNames.add(Manifest.permission.RECEIVE_MMS) + } + + PermissionGroup.STORAGE -> { + if (hasPermissionInManifest(Manifest.permission.READ_EXTERNAL_STORAGE)) + permissionNames.add(Manifest.permission.READ_EXTERNAL_STORAGE) + + if (hasPermissionInManifest(Manifest.permission.WRITE_EXTERNAL_STORAGE)) + permissionNames.add(Manifest.permission.WRITE_EXTERNAL_STORAGE) + } + else -> return null } - } - - PermissionGroup.SMS -> - { - if(hasPermissionInManifest(Manifest.permission.SEND_SMS)) - permissionNames.add(Manifest.permission.SEND_SMS) - - if(hasPermissionInManifest(Manifest.permission.RECEIVE_SMS)) - permissionNames.add(Manifest.permission.RECEIVE_SMS) - - if(hasPermissionInManifest(Manifest.permission.READ_SMS)) - permissionNames.add(Manifest.permission.READ_SMS) - - if(hasPermissionInManifest(Manifest.permission.RECEIVE_WAP_PUSH)) - permissionNames.add(Manifest.permission.RECEIVE_WAP_PUSH) - - if(hasPermissionInManifest(Manifest.permission.RECEIVE_MMS)) - permissionNames.add(Manifest.permission.RECEIVE_MMS) - } - - PermissionGroup.STORAGE -> - { - if(hasPermissionInManifest(Manifest.permission.READ_EXTERNAL_STORAGE)) - permissionNames.add(Manifest.permission.READ_EXTERNAL_STORAGE) - - if(hasPermissionInManifest(Manifest.permission.WRITE_EXTERNAL_STORAGE)) - permissionNames.add(Manifest.permission.WRITE_EXTERNAL_STORAGE) - } - else -> return null + + return permissionNames } - return permissionNames - } + private fun hasPermissionInManifest(permission: String): Boolean { + try { + requestedPermissions?.let { + return it.any { r -> r.equals(permission, true) } + } - private fun hasPermissionInManifest(permission: String) : Boolean - { - try - { - requestedPermissions?.let { - return it.any { r -> r.equals(permission, true) } - } + val context: Context? = registrar.activity() ?: registrar.activeContext() - val context: Context? = registrar.activity() ?: registrar.activeContext() + if (context == null) { + Log.d(mLogTag, "Unable to detect current Activity or App Context.") + return false + } - if (context == null) - { - Log.d(logTag, "Unable to detect current Activity or App Context. Please ensure Plugin.CurrentActivity is installed in your Android project and your Application class is registering with Application.IActivityLifecycleCallbacks.") - return false - } + val info: PackageInfo? = context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS) - val info: PackageInfo? = context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS) + if (info == null) { + Log.d(mLogTag, "Unable to get Package info, will not be able to determine permissions to request.") + return false + } - if(info == null) - { - Log.d(logTag, "Unable to get Package info, will not be able to determine permissions to request.") - return false - } + requestedPermissions = info.requestedPermissions.toMutableList() - requestedPermissions = info.requestedPermissions.toMutableList() + if (requestedPermissions == null) { + Log.d(mLogTag, "There are no requested permissions, please check to ensure you have marked permissions you want to request.") + return false + } - if (requestedPermissions == null) - { - Log.d(logTag, "There are no requested permissions, please check to ensure you have marked permissions you want to request.") + requestedPermissions?.let { + return it.any { r -> r.equals(permission, true) } + } ?: return false + } catch (ex: Exception) { + Log.d(mLogTag, "Unable to check manifest for permission: $ex") + } return false - } - - requestedPermissions?.let { - return it.any { r -> r.equals(permission, true) } - } ?: return false } - catch(ex: Exception) - { - Log.d(logTag,"Unable to check manifest for permission: $ex") - } - return false - } - private fun handleSuccess(permissionStatus: PermissionStatus, result: Result) { - result.success(Codec.encodePermissionStatus(permissionStatus)) - } + private fun handleSuccess(permissionStatus: PermissionStatus, result: Result?) { + result?.success(Codec.encodePermissionStatus(permissionStatus)) + } } diff --git a/android/src/main/kotlin/com/baseflow/permissionhandler/utils/Codec.kt b/android/src/main/kotlin/com/baseflow/permissionhandler/utils/Codec.kt index 6434eee9d..828e029ef 100644 --- a/android/src/main/kotlin/com/baseflow/permissionhandler/utils/Codec.kt +++ b/android/src/main/kotlin/com/baseflow/permissionhandler/utils/Codec.kt @@ -3,19 +3,35 @@ package com.baseflow.permissionhandler.utils import com.baseflow.permissionhandler.data.PermissionGroup import com.baseflow.permissionhandler.data.PermissionStatus import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.reflect.TypeToken class Codec { companion object { - @JvmStatic val gsonDecoder : Gson = Gson() + @JvmStatic + private val gsonDecoder : Gson = GsonBuilder().enableComplexMapKeySerialization().create() @JvmStatic fun decodePermissionGroup(arguments: Any) : PermissionGroup { return Codec.gsonDecoder.fromJson(arguments.toString(), PermissionGroup::class.java) } + @JvmStatic + fun decodePermissionGroups(arguments: Any) : Array { + + var permissionGroupsType = object: TypeToken>() {}.type + return Codec.gsonDecoder.fromJson(arguments.toString(), permissionGroupsType) + } + @JvmStatic fun encodePermissionStatus(permissionStatus: PermissionStatus) : String { return gsonDecoder.toJson(permissionStatus) } + + @JvmStatic + fun encodePermissionRequestResult(permissionResults: Map) : String { + val jsonString = gsonDecoder.toJson(permissionResults) + return jsonString + } } } \ No newline at end of file diff --git a/example/android/.idea/caches/build_file_checksums.ser b/example/android/.idea/caches/build_file_checksums.ser index 47ade256b7b29396f2d5d2f38c2d113f7cda52c0..cbe3acd3bc37eacac532d37bdcbd5fd5d9d8790c 100644 GIT binary patch delta 15 XcmeBY?Pr}ZgXQ?re{LJ+d}agyH3bJo delta 15 XcmeBY?Pr}ZgJp;HfryQBJ~ILUFpUPG diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index a1f55e647..2bbcc85f6 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + F83E7534C5D827024000AB4C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 36F84C9A972D968241208A7F /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -41,6 +42,7 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; + 36F84C9A972D968241208A7F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; @@ -63,12 +65,21 @@ files = ( 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + F83E7534C5D827024000AB4C /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1850378EF7B1AE3FA40E4F87 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 36F84C9A972D968241208A7F /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -89,6 +100,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + A2B39E4F789ABF9C57736776 /* Pods */, + 1850378EF7B1AE3FA40E4F87 /* Frameworks */, ); sourceTree = ""; }; @@ -123,6 +136,13 @@ name = "Supporting Files"; sourceTree = ""; }; + A2B39E4F789ABF9C57736776 /* Pods */ = { + isa = PBXGroup; + children = ( + ); + name = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -130,12 +150,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + E8ED1A28A9A0730EDF0565B2 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 259652FD16940B446BA7C6C6 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -197,6 +219,26 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 259652FD16940B446BA7C6C6 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", + "${BUILT_PRODUCTS_DIR}/permission_handler/permission_handler.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/permission_handler.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -225,6 +267,24 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + E8ED1A28A9A0730EDF0565B2 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16e..21a3cc14c 100644 --- a/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/example/lib/main.dart b/example/lib/main.dart index 35048a2b9..ed978e578 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -27,6 +27,13 @@ class _MyAppState extends State { // Platform messages may fail, so we use a try/catch PlatformException. try { permissionStatus = await PermissionHandler.checkPermissionStatus(PermissionGroup.calendar); + + if(permissionStatus != PermissionStatus.granted){ + var permissions = await PermissionHandler.requestPermissions([PermissionGroup.calendar]); + if(permissions.containsKey(PermissionGroup.calendar)) { + permissionStatus = permissions[PermissionGroup.calendar]; + } + } } on PlatformException { permissionStatus = PermissionStatus.unknown; } diff --git a/lib/permission_handler.dart b/lib/permission_handler.dart index b71345c90..f7e1c2e0b 100644 --- a/lib/permission_handler.dart +++ b/lib/permission_handler.dart @@ -11,7 +11,22 @@ class PermissionHandler { /// Returns a [Future] containing the current permission status for the supplied [PermissionGroup]. static Future checkPermissionStatus(PermissionGroup permission) async { - final status = await _channel.invokeMethod('checkPermissionStatus', Codec.encodePermissionGroup(permission)); + final status = await _channel.invokeMethod( + 'checkPermissionStatus', + Codec.encodePermissionGroup(permission)); + return Codec.decodePermissionStatus(status); } + + static Future openAppSettings() async => + await _channel.invokeMethod("openAppSettings"); + + static Future> requestPermissions(List permissions) async { + final jsonData = Codec.encodePermissionGroups(permissions); + final status = await _channel.invokeMethod( + 'requestPermissions', + jsonData); + + return Codec.decodePermissionRequestResult(status); + } } diff --git a/lib/utils/codec.dart b/lib/utils/codec.dart index 4238b9de6..d91745d9e 100644 --- a/lib/utils/codec.dart +++ b/lib/utils/codec.dart @@ -8,10 +8,27 @@ class Codec { return PermissionStatus.values.firstWhere((e) => e.toString().split('.').last == permission); } + + static Map decodePermissionRequestResult(dynamic value) { + final jsonObject = json.decode(value); + + final permissionResults = Map(); + jsonObject.forEach((key, value) { + final permissionGroup = PermissionGroup.values.firstWhere((e) => e.toString().split('.').last == key.toString()); + final permissionStatus = PermissionStatus.values.firstWhere((e) => e.toString().split('.').last == value.toString()); + + permissionResults[permissionGroup] = permissionStatus; + }); + + return permissionResults; + } static String encodePermissionGroup(PermissionGroup permissionGroup) => json.encode(_encodeEnum(permissionGroup)); + static String encodePermissionGroups(List permissions) => + json.encode(permissions.map((p) => _encodeEnum(p)).toList()); + static String _encodeEnum(dynamic value) { return value.toString().split('.').last; }