Commit 1c81b9a7 by Maurits van Beusekom Committed by GitHub

Merge pull request #2 from BaseflowIT/feature/android_should_show_rationale

Request to see if you should show a rationale for requesting permission (only for Android)
parents afe7f77d b3439372
...@@ -2,9 +2,11 @@ package com.baseflow.permissionhandler ...@@ -2,9 +2,11 @@ package com.baseflow.permissionhandler
import android.Manifest import android.Manifest
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.util.Log import android.util.Log
import com.baseflow.permissionhandler.data.PermissionGroup import com.baseflow.permissionhandler.data.PermissionGroup
...@@ -14,22 +16,38 @@ import io.flutter.plugin.common.MethodChannel ...@@ -14,22 +16,38 @@ import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.Registrar import io.flutter.plugin.common.PluginRegistry.Registrar
class PermissionHandlerPlugin(private val registrar: Registrar, private var requestedPermissions: MutableList<String>? = null): MethodCallHandler { class PermissionHandlerPlugin(private val registrar: Registrar, private var requestedPermissions: MutableList<String>? = null) : MethodCallHandler {
private var mRequestResults = mutableMapOf<PermissionGroup, PermissionStatus>()
private var mResult: Result? = null
companion object { companion object {
@JvmStatic private val logTag = "permissions_handler" const val permissionCode = 25
@JvmStatic
private val mLogTag = "permissions_handler"
@JvmStatic @JvmStatic
fun registerWith(registrar: Registrar) { fun registerWith(registrar: Registrar) {
val channel = MethodChannel(registrar.messenger(), "flutter.baseflow.com/permissions/methods") val channel = MethodChannel(registrar.messenger(), "flutter.baseflow.com/permissions/methods")
channel.setMethodCallHandler(PermissionHandlerPlugin(registrar)) 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 @JvmStatic
fun parseManifestName(permission: String) : PermissionGroup fun parseManifestName(permission: String): PermissionGroup {
{
when (permission) { when (permission) {
Manifest.permission.READ_CALENDAR, Manifest.permission.READ_CALENDAR,
...@@ -72,123 +90,272 @@ class PermissionHandlerPlugin(private val registrar: Registrar, private var requ ...@@ -72,123 +90,272 @@ class PermissionHandlerPlugin(private val registrar: Registrar, private var requ
} }
override fun onMethodCall(call: MethodCall, result: Result) { override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "checkPermissionStatus") { when {
call.method == "checkPermissionStatus" -> {
val permission = Codec.decodePermissionGroup(call.arguments) val permission = Codec.decodePermissionGroup(call.arguments)
checkPermissionStatus(permission, result) val permissionStatus = checkPermissionStatus(permission)
} else { handleSuccess(permissionStatus, result)
result.notImplemented() }
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 == "shouldShowRequestPermissionRationale" -> {
val permission = Codec.decodePermissionGroup(call.arguments)
result.success(shouldShowRequestPermissionRationale(permission))
}
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
private fun checkPermissionStatus(permission: PermissionGroup, result: Result) for (name in names) {
if (targetsMOrHigher && ContextCompat.checkSelfPermission(context, name) != PackageManager.PERMISSION_GRANTED) {
return PermissionStatus.DENIED
}
}
return PermissionStatus.GRANTED
}
private fun shouldShowRequestPermissionRationale(permission: PermissionGroup) : Boolean {
val activity = registrar.activity()
if(activity == null)
{ {
Log.d(mLogTag, "Unable to detect current Activity.")
return false
}
val names = getManifestNames(permission) val names = getManifestNames(permission)
// if isn't an android specific group then go ahead and return false;
if (names == null) if (names == null)
{ {
Log.d(logTag, "No android specific permissions needed for: $permission") Log.d(mLogTag, "No android specific permissions needed for: $permission")
handleSuccess(PermissionStatus.GRANTED, result) return false
return
} }
//if no permissions were found then there is an issue and persmission is not set in Android manifest if (names.isEmpty())
if (names.count() == 0)
{ {
Log.d(logTag, "No permissions found in manifest for: $permission") Log.d(mLogTag,"No permissions found in manifest for: $permission no need to show request rationale")
handleSuccess(PermissionStatus.UNKNOWN, result) return false
return
} }
val context: Context? = registrar.activity() ?: registrar.activeContext() for(name in names)
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 ActivityCompat.shouldShowRequestPermissionRationale(activity, name)
handleSuccess(PermissionStatus.UNKNOWN, result) }
return false
}
private fun requestPermissions(permissions: Array<PermissionGroup>) {
if (registrar.activity() == null) {
Log.d(mLogTag, "Unable to detect current Activity.")
for (permission in permissions) {
mRequestResults[permission] = PermissionStatus.UNKNOWN
}
processResult()
return return
} }
val targetsMOrHigher = context.applicationInfo.targetSdkVersion >= android.os.Build.VERSION_CODES.M val permissionsToRequest = mutableListOf<String>()
for (permission in permissions) {
val permissionStatus = checkPermissionStatus(permission)
for (name in names) if (permissionStatus != PermissionStatus.GRANTED) {
{ val names = getManifestNames(permission)
if (targetsMOrHigher && ContextCompat.checkSelfPermission(context, name) != PackageManager.PERMISSION_GRANTED) {
handleSuccess(PermissionStatus.DENIED, result) //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 handlePermissionsRequest(permissions: Array<String>, grantResults: IntArray) {
if (mResult == null) {
return 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()
} }
handleSuccess(PermissionStatus.GRANTED, result) if (!mRequestResults.containsKey(PermissionGroup.LOCATION_WHEN_IN_USE)) {
mRequestResults[PermissionGroup.LOCATION_WHEN_IN_USE] = grantResults[i].toPermissionStatus()
}
} }
private fun getManifestNames(permission: PermissionGroup) : List<String>? if (!mRequestResults.containsKey(permission)) {
{ mRequestResults[permission] = grantResults[i].toPermissionStatus()
val permissionNames : MutableList<String> = mutableListOf() }
when(permission) { }
PermissionGroup.CALENDAR ->
{ processResult()
if(hasPermissionInManifest(Manifest.permission.READ_CALENDAR)) }
private fun Int.toPermissionStatus(): PermissionStatus {
return if (this == PackageManager.PERMISSION_GRANTED) PermissionStatus.GRANTED else PermissionStatus.DENIED
}
private fun processResult() {
mResult?.success(Codec.encodePermissionRequestResult(mRequestResults))
mRequestResults.clear()
mResult = null
}
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
}
return try {
val 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)
true
} catch(ex: Exception) {
false
}
}
private fun getManifestNames(permission: PermissionGroup): List<String>? {
val permissionNames: MutableList<String> = mutableListOf()
when (permission) {
PermissionGroup.CALENDAR -> {
if (hasPermissionInManifest(Manifest.permission.READ_CALENDAR))
permissionNames.add(Manifest.permission.READ_CALENDAR) permissionNames.add(Manifest.permission.READ_CALENDAR)
if(hasPermissionInManifest(Manifest.permission.WRITE_CALENDAR)) if (hasPermissionInManifest(Manifest.permission.WRITE_CALENDAR))
permissionNames.add(Manifest.permission.WRITE_CALENDAR) permissionNames.add(Manifest.permission.WRITE_CALENDAR)
} }
PermissionGroup.CAMERA -> PermissionGroup.CAMERA -> {
{ if (hasPermissionInManifest(Manifest.permission.CAMERA))
if(hasPermissionInManifest(Manifest.permission.CAMERA))
permissionNames.add(Manifest.permission.CAMERA) permissionNames.add(Manifest.permission.CAMERA)
} }
PermissionGroup.CONTACTS -> PermissionGroup.CONTACTS -> {
{ if (hasPermissionInManifest(Manifest.permission.READ_CONTACTS))
if(hasPermissionInManifest(Manifest.permission.READ_CONTACTS))
permissionNames.add(Manifest.permission.READ_CONTACTS) permissionNames.add(Manifest.permission.READ_CONTACTS)
if(hasPermissionInManifest(Manifest.permission.WRITE_CONTACTS)) if (hasPermissionInManifest(Manifest.permission.WRITE_CONTACTS))
permissionNames.add(Manifest.permission.WRITE_CONTACTS) permissionNames.add(Manifest.permission.WRITE_CONTACTS)
if(hasPermissionInManifest(Manifest.permission.GET_ACCOUNTS)) if (hasPermissionInManifest(Manifest.permission.GET_ACCOUNTS))
permissionNames.add(Manifest.permission.GET_ACCOUNTS) permissionNames.add(Manifest.permission.GET_ACCOUNTS)
} }
PermissionGroup.LOCATION_ALWAYS, PermissionGroup.LOCATION_ALWAYS,
PermissionGroup.LOCATION_WHEN_IN_USE, PermissionGroup.LOCATION_WHEN_IN_USE,
PermissionGroup.LOCATION -> PermissionGroup.LOCATION -> {
{ if (hasPermissionInManifest(Manifest.permission.ACCESS_COARSE_LOCATION))
if(hasPermissionInManifest(Manifest.permission.ACCESS_COARSE_LOCATION))
permissionNames.add(Manifest.permission.ACCESS_COARSE_LOCATION) permissionNames.add(Manifest.permission.ACCESS_COARSE_LOCATION)
if(hasPermissionInManifest(Manifest.permission.ACCESS_FINE_LOCATION)) if (hasPermissionInManifest(Manifest.permission.ACCESS_FINE_LOCATION))
permissionNames.add(Manifest.permission.ACCESS_FINE_LOCATION) permissionNames.add(Manifest.permission.ACCESS_FINE_LOCATION)
} }
PermissionGroup.SPEECH, PermissionGroup.SPEECH,
PermissionGroup.MICROPHONE -> PermissionGroup.MICROPHONE -> {
{ if (hasPermissionInManifest(Manifest.permission.RECORD_AUDIO))
if(hasPermissionInManifest(Manifest.permission.RECORD_AUDIO))
permissionNames.add(Manifest.permission.RECORD_AUDIO) permissionNames.add(Manifest.permission.RECORD_AUDIO)
} }
PermissionGroup.PHONE -> PermissionGroup.PHONE -> {
{ if (hasPermissionInManifest(Manifest.permission.READ_PHONE_STATE))
if(hasPermissionInManifest(Manifest.permission.READ_PHONE_STATE))
permissionNames.add(Manifest.permission.READ_PHONE_STATE) permissionNames.add(Manifest.permission.READ_PHONE_STATE)
if(hasPermissionInManifest(Manifest.permission.CALL_PHONE)) if (hasPermissionInManifest(Manifest.permission.CALL_PHONE))
permissionNames.add(Manifest.permission.CALL_PHONE) permissionNames.add(Manifest.permission.CALL_PHONE)
if(hasPermissionInManifest(Manifest.permission.READ_CALL_LOG)) if (hasPermissionInManifest(Manifest.permission.READ_CALL_LOG))
permissionNames.add(Manifest.permission.READ_CALL_LOG) permissionNames.add(Manifest.permission.READ_CALL_LOG)
if(hasPermissionInManifest(Manifest.permission.WRITE_CALL_LOG)) if (hasPermissionInManifest(Manifest.permission.WRITE_CALL_LOG))
permissionNames.add(Manifest.permission.WRITE_CALL_LOG) permissionNames.add(Manifest.permission.WRITE_CALL_LOG)
if(hasPermissionInManifest(Manifest.permission.ADD_VOICEMAIL)) if (hasPermissionInManifest(Manifest.permission.ADD_VOICEMAIL))
permissionNames.add(Manifest.permission.ADD_VOICEMAIL) permissionNames.add(Manifest.permission.ADD_VOICEMAIL)
if(hasPermissionInManifest(Manifest.permission.USE_SIP)) if (hasPermissionInManifest(Manifest.permission.USE_SIP))
permissionNames.add(Manifest.permission.USE_SIP) permissionNames.add(Manifest.permission.USE_SIP)
if(hasPermissionInManifest(Manifest.permission.PROCESS_OUTGOING_CALLS)) if (hasPermissionInManifest(Manifest.permission.PROCESS_OUTGOING_CALLS))
permissionNames.add(Manifest.permission.PROCESS_OUTGOING_CALLS) permissionNames.add(Manifest.permission.PROCESS_OUTGOING_CALLS)
} }
PermissionGroup.SENSORS -> { PermissionGroup.SENSORS -> {
...@@ -199,30 +366,28 @@ class PermissionHandlerPlugin(private val registrar: Registrar, private var requ ...@@ -199,30 +366,28 @@ class PermissionHandlerPlugin(private val registrar: Registrar, private var requ
} }
} }
PermissionGroup.SMS -> PermissionGroup.SMS -> {
{ if (hasPermissionInManifest(Manifest.permission.SEND_SMS))
if(hasPermissionInManifest(Manifest.permission.SEND_SMS))
permissionNames.add(Manifest.permission.SEND_SMS) permissionNames.add(Manifest.permission.SEND_SMS)
if(hasPermissionInManifest(Manifest.permission.RECEIVE_SMS)) if (hasPermissionInManifest(Manifest.permission.RECEIVE_SMS))
permissionNames.add(Manifest.permission.RECEIVE_SMS) permissionNames.add(Manifest.permission.RECEIVE_SMS)
if(hasPermissionInManifest(Manifest.permission.READ_SMS)) if (hasPermissionInManifest(Manifest.permission.READ_SMS))
permissionNames.add(Manifest.permission.READ_SMS) permissionNames.add(Manifest.permission.READ_SMS)
if(hasPermissionInManifest(Manifest.permission.RECEIVE_WAP_PUSH)) if (hasPermissionInManifest(Manifest.permission.RECEIVE_WAP_PUSH))
permissionNames.add(Manifest.permission.RECEIVE_WAP_PUSH) permissionNames.add(Manifest.permission.RECEIVE_WAP_PUSH)
if(hasPermissionInManifest(Manifest.permission.RECEIVE_MMS)) if (hasPermissionInManifest(Manifest.permission.RECEIVE_MMS))
permissionNames.add(Manifest.permission.RECEIVE_MMS) permissionNames.add(Manifest.permission.RECEIVE_MMS)
} }
PermissionGroup.STORAGE -> PermissionGroup.STORAGE -> {
{ if (hasPermissionInManifest(Manifest.permission.READ_EXTERNAL_STORAGE))
if(hasPermissionInManifest(Manifest.permission.READ_EXTERNAL_STORAGE))
permissionNames.add(Manifest.permission.READ_EXTERNAL_STORAGE) permissionNames.add(Manifest.permission.READ_EXTERNAL_STORAGE)
if(hasPermissionInManifest(Manifest.permission.WRITE_EXTERNAL_STORAGE)) if (hasPermissionInManifest(Manifest.permission.WRITE_EXTERNAL_STORAGE))
permissionNames.add(Manifest.permission.WRITE_EXTERNAL_STORAGE) permissionNames.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
} }
else -> return null else -> return null
...@@ -231,50 +396,43 @@ class PermissionHandlerPlugin(private val registrar: Registrar, private var requ ...@@ -231,50 +396,43 @@ class PermissionHandlerPlugin(private val registrar: Registrar, private var requ
return permissionNames return permissionNames
} }
private fun hasPermissionInManifest(permission: String) : Boolean private fun hasPermissionInManifest(permission: String): Boolean {
{ try {
try
{
requestedPermissions?.let { requestedPermissions?.let {
return it.any { r -> r.equals(permission, true) } 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) if (context == null) {
{ Log.d(mLogTag, "Unable to detect current Activity or App Context.")
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 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) if (info == null) {
{ Log.d(mLogTag, "Unable to get Package info, will not be able to determine permissions to request.")
Log.d(logTag, "Unable to get Package info, will not be able to determine permissions to request.")
return false return false
} }
requestedPermissions = info.requestedPermissions.toMutableList() requestedPermissions = info.requestedPermissions.toMutableList()
if (requestedPermissions == null) if (requestedPermissions == null) {
{ Log.d(mLogTag, "There are no requested permissions, please check to ensure you have marked permissions you want to request.")
Log.d(logTag, "There are no requested permissions, please check to ensure you have marked permissions you want to request.")
return false return false
} }
requestedPermissions?.let { requestedPermissions?.let {
return it.any { r -> r.equals(permission, true) } return it.any { r -> r.equals(permission, true) }
} ?: return false } ?: return false
} } catch (ex: Exception) {
catch(ex: Exception) Log.d(mLogTag, "Unable to check manifest for permission: $ex")
{
Log.d(logTag,"Unable to check manifest for permission: $ex")
} }
return false return false
} }
private fun handleSuccess(permissionStatus: PermissionStatus, result: Result) { private fun handleSuccess(permissionStatus: PermissionStatus, result: Result?) {
result.success(Codec.encodePermissionStatus(permissionStatus)) result?.success(Codec.encodePermissionStatus(permissionStatus))
} }
} }
...@@ -3,10 +3,13 @@ package com.baseflow.permissionhandler.utils ...@@ -3,10 +3,13 @@ package com.baseflow.permissionhandler.utils
import com.baseflow.permissionhandler.data.PermissionGroup import com.baseflow.permissionhandler.data.PermissionGroup
import com.baseflow.permissionhandler.data.PermissionStatus import com.baseflow.permissionhandler.data.PermissionStatus
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
class Codec { class Codec {
companion object { companion object {
@JvmStatic val gsonDecoder : Gson = Gson() @JvmStatic
private val gsonDecoder : Gson = GsonBuilder().enableComplexMapKeySerialization().create()
@JvmStatic @JvmStatic
fun decodePermissionGroup(arguments: Any) : PermissionGroup { fun decodePermissionGroup(arguments: Any) : PermissionGroup {
...@@ -14,8 +17,21 @@ class Codec { ...@@ -14,8 +17,21 @@ class Codec {
} }
@JvmStatic @JvmStatic
fun decodePermissionGroups(arguments: Any) : Array<PermissionGroup> {
var permissionGroupsType = object: TypeToken<Array<PermissionGroup>>() {}.type
return Codec.gsonDecoder.fromJson(arguments.toString(), permissionGroupsType)
}
@JvmStatic
fun encodePermissionStatus(permissionStatus: PermissionStatus) : String { fun encodePermissionStatus(permissionStatus: PermissionStatus) : String {
return gsonDecoder.toJson(permissionStatus) return gsonDecoder.toJson(permissionStatus)
} }
@JvmStatic
fun encodePermissionRequestResult(permissionResults: Map<PermissionGroup, PermissionStatus>) : String {
val jsonString = gsonDecoder.toJson(permissionResults)
return jsonString
}
} }
} }
\ No newline at end of file
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 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 */ /* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
...@@ -41,6 +42,7 @@ ...@@ -41,6 +42,7 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; 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 = "<group>"; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
...@@ -63,12 +65,21 @@ ...@@ -63,12 +65,21 @@
files = ( files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
F83E7534C5D827024000AB4C /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
1850378EF7B1AE3FA40E4F87 /* Frameworks */ = {
isa = PBXGroup;
children = (
36F84C9A972D968241208A7F /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = { 9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
...@@ -89,6 +100,8 @@ ...@@ -89,6 +100,8 @@
9740EEB11CF90186004384FC /* Flutter */, 9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */, 97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */, 97C146EF1CF9000F007C117D /* Products */,
A2B39E4F789ABF9C57736776 /* Pods */,
1850378EF7B1AE3FA40E4F87 /* Frameworks */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
...@@ -123,6 +136,13 @@ ...@@ -123,6 +136,13 @@
name = "Supporting Files"; name = "Supporting Files";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
A2B39E4F789ABF9C57736776 /* Pods */ = {
isa = PBXGroup;
children = (
);
name = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
...@@ -130,12 +150,14 @@ ...@@ -130,12 +150,14 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = ( buildPhases = (
E8ED1A28A9A0730EDF0565B2 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */, 9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */, 97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */, 97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */, 97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
259652FD16940B446BA7C6C6 /* [CP] Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
...@@ -197,6 +219,26 @@ ...@@ -197,6 +219,26 @@
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase 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 */ = { 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
...@@ -225,6 +267,24 @@ ...@@ -225,6 +267,24 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 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 */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
......
...@@ -4,4 +4,7 @@ ...@@ -4,4 +4,7 @@
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "group:Runner.xcodeproj">
</FileRef> </FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace> </Workspace>
...@@ -24,9 +24,21 @@ class _MyAppState extends State<MyApp> { ...@@ -24,9 +24,21 @@ class _MyAppState extends State<MyApp> {
// Platform messages are asynchronous, so we initialize in an async method. // Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async { Future<void> initPlatformState() async {
PermissionStatus permissionStatus; PermissionStatus permissionStatus;
// Platform messages may fail, so we use a try/catch PlatformException. // Platform messages may fail, so we use a try/catch PlatformException.
try { try {
permissionStatus = await PermissionHandler.checkPermissionStatus(PermissionGroup.calendar); permissionStatus = await PermissionHandler.checkPermissionStatus(PermissionGroup.calendar);
if(permissionStatus != PermissionStatus.granted){
final shouldShowRationale = await PermissionHandler.shouldShowRequestPermissionRationale(PermissionGroup.calendar);
if(shouldShowRationale) {
var permissions = await PermissionHandler.requestPermissions([PermissionGroup.calendar]);
if(permissions.containsKey(PermissionGroup.calendar)) {
permissionStatus = permissions[PermissionGroup.calendar];
}
}
}
} on PlatformException { } on PlatformException {
permissionStatus = PermissionStatus.unknown; permissionStatus = PermissionStatus.unknown;
} }
...@@ -49,7 +61,15 @@ class _MyAppState extends State<MyApp> { ...@@ -49,7 +61,15 @@ class _MyAppState extends State<MyApp> {
title: const Text('Plugin example app'), title: const Text('Plugin example app'),
), ),
body: new Center( body: new Center(
child: new Text('Running on: $_permissionStatus\n'), child: new Column(
children: <Widget>[
new Text('Running on: $_permissionStatus\n'),
new RaisedButton(
child: new Text("Open settings"),
onPressed: () async => await PermissionHandler.openAppSettings(),
),
],
),
), ),
), ),
); );
......
...@@ -11,7 +11,38 @@ class PermissionHandler { ...@@ -11,7 +11,38 @@ class PermissionHandler {
/// Returns a [Future] containing the current permission status for the supplied [PermissionGroup]. /// Returns a [Future] containing the current permission status for the supplied [PermissionGroup].
static Future<PermissionStatus> checkPermissionStatus(PermissionGroup permission) async { static Future<PermissionStatus> 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); return Codec.decodePermissionStatus(status);
} }
/// Open the App settings page.
///
/// Returns [true] if the app settings page could be opened, otherwise [false] is returned.
static Future<bool> openAppSettings() async =>
await _channel.invokeMethod("openAppSettings");
/// Request the user for access to the supplied list of permissiongroups.
///
/// Returns a [Map] containing the status per requested permissiongroup.
static Future<Map<PermissionGroup, PermissionStatus>> requestPermissions(List<PermissionGroup> permissions) async {
final jsonData = Codec.encodePermissionGroups(permissions);
final status = await _channel.invokeMethod(
'requestPermissions',
jsonData);
return Codec.decodePermissionRequestResult(status);
}
/// Request to see if you should show a rationale for requesting permission.
///
/// This method is only implemented on Android, calling this on iOS always
/// returns [false].
static Future<bool> shouldShowRequestPermissionRationale(PermissionGroup permission) async =>
await _channel.invokeMethod(
'shouldShowRequestPermissionRationale',
Codec.encodePermissionGroup(permission));
} }
...@@ -9,9 +9,26 @@ class Codec { ...@@ -9,9 +9,26 @@ class Codec {
return PermissionStatus.values.firstWhere((e) => e.toString().split('.').last == permission); return PermissionStatus.values.firstWhere((e) => e.toString().split('.').last == permission);
} }
static Map<PermissionGroup, PermissionStatus> decodePermissionRequestResult(dynamic value) {
final jsonObject = json.decode(value);
final permissionResults = Map<PermissionGroup, PermissionStatus>();
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) => static String encodePermissionGroup(PermissionGroup permissionGroup) =>
json.encode(_encodeEnum(permissionGroup)); json.encode(_encodeEnum(permissionGroup));
static String encodePermissionGroups(List<PermissionGroup> permissions) =>
json.encode(permissions.map((p) => _encodeEnum(p)).toList());
static String _encodeEnum(dynamic value) { static String _encodeEnum(dynamic value) {
return value.toString().split('.').last; return value.toString().split('.').last;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment