diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json new file mode 100644 index 0000000..e3597c2 --- /dev/null +++ b/.dart_tool/package_config.json @@ -0,0 +1,80 @@ +{ + "configVersion": 2, + "packages": [ + { + "name": "characters", + "rootUri": "file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/characters-1.2.0", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "collection", + "rootUri": "file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/collection-1.16.0", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "flutter", + "rootUri": "file:///Users/datang/fvm/versions/3.0.0/packages/flutter", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "flutter_lints", + "rootUri": "file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/flutter_lints-1.0.4", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "lints", + "rootUri": "file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/lints-1.0.1", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "material_color_utilities", + "rootUri": "file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/material_color_utilities-0.1.4", + "packageUri": "lib/", + "languageVersion": "2.13" + }, + { + "name": "meta", + "rootUri": "file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/meta-1.7.0", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "permission_handler_platform_interface", + "rootUri": "file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/permission_handler_platform_interface-3.7.0", + "packageUri": "lib/", + "languageVersion": "2.14" + }, + { + "name": "plugin_platform_interface", + "rootUri": "file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/plugin_platform_interface-2.1.2", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "sky_engine", + "rootUri": "file:///Users/datang/fvm/versions/3.0.0/bin/cache/pkg/sky_engine", + "packageUri": "lib/", + "languageVersion": "2.12" + }, + { + "name": "vector_math", + "rootUri": "file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.2", + "packageUri": "lib/", + "languageVersion": "2.14" + }, + { + "name": "permission_handler_android", + "rootUri": "../", + "packageUri": "lib/", + "languageVersion": "2.15" + } + ], + "generated": "2022-07-29T09:47:41.527304Z", + "generator": "pub", + "generatorVersion": "2.17.0" +} diff --git a/.dart_tool/package_config_subset b/.dart_tool/package_config_subset new file mode 100644 index 0000000..ce9e822 --- /dev/null +++ b/.dart_tool/package_config_subset @@ -0,0 +1,49 @@ +characters +2.12 +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/characters-1.2.0/ +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/characters-1.2.0/lib/ +collection +2.12 +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/collection-1.16.0/ +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/collection-1.16.0/lib/ +flutter_lints +2.12 +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/flutter_lints-1.0.4/ +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/flutter_lints-1.0.4/lib/ +lints +2.12 +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/lints-1.0.1/ +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/lints-1.0.1/lib/ +material_color_utilities +2.13 +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/material_color_utilities-0.1.4/ +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/material_color_utilities-0.1.4/lib/ +meta +2.12 +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/meta-1.7.0/ +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/meta-1.7.0/lib/ +permission_handler_platform_interface +2.14 +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/permission_handler_platform_interface-3.7.0/ +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/permission_handler_platform_interface-3.7.0/lib/ +plugin_platform_interface +2.12 +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/plugin_platform_interface-2.1.2/ +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/plugin_platform_interface-2.1.2/lib/ +vector_math +2.14 +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.2/ +file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.2/lib/ +permission_handler_android +2.15 +file:///Users/datang/Downloads/flutter-permission-handler-master/permission_handler_android/ +file:///Users/datang/Downloads/flutter-permission-handler-master/permission_handler_android/lib/ +sky_engine +2.12 +file:///Users/datang/fvm/versions/3.0.0/bin/cache/pkg/sky_engine/ +file:///Users/datang/fvm/versions/3.0.0/bin/cache/pkg/sky_engine/lib/ +flutter +2.12 +file:///Users/datang/fvm/versions/3.0.0/packages/flutter/ +file:///Users/datang/fvm/versions/3.0.0/packages/flutter/lib/ +2 diff --git a/.dart_tool/version b/.dart_tool/version new file mode 100644 index 0000000..56fea8a --- /dev/null +++ b/.dart_tool/version @@ -0,0 +1 @@ +3.0.0 \ No newline at end of file diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml new file mode 100644 index 0000000..a45ee49 --- /dev/null +++ b/.idea/libraries/Dart_Packages.xml @@ -0,0 +1,300 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Dart_SDK.xml b/.idea/libraries/Dart_SDK.xml new file mode 100644 index 0000000..6f3ed9b --- /dev/null +++ b/.idea/libraries/Dart_SDK.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml new file mode 100644 index 0000000..b0f6971 --- /dev/null +++ b/.idea/libraries/Flutter_Plugins.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..ff5305b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/permission_handler_android.iml b/.idea/permission_handler_android.iml new file mode 100644 index 0000000..f2db11b --- /dev/null +++ b/.idea/permission_handler_android.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.packages b/.packages new file mode 100644 index 0000000..c401f17 --- /dev/null +++ b/.packages @@ -0,0 +1,18 @@ +# This file is deprecated. Tools should instead consume +# `.dart_tool/package_config.json`. +# +# For more info see: https://dart.dev/go/dot-packages-deprecation +# +# Generated by pub on 2022-07-29 17:47:41.517101. +characters:file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/characters-1.2.0/lib/ +collection:file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/collection-1.16.0/lib/ +flutter:file:///Users/datang/fvm/versions/3.0.0/packages/flutter/lib/ +flutter_lints:file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/flutter_lints-1.0.4/lib/ +lints:file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/lints-1.0.1/lib/ +material_color_utilities:file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/material_color_utilities-0.1.4/lib/ +meta:file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/meta-1.7.0/lib/ +permission_handler_platform_interface:file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/permission_handler_platform_interface-3.7.0/lib/ +plugin_platform_interface:file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/plugin_platform_interface-2.1.2/lib/ +sky_engine:file:///Users/datang/fvm/versions/3.0.0/bin/cache/pkg/sky_engine/lib/ +vector_math:file:///Users/datang/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.2/lib/ +permission_handler_android:lib/ diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..b503219 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,8 @@ + +# Below is a list of people and organizations that have contributed +# to the Flutter project. Names should be added to the list like so: +# +# Name/Organization + +Baseflow +Maurits van Beusekom \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..50906d0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +## 10.0.0 + + * __BREAKING CHANGE__: Updated Android `compileSdkVersion` to `33` to handle the new `POST_NOTIFICATIONS` permission. + > When updating to version 10.0.0 make sure to update the `android/app/build.gradle` file and set the `compileSdkVersion` to `33`. + +## 9.0.2+1 + +* Undoes PR [#765](https://github.com/baseflow/flutter-permission-handler/pull/765) which by mistake requests write_external_storage permission based on the target SDK instead of the actual SDK of the Android device. + +## 9.0.2 + +* Moves Android implementation into its own package. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bd6192f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Baseflow + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/android/.classpath b/android/.classpath new file mode 100644 index 0000000..4a04201 --- /dev/null +++ b/android/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..90b6897 --- /dev/null +++ b/android/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,13 @@ +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(7.1.1)) +connection.project.dir= +eclipse.preferences.version=1 +gradle.user.home= +java.home=/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..e073012 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,40 @@ +group 'com.baseflow.permissionhandler' +version '1.0' +def args = ["-Xlint:deprecation","-Xlint:unchecked","-Werror"] + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +project.getTasks().withType(JavaCompile){ + options.compilerArgs.addAll(args) +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 33 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 16 + } +} \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..d9cf55d --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..da9702f --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..760984f --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'permission_handler' diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..16d58da --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/android/src/main/java/com/baseflow/permissionhandler/AppSettingsManager.java b/android/src/main/java/com/baseflow/permissionhandler/AppSettingsManager.java new file mode 100644 index 0000000..342d9df --- /dev/null +++ b/android/src/main/java/com/baseflow/permissionhandler/AppSettingsManager.java @@ -0,0 +1,39 @@ +package com.baseflow.permissionhandler; + +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +final class AppSettingsManager { + @FunctionalInterface + interface OpenAppSettingsSuccessCallback { + void onSuccess(boolean appSettingsOpenedSuccessfully); + } + + void openAppSettings( + Context context, + OpenAppSettingsSuccessCallback successCallback, + ErrorCallback errorCallback) { + if(context == null) { + Log.d(PermissionConstants.LOG_TAG, "Context cannot be null."); + errorCallback.onError("PermissionHandler.AppSettingsManager", "Android context cannot be null."); + return; + } + + try { + Intent settingsIntent = new Intent(); + settingsIntent.setAction(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + settingsIntent.addCategory(Intent.CATEGORY_DEFAULT); + settingsIntent.setData(android.net.Uri.parse("package:" + context.getPackageName())); + 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); + + successCallback.onSuccess(true); + } catch (Exception ex) { + successCallback.onSuccess(false); + } + } +} diff --git a/android/src/main/java/com/baseflow/permissionhandler/ErrorCallback.java b/android/src/main/java/com/baseflow/permissionhandler/ErrorCallback.java new file mode 100644 index 0000000..7c7a176 --- /dev/null +++ b/android/src/main/java/com/baseflow/permissionhandler/ErrorCallback.java @@ -0,0 +1,6 @@ +package com.baseflow.permissionhandler; + +@FunctionalInterface +interface ErrorCallback { + void onError(String errorCode, String errorDescription); +} diff --git a/android/src/main/java/com/baseflow/permissionhandler/MethodCallHandlerImpl.java b/android/src/main/java/com/baseflow/permissionhandler/MethodCallHandlerImpl.java new file mode 100644 index 0000000..bbbafef --- /dev/null +++ b/android/src/main/java/com/baseflow/permissionhandler/MethodCallHandlerImpl.java @@ -0,0 +1,104 @@ +package com.baseflow.permissionhandler; + +import android.app.Activity; +import android.content.Context; + +import androidx.annotation.NonNull; + +import androidx.annotation.Nullable; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.Result; + +import java.util.List; + +final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { + private final Context applicationContext; + private final AppSettingsManager appSettingsManager; + private final PermissionManager permissionManager; + private final ServiceManager serviceManager; + + MethodCallHandlerImpl( + Context applicationContext, + AppSettingsManager appSettingsManager, + PermissionManager permissionManager, + ServiceManager serviceManager) { + this.applicationContext = applicationContext; + this.appSettingsManager = appSettingsManager; + this.permissionManager = permissionManager; + this.serviceManager = serviceManager; + } + + @Nullable + private Activity activity; + + public void setActivity(@Nullable Activity activity) { + this.activity = activity; + } + + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) + { + switch (call.method) { + case "checkServiceStatus": { + @PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString()); + serviceManager.checkServiceStatus( + permission, + applicationContext, + result::success, + (String errorCode, String errorDescription) -> result.error( + errorCode, + errorDescription, + null)); + + break; + } + case "checkPermissionStatus": { + @PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString()); + permissionManager.checkPermissionStatus( + permission, + applicationContext, + result::success); + break; + } + case "requestPermissions": + final List permissions = call.arguments(); + permissionManager.requestPermissions( + permissions, + activity, + result::success, + (String errorCode, String errorDescription) -> result.error( + errorCode, + errorDescription, + null)); + + break; + case "shouldShowRequestPermissionRationale": { + @PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString()); + permissionManager.shouldShowRequestPermissionRationale( + permission, + activity, + result::success, + (String errorCode, String errorDescription) -> result.error( + errorCode, + errorDescription, + null)); + + break; + } + case "openAppSettings": + appSettingsManager.openAppSettings( + applicationContext, + result::success, + (String errorCode, String errorDescription) -> result.error( + errorCode, + errorDescription, + null)); + + break; + default: + result.notImplemented(); + break; + } + } +} diff --git a/android/src/main/java/com/baseflow/permissionhandler/PermissionConstants.java b/android/src/main/java/com/baseflow/permissionhandler/PermissionConstants.java new file mode 100644 index 0000000..af5dbdd --- /dev/null +++ b/android/src/main/java/com/baseflow/permissionhandler/PermissionConstants.java @@ -0,0 +1,118 @@ +package com.baseflow.permissionhandler; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +final class PermissionConstants { + static final String LOG_TAG = "permissions_handler"; + static final int PERMISSION_CODE = 24; + static final int PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS = 209; + static final int PERMISSION_CODE_MANAGE_EXTERNAL_STORAGE = 210; + static final int PERMISSION_CODE_SYSTEM_ALERT_WINDOW = 211; + static final int PERMISSION_CODE_REQUEST_INSTALL_PACKAGES = 212; + static final int PERMISSION_CODE_ACCESS_NOTIFICATION_POLICY = 213; + + //PERMISSION_GROUP + static final int PERMISSION_GROUP_CALENDAR = 0; + static final int PERMISSION_GROUP_CAMERA = 1; + static final int PERMISSION_GROUP_CONTACTS = 2; + static final int PERMISSION_GROUP_LOCATION = 3; + static final int PERMISSION_GROUP_LOCATION_ALWAYS = 4; + static final int PERMISSION_GROUP_LOCATION_WHEN_IN_USE = 5; + static final int PERMISSION_GROUP_MEDIA_LIBRARY = 6; + static final int PERMISSION_GROUP_MICROPHONE = 7; + static final int PERMISSION_GROUP_PHONE = 8; + static final int PERMISSION_GROUP_PHOTOS = 9; + static final int PERMISSION_GROUP_PHOTOS_ADD_ONLY = 10; + static final int PERMISSION_GROUP_REMINDERS = 11; + static final int PERMISSION_GROUP_SENSORS = 12; + static final int PERMISSION_GROUP_SMS = 13; + static final int PERMISSION_GROUP_SPEECH = 14; + static final int PERMISSION_GROUP_STORAGE = 15; + static final int PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS = 16; + static final int PERMISSION_GROUP_NOTIFICATION = 17; + static final int PERMISSION_GROUP_ACCESS_MEDIA_LOCATION = 18; + static final int PERMISSION_GROUP_ACTIVITY_RECOGNITION = 19; + static final int PERMISSION_GROUP_UNKNOWN = 20; + static final int PERMISSION_GROUP_BLUETOOTH = 21; + static final int PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE = 22; + static final int PERMISSION_GROUP_SYSTEM_ALERT_WINDOW = 23; + static final int PERMISSION_GROUP_REQUEST_INSTALL_PACKAGES = 24; + static final int PERMISSION_GROUP_APP_TRACK_TRANSPARENCY = 25; + static final int PERMISSION_GROUP_CRITICAL_ALERTS = 26; + static final int PERMISSION_GROUP_ACCESS_NOTIFICATION_POLICY = 27; + static final int PERMISSION_GROUP_BLUETOOTH_SCAN = 28; + static final int PERMISSION_GROUP_BLUETOOTH_ADVERTISE = 29; + static final int PERMISSION_GROUP_BLUETOOTH_CONNECT = 30; + static final int PERMISSION_GROUP_CALL_LOG = 31; + + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + PERMISSION_GROUP_CALENDAR, + PERMISSION_GROUP_CAMERA, + PERMISSION_GROUP_CONTACTS, + PERMISSION_GROUP_LOCATION, + PERMISSION_GROUP_LOCATION_ALWAYS, + PERMISSION_GROUP_LOCATION_WHEN_IN_USE, + PERMISSION_GROUP_MEDIA_LIBRARY, + PERMISSION_GROUP_MICROPHONE, + PERMISSION_GROUP_PHONE, + PERMISSION_GROUP_PHOTOS, + PERMISSION_GROUP_REMINDERS, + PERMISSION_GROUP_SENSORS, + PERMISSION_GROUP_SMS, + PERMISSION_GROUP_SPEECH, + PERMISSION_GROUP_STORAGE, + PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS, + PERMISSION_GROUP_NOTIFICATION, + PERMISSION_GROUP_ACCESS_MEDIA_LOCATION, + PERMISSION_GROUP_ACTIVITY_RECOGNITION, + PERMISSION_GROUP_UNKNOWN, + PERMISSION_GROUP_BLUETOOTH, + PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE, + PERMISSION_GROUP_SYSTEM_ALERT_WINDOW, + PERMISSION_GROUP_REQUEST_INSTALL_PACKAGES, + PERMISSION_GROUP_ACCESS_NOTIFICATION_POLICY, + PERMISSION_GROUP_BLUETOOTH_SCAN, + PERMISSION_GROUP_BLUETOOTH_ADVERTISE, + PERMISSION_GROUP_BLUETOOTH_CONNECT, + PERMISSION_GROUP_CALL_LOG + }) + @interface PermissionGroup { + } + + //PERMISSION_STATUS + static final int PERMISSION_STATUS_DENIED = 0; + static final int PERMISSION_STATUS_GRANTED = 1; + static final int PERMISSION_STATUS_RESTRICTED = 2; + static final int PERMISSION_STATUS_LIMITED = 3; + static final int PERMISSION_STATUS_NEVER_ASK_AGAIN = 4; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + PERMISSION_STATUS_DENIED, + PERMISSION_STATUS_GRANTED, + PERMISSION_STATUS_RESTRICTED, + PERMISSION_STATUS_LIMITED, + PERMISSION_STATUS_NEVER_ASK_AGAIN + }) + @interface PermissionStatus { + } + + //SERVICE_STATUS + static final int SERVICE_STATUS_DISABLED = 0; + static final int SERVICE_STATUS_ENABLED = 1; + static final int SERVICE_STATUS_NOT_APPLICABLE = 2; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + SERVICE_STATUS_DISABLED, + SERVICE_STATUS_ENABLED, + SERVICE_STATUS_NOT_APPLICABLE + }) + @interface ServiceStatus { + } +} \ No newline at end of file diff --git a/android/src/main/java/com/baseflow/permissionhandler/PermissionHandlerPlugin.java b/android/src/main/java/com/baseflow/permissionhandler/PermissionHandlerPlugin.java new file mode 100644 index 0000000..c9ff345 --- /dev/null +++ b/android/src/main/java/com/baseflow/permissionhandler/PermissionHandlerPlugin.java @@ -0,0 +1,154 @@ +package com.baseflow.permissionhandler; + +import android.app.Activity; +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodChannel; + +/** + * Platform implementation of the permission_handler Flutter plugin. + * + *

Instantiate this in an add to app scenario to gracefully handle activity and context changes. + * See {@code com.example.permissionhandlerexample.MainActivity} for an example. + * + *

Call {@link #registerWith(io.flutter.plugin.common.PluginRegistry.Registrar)} to register an + * implementation of this that uses the stable {@code io.flutter.plugin.common} package. + */ +public final class PermissionHandlerPlugin implements FlutterPlugin, ActivityAware { + + private final PermissionManager permissionManager; + private MethodChannel methodChannel; + + @SuppressWarnings("deprecation") + @Nullable private io.flutter.plugin.common.PluginRegistry.Registrar pluginRegistrar; + + @Nullable private ActivityPluginBinding pluginBinding; + + @Nullable + private MethodCallHandlerImpl methodCallHandler; + + public PermissionHandlerPlugin() { + this.permissionManager = new PermissionManager(); + } + + /** + * Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common} + * package. + * + *

Calling this automatically initializes the plugin. However plugins initialized this way + * won't react to changes in activity or context, unlike {@link PermissionHandlerPlugin}. + */ + @SuppressWarnings("deprecation") + public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { + final PermissionHandlerPlugin plugin = new PermissionHandlerPlugin(); + + plugin.pluginRegistrar = registrar; + plugin.registerListeners(); + + plugin.startListening(registrar.context(), registrar.messenger()); + + if (registrar.activeContext() instanceof Activity) { + plugin.startListeningToActivity( + registrar.activity() + ); + } + } + + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + startListening( + binding.getApplicationContext(), + binding.getBinaryMessenger() + ); + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + stopListening(); + } + + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + startListeningToActivity( + binding.getActivity() + ); + + this.pluginBinding = binding; + registerListeners(); + } + + @Override + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { + onAttachedToActivity(binding); + } + + @Override + public void onDetachedFromActivity() { + stopListeningToActivity(); + + deregisterListeners(); + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity(); + } + + + private void startListening(Context applicationContext, BinaryMessenger messenger) { + methodChannel = new MethodChannel( + messenger, + "flutter.baseflow.com/permissions/methods"); + + methodCallHandler = new MethodCallHandlerImpl( + applicationContext, + new AppSettingsManager(), + this.permissionManager, + new ServiceManager() + ); + + methodChannel.setMethodCallHandler(methodCallHandler); + } + + private void stopListening() { + methodChannel.setMethodCallHandler(null); + methodChannel = null; + methodCallHandler = null; + } + + private void startListeningToActivity( + Activity activity + ) { + if (methodCallHandler != null) { + methodCallHandler.setActivity(activity); + } + } + + private void stopListeningToActivity() { + if (methodCallHandler != null) { + methodCallHandler.setActivity(null); + } + } + + private void registerListeners() { + if (this.pluginRegistrar != null) { + this.pluginRegistrar.addActivityResultListener(this.permissionManager); + this.pluginRegistrar.addRequestPermissionsResultListener(this.permissionManager); + } else if (pluginBinding != null) { + this.pluginBinding.addActivityResultListener(this.permissionManager); + this.pluginBinding.addRequestPermissionsResultListener(this.permissionManager); + } + } + + private void deregisterListeners() { + if (this.pluginBinding != null) { + this.pluginBinding.removeActivityResultListener(this.permissionManager); + this.pluginBinding.removeRequestPermissionsResultListener(this.permissionManager); + } + } +} diff --git a/android/src/main/java/com/baseflow/permissionhandler/PermissionManager.java b/android/src/main/java/com/baseflow/permissionhandler/PermissionManager.java new file mode 100644 index 0000000..295a649 --- /dev/null +++ b/android/src/main/java/com/baseflow/permissionhandler/PermissionManager.java @@ -0,0 +1,497 @@ +package com.baseflow.permissionhandler; + +import android.Manifest; +import android.app.Activity; +import android.app.Application; +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.os.PowerManager; +import android.provider.Settings; +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; +import androidx.core.app.NotificationManagerCompat; +import androidx.core.content.ContextCompat; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.flutter.plugin.common.PluginRegistry; + +final class PermissionManager implements PluginRegistry.ActivityResultListener, PluginRegistry.RequestPermissionsResultListener { + + @Nullable + private RequestPermissionsSuccessCallback successCallback; + + @Nullable + private Activity activity; + + private Map requestResults; + + @Override + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode != PermissionConstants.PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS && + requestCode != PermissionConstants.PERMISSION_CODE_MANAGE_EXTERNAL_STORAGE && + requestCode != PermissionConstants.PERMISSION_CODE_SYSTEM_ALERT_WINDOW && + requestCode != PermissionConstants.PERMISSION_CODE_REQUEST_INSTALL_PACKAGES && + requestCode != PermissionConstants.PERMISSION_CODE_ACCESS_NOTIFICATION_POLICY) { + return false; + } + + int status = resultCode == Activity.RESULT_OK + ? PermissionConstants.PERMISSION_STATUS_GRANTED + : PermissionConstants.PERMISSION_STATUS_DENIED; + + int permission; + + if (requestCode == PermissionConstants.PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS) { + permission = PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS; + } else if (requestCode == PermissionConstants.PERMISSION_CODE_MANAGE_EXTERNAL_STORAGE) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + status = Environment.isExternalStorageManager() + ? PermissionConstants.PERMISSION_STATUS_GRANTED + : PermissionConstants.PERMISSION_STATUS_DENIED; + } else { + return false; + } + permission = PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE; + } else if (requestCode == PermissionConstants.PERMISSION_CODE_SYSTEM_ALERT_WINDOW) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + status = Settings.canDrawOverlays(activity) + ? PermissionConstants.PERMISSION_STATUS_GRANTED + : PermissionConstants.PERMISSION_STATUS_DENIED; + permission = PermissionConstants.PERMISSION_GROUP_SYSTEM_ALERT_WINDOW; + } else { + return false; + } + } else if (requestCode == PermissionConstants.PERMISSION_CODE_REQUEST_INSTALL_PACKAGES) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + status = activity.getPackageManager().canRequestPackageInstalls() + ? PermissionConstants.PERMISSION_STATUS_GRANTED + : PermissionConstants.PERMISSION_STATUS_DENIED; + permission = PermissionConstants.PERMISSION_GROUP_REQUEST_INSTALL_PACKAGES; + } else { + return false; + } + } else if (requestCode == PermissionConstants.PERMISSION_CODE_ACCESS_NOTIFICATION_POLICY) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + NotificationManager notificationManager = (NotificationManager) activity.getSystemService(Application.NOTIFICATION_SERVICE); + status = notificationManager.isNotificationPolicyAccessGranted() + ? PermissionConstants.PERMISSION_STATUS_GRANTED + : PermissionConstants.PERMISSION_STATUS_DENIED; + permission = PermissionConstants.PERMISSION_GROUP_ACCESS_NOTIFICATION_POLICY; + } else { + return false; + } + } else { + return false; + } + + HashMap results = new HashMap<>(); + results.put(permission, status); + successCallback.onSuccess(results); + return true; + } + + @Override + public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + if (requestCode != PermissionConstants.PERMISSION_CODE) { + ongoing = false; + return false; + } + + if (requestResults == null) { + return false; + } + + for (int i = 0; i < permissions.length; i++) { + final String permissionName = permissions[i]; + + @PermissionConstants.PermissionGroup final int permission = + PermissionUtils.parseManifestName(permissionName); + + if (permission == PermissionConstants.PERMISSION_GROUP_UNKNOWN) + continue; + + final int result = grantResults[i]; + + if (permission == PermissionConstants.PERMISSION_GROUP_MICROPHONE) { + if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_MICROPHONE)) { + requestResults.put( + PermissionConstants.PERMISSION_GROUP_MICROPHONE, + PermissionUtils.toPermissionStatus(this.activity, permissionName, result)); + } + if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_SPEECH)) { + requestResults.put( + PermissionConstants.PERMISSION_GROUP_SPEECH, + PermissionUtils.toPermissionStatus(this.activity, permissionName, result)); + } + if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_CALL_LOG)) { + requestResults.put( + PermissionConstants.PERMISSION_GROUP_CALL_LOG, + PermissionUtils.toPermissionStatus(this.activity, permissionName, result)); + } + } else if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS) { + @PermissionConstants.PermissionStatus int permissionStatus = + PermissionUtils.toPermissionStatus(this.activity, permissionName, result); + + if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS)) { + requestResults.put(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS, permissionStatus); + } + } else if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION) { + @PermissionConstants.PermissionStatus int permissionStatus = + PermissionUtils.toPermissionStatus(this.activity, permissionName, result); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS)) { + requestResults.put( + PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS, + permissionStatus); + } + } + + if (!requestResults.containsKey(PermissionConstants.PERMISSION_GROUP_LOCATION_WHEN_IN_USE)) { + requestResults.put( + PermissionConstants.PERMISSION_GROUP_LOCATION_WHEN_IN_USE, + permissionStatus); + } + + requestResults.put(permission, permissionStatus); + } else if (!requestResults.containsKey(permission)) { + requestResults.put( + permission, + PermissionUtils.toPermissionStatus(this.activity, permissionName, result)); + } + + PermissionUtils.updatePermissionShouldShowStatus(this.activity, permission); + } + + this.successCallback.onSuccess(requestResults); + ongoing = false; + return true; + } + + @FunctionalInterface + interface RequestPermissionsSuccessCallback { + void onSuccess(Map results); + } + + @FunctionalInterface + interface CheckPermissionsSuccessCallback { + void onSuccess(@PermissionConstants.PermissionStatus int permissionStatus); + } + + @FunctionalInterface + interface ShouldShowRequestPermissionRationaleSuccessCallback { + void onSuccess(boolean shouldShowRequestPermissionRationale); + } + + private boolean ongoing = false; + + void checkPermissionStatus( + @PermissionConstants.PermissionGroup int permission, + Context context, + CheckPermissionsSuccessCallback successCallback) { + + successCallback.onSuccess(determinePermissionStatus( + permission, + context)); + } + + void requestPermissions( + List permissions, + Activity activity, + RequestPermissionsSuccessCallback successCallback, + ErrorCallback errorCallback) { + if (ongoing) { + errorCallback.onError( + "PermissionHandler.PermissionManager", + "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)."); + return; + } + + if (activity == null) { + Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity."); + + errorCallback.onError( + "PermissionHandler.PermissionManager", + "Unable to detect current Android Activity."); + return; + } + + this.successCallback = successCallback; + this.activity = activity; + this.requestResults = new HashMap<>(); + + ArrayList permissionsToRequest = new ArrayList<>(); + for (Integer permission : permissions) { + @PermissionConstants.PermissionStatus final int permissionStatus = determinePermissionStatus(permission, activity); + if (permissionStatus == PermissionConstants.PERMISSION_STATUS_GRANTED) { + if (!requestResults.containsKey(permission)) { + requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_GRANTED); + } + continue; + } + + final List names = PermissionUtils.getManifestNames(activity, 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 (!requestResults.containsKey(permission)) { + // On Android below M, the android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS flag in AndroidManifest.xml + // may be ignored and not visible to the App as it's a new permission setting as a whole. + if (permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_RESTRICTED); + } else { + requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_DENIED); + } + // On Android below R, the android.permission.MANAGE_EXTERNAL_STORAGE flag in AndroidManifest.xml + // may be ignored and not visible to the App as it's a new permission setting as a whole. + if (permission == PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE && Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_RESTRICTED); + } else { + requestResults.put(permission, PermissionConstants.PERMISSION_STATUS_DENIED); + } + } + + continue; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) { + executeIntent( + Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + PermissionConstants.PERMISSION_CODE_IGNORE_BATTERY_OPTIMIZATIONS); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && permission == PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE) { + executeIntent( + Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, + PermissionConstants.PERMISSION_CODE_MANAGE_EXTERNAL_STORAGE); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permission == PermissionConstants.PERMISSION_GROUP_SYSTEM_ALERT_WINDOW) { + executeIntent( + Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + PermissionConstants.PERMISSION_CODE_SYSTEM_ALERT_WINDOW); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && permission == PermissionConstants.PERMISSION_GROUP_REQUEST_INSTALL_PACKAGES) { + executeIntent( + Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, + PermissionConstants.PERMISSION_CODE_REQUEST_INSTALL_PACKAGES); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && permission == PermissionConstants.PERMISSION_GROUP_ACCESS_NOTIFICATION_POLICY) { + executeSimpleIntent( + Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS, + PermissionConstants.PERMISSION_CODE_ACCESS_NOTIFICATION_POLICY); + } else { + permissionsToRequest.addAll(names); + } + } + + final String[] requestPermissions = permissionsToRequest.toArray(new String[0]); + if (permissionsToRequest.size() > 0) { + ongoing = true; + + ActivityCompat.requestPermissions( + activity, + requestPermissions, + PermissionConstants.PERMISSION_CODE); + } else { + ongoing = false; + if (requestResults.size() > 0) { + successCallback.onSuccess(requestResults); + } + } + } + + @PermissionConstants.PermissionStatus + private int determinePermissionStatus( + @PermissionConstants.PermissionGroup int permission, + Context context) { + + if (permission == PermissionConstants.PERMISSION_GROUP_NOTIFICATION) { + return checkNotificationPermissionStatus(context); + } + + if (permission == PermissionConstants.PERMISSION_GROUP_BLUETOOTH) { + return checkBluetoothPermissionStatus(context); + } + + if (permission == PermissionConstants.PERMISSION_GROUP_BLUETOOTH_CONNECT + || permission == PermissionConstants.PERMISSION_GROUP_BLUETOOTH_SCAN + || permission == PermissionConstants.PERMISSION_GROUP_BLUETOOTH_ADVERTISE){ + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + return checkBluetoothPermissionStatus(context); + } + } + + final List names = PermissionUtils.getManifestNames(context, permission); + + if (names == null) { + Log.d(PermissionConstants.LOG_TAG, "No android specific permissions needed for: " + permission); + + return PermissionConstants.PERMISSION_STATUS_GRANTED; + } + + //if no permissions were found then there is an issue and permission is not set in Android manifest + if (names.size() == 0) { + Log.d(PermissionConstants.LOG_TAG, "No permissions found in manifest for: " + names + permission); + + // On Android below M, the android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS flag in AndroidManifest.xml + // may be ignored and not visible to the App as it's a new permission setting as a whole. + if (permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return PermissionConstants.PERMISSION_STATUS_RESTRICTED; + } + } + + // On Android below R, the android.permission.MANAGE_EXTERNAL_STORAGE flag in AndroidManifest.xml + // may be ignored and not visible to the App as it's a new permission setting as a whole. + if (permission == PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + return PermissionConstants.PERMISSION_STATUS_RESTRICTED; + } + } + + return Build.VERSION.SDK_INT < Build.VERSION_CODES.M + ? PermissionConstants.PERMISSION_STATUS_GRANTED + : PermissionConstants.PERMISSION_STATUS_DENIED; + } + + final boolean targetsMOrHigher = context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M; + + for (String name : names) { + // Only handle them if the client app actually targets a API level greater than M. + if (targetsMOrHigher) { + if (permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) { + String packageName = context.getPackageName(); + PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + // PowerManager.isIgnoringBatteryOptimizations has been included in Android M first. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (pm != null && pm.isIgnoringBatteryOptimizations(packageName)) { + return PermissionConstants.PERMISSION_STATUS_GRANTED; + } else { + return PermissionConstants.PERMISSION_STATUS_DENIED; + } + } else { + return PermissionConstants.PERMISSION_STATUS_RESTRICTED; + } + } + + if (permission == PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + return PermissionConstants.PERMISSION_STATUS_RESTRICTED; + } + + return Environment.isExternalStorageManager() + ? PermissionConstants.PERMISSION_STATUS_GRANTED + : PermissionConstants.PERMISSION_STATUS_DENIED; + } + + if (permission == PermissionConstants.PERMISSION_GROUP_SYSTEM_ALERT_WINDOW) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return Settings.canDrawOverlays(context) + ? PermissionConstants.PERMISSION_STATUS_GRANTED + : PermissionConstants.PERMISSION_STATUS_DENIED; + } + } + + if (permission == PermissionConstants.PERMISSION_GROUP_REQUEST_INSTALL_PACKAGES) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return context.getPackageManager().canRequestPackageInstalls() + ? PermissionConstants.PERMISSION_STATUS_GRANTED + : PermissionConstants.PERMISSION_STATUS_DENIED; + } + } + + if (permission == PermissionConstants.PERMISSION_GROUP_ACCESS_NOTIFICATION_POLICY) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + NotificationManager notificationManager = (NotificationManager) context.getSystemService(Application.NOTIFICATION_SERVICE); + return notificationManager.isNotificationPolicyAccessGranted() + ? PermissionConstants.PERMISSION_STATUS_GRANTED + : PermissionConstants.PERMISSION_STATUS_DENIED; + } + } + + final int permissionStatus = ContextCompat.checkSelfPermission(context, name); + if (permissionStatus != PackageManager.PERMISSION_GRANTED) { + return PermissionConstants.PERMISSION_STATUS_DENIED; + } + } + } + return PermissionConstants.PERMISSION_STATUS_GRANTED; + } + + private void executeIntent(String action, int requestCode) { + String packageName = activity.getPackageName(); + Intent intent = new Intent(); + intent.setAction(action); + intent.setData(Uri.parse("package:" + packageName)); + activity.startActivityForResult(intent, requestCode); + } + + private void executeSimpleIntent(String action, int requestCode) { + activity.startActivityForResult(new Intent(action), requestCode); + } + + void shouldShowRequestPermissionRationale( + int permission, + Activity activity, + ShouldShowRequestPermissionRationaleSuccessCallback successCallback, + ErrorCallback errorCallback) { + if (activity == null) { + Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity."); + + errorCallback.onError( + "PermissionHandler.PermissionManager", + "Unable to detect current Android Activity."); + return; + } + + List names = PermissionUtils.getManifestNames(activity, permission); + + // if isn't an android specific group then go ahead and return false; + if (names == null) { + Log.d(PermissionConstants.LOG_TAG, "No android specific permissions needed for: " + permission); + successCallback.onSuccess(false); + return; + } + + if (names.isEmpty()) { + Log.d(PermissionConstants.LOG_TAG, "No permissions found in manifest for: " + permission + " no need to show request rationale"); + successCallback.onSuccess(false); + return; + } + + successCallback.onSuccess(ActivityCompat.shouldShowRequestPermissionRationale(activity, names.get(0))); + } + + private int checkNotificationPermissionStatus(Context context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + NotificationManagerCompat manager = NotificationManagerCompat.from(context); + boolean isGranted = manager.areNotificationsEnabled(); + if (isGranted) { + return PermissionConstants.PERMISSION_STATUS_GRANTED; + } + return PermissionConstants.PERMISSION_STATUS_DENIED; + } + + return context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED + ? PermissionConstants.PERMISSION_STATUS_GRANTED + : PermissionConstants.PERMISSION_STATUS_DENIED; + } + + private int checkBluetoothPermissionStatus(Context context) { + List names = PermissionUtils.getManifestNames(context, PermissionConstants.PERMISSION_GROUP_BLUETOOTH); + boolean missingInManifest = names == null || names.isEmpty(); + if (missingInManifest) { + Log.d(PermissionConstants.LOG_TAG, "Bluetooth permission missing in manifest"); + return PermissionConstants.PERMISSION_STATUS_DENIED; + } + return PermissionConstants.PERMISSION_STATUS_GRANTED; + } +} diff --git a/android/src/main/java/com/baseflow/permissionhandler/PermissionUtils.java b/android/src/main/java/com/baseflow/permissionhandler/PermissionUtils.java new file mode 100644 index 0000000..61cbee6 --- /dev/null +++ b/android/src/main/java/com/baseflow/permissionhandler/PermissionUtils.java @@ -0,0 +1,407 @@ +package com.baseflow.permissionhandler; + +import android.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Environment; +import android.util.Log; + +import androidx.annotation.RequiresApi; +import androidx.core.app.ActivityCompat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class PermissionUtils { + + @PermissionConstants.PermissionGroup + static int parseManifestName(String permission) { + switch (permission) { + case Manifest.permission.READ_CALENDAR: + case Manifest.permission.WRITE_CALENDAR: + return PermissionConstants.PERMISSION_GROUP_CALENDAR; + case Manifest.permission.CAMERA: + return PermissionConstants.PERMISSION_GROUP_CAMERA; + case Manifest.permission.READ_CONTACTS: + case Manifest.permission.WRITE_CONTACTS: + case Manifest.permission.GET_ACCOUNTS: + return PermissionConstants.PERMISSION_GROUP_CONTACTS; + case Manifest.permission.ACCESS_BACKGROUND_LOCATION: + return PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS; + case Manifest.permission.ACCESS_COARSE_LOCATION: + case Manifest.permission.ACCESS_FINE_LOCATION: + return PermissionConstants.PERMISSION_GROUP_LOCATION; + case Manifest.permission.RECORD_AUDIO: + return PermissionConstants.PERMISSION_GROUP_MICROPHONE; + case Manifest.permission.READ_PHONE_STATE: + case Manifest.permission.READ_PHONE_NUMBERS: + case Manifest.permission.CALL_PHONE: + case Manifest.permission.ADD_VOICEMAIL: + case Manifest.permission.USE_SIP: + case Manifest.permission.BIND_CALL_REDIRECTION_SERVICE: + return PermissionConstants.PERMISSION_GROUP_PHONE; + case Manifest.permission.BODY_SENSORS: + return PermissionConstants.PERMISSION_GROUP_SENSORS; + case Manifest.permission.SEND_SMS: + case Manifest.permission.RECEIVE_SMS: + case Manifest.permission.READ_SMS: + case Manifest.permission.RECEIVE_WAP_PUSH: + case Manifest.permission.RECEIVE_MMS: + return PermissionConstants.PERMISSION_GROUP_SMS; + case Manifest.permission.READ_EXTERNAL_STORAGE: + case Manifest.permission.WRITE_EXTERNAL_STORAGE: + return PermissionConstants.PERMISSION_GROUP_STORAGE; + case Manifest.permission.ACCESS_MEDIA_LOCATION: + return PermissionConstants.PERMISSION_GROUP_ACCESS_MEDIA_LOCATION; + case Manifest.permission.ACTIVITY_RECOGNITION: + return PermissionConstants.PERMISSION_GROUP_ACTIVITY_RECOGNITION; + case Manifest.permission.MANAGE_EXTERNAL_STORAGE: + return PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE; + case Manifest.permission.SYSTEM_ALERT_WINDOW: + return PermissionConstants.PERMISSION_GROUP_SYSTEM_ALERT_WINDOW; + case Manifest.permission.REQUEST_INSTALL_PACKAGES: + return PermissionConstants.PERMISSION_GROUP_REQUEST_INSTALL_PACKAGES; + case Manifest.permission.ACCESS_NOTIFICATION_POLICY: + return PermissionConstants.PERMISSION_GROUP_ACCESS_NOTIFICATION_POLICY; + case Manifest.permission.BLUETOOTH_SCAN: + return PermissionConstants.PERMISSION_GROUP_BLUETOOTH_SCAN; + case Manifest.permission.BLUETOOTH_ADVERTISE: + return PermissionConstants.PERMISSION_GROUP_BLUETOOTH_ADVERTISE; + case Manifest.permission.BLUETOOTH_CONNECT: + return PermissionConstants.PERMISSION_GROUP_BLUETOOTH_CONNECT; + case Manifest.permission.POST_NOTIFICATIONS: + return PermissionConstants.PERMISSION_GROUP_NOTIFICATION; + case Manifest.permission.READ_CALL_LOG: + case Manifest.permission.WRITE_CALL_LOG: + return PermissionConstants.PERMISSION_GROUP_CALL_LOG; + default: + return PermissionConstants.PERMISSION_GROUP_UNKNOWN; + } + } + + static List getManifestNames(Context context, @PermissionConstants.PermissionGroup int permission) { + final ArrayList permissionNames = new ArrayList<>(); + + switch (permission) { + case PermissionConstants.PERMISSION_GROUP_CALENDAR: + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_CALENDAR)) + permissionNames.add(Manifest.permission.READ_CALENDAR); + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.WRITE_CALENDAR)) + permissionNames.add(Manifest.permission.WRITE_CALENDAR); + break; + + case PermissionConstants.PERMISSION_GROUP_CAMERA: + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.CAMERA)) + permissionNames.add(Manifest.permission.CAMERA); + break; + + case PermissionConstants.PERMISSION_GROUP_CONTACTS: + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_CONTACTS)) + permissionNames.add(Manifest.permission.READ_CONTACTS); + + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.WRITE_CONTACTS)) + permissionNames.add(Manifest.permission.WRITE_CONTACTS); + + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.GET_ACCOUNTS)) + permissionNames.add(Manifest.permission.GET_ACCOUNTS); + break; + + case PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS: + // Note that the LOCATION_ALWAYS will deliberately fallthrough to the LOCATION + // case on pre Android Q devices. The ACCESS_BACKGROUND_LOCATION permission was only + // introduced in Android Q, before it should be treated as the ACCESS_COARSE_LOCATION or + // ACCESS_FINE_LOCATION. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.ACCESS_BACKGROUND_LOCATION)) + permissionNames.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION); + break; + } + case PermissionConstants.PERMISSION_GROUP_LOCATION_WHEN_IN_USE: + case PermissionConstants.PERMISSION_GROUP_LOCATION: + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.ACCESS_COARSE_LOCATION)) + permissionNames.add(Manifest.permission.ACCESS_COARSE_LOCATION); + + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.ACCESS_FINE_LOCATION)) + permissionNames.add(Manifest.permission.ACCESS_FINE_LOCATION); + break; + + + case PermissionConstants.PERMISSION_GROUP_SPEECH: + case PermissionConstants.PERMISSION_GROUP_MICROPHONE: + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.RECORD_AUDIO)) + permissionNames.add(Manifest.permission.RECORD_AUDIO); + break; + + case PermissionConstants.PERMISSION_GROUP_PHONE: + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_PHONE_STATE)) + permissionNames.add(Manifest.permission.READ_PHONE_STATE); + + if (android.os.Build.VERSION.SDK_INT > Build.VERSION_CODES.Q && hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_PHONE_NUMBERS)) { + permissionNames.add(Manifest.permission.READ_PHONE_NUMBERS); + } + + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.CALL_PHONE)) + permissionNames.add(Manifest.permission.CALL_PHONE); + + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.ADD_VOICEMAIL)) + permissionNames.add(Manifest.permission.ADD_VOICEMAIL); + + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.USE_SIP)) + permissionNames.add(Manifest.permission.USE_SIP); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && hasPermissionInManifest(context, permissionNames, Manifest.permission.BIND_CALL_REDIRECTION_SERVICE)) + permissionNames.add(Manifest.permission.BIND_CALL_REDIRECTION_SERVICE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && hasPermissionInManifest(context, permissionNames, Manifest.permission.ANSWER_PHONE_CALLS)) + permissionNames.add(Manifest.permission.ANSWER_PHONE_CALLS); + + break; + case PermissionConstants.PERMISSION_GROUP_CALL_LOG: + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_CALL_LOG)) + permissionNames.add(Manifest.permission.READ_CALL_LOG); + + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.WRITE_CALL_LOG)) + permissionNames.add(Manifest.permission.WRITE_CALL_LOG); + break; + + case PermissionConstants.PERMISSION_GROUP_SENSORS: + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.BODY_SENSORS)) { + permissionNames.add(Manifest.permission.BODY_SENSORS); + } + } + break; + + case PermissionConstants.PERMISSION_GROUP_SMS: + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.SEND_SMS)) + permissionNames.add(Manifest.permission.SEND_SMS); + + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.RECEIVE_SMS)) + permissionNames.add(Manifest.permission.RECEIVE_SMS); + + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_SMS)) + permissionNames.add(Manifest.permission.READ_SMS); + + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.RECEIVE_WAP_PUSH)) + permissionNames.add(Manifest.permission.RECEIVE_WAP_PUSH); + + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.RECEIVE_MMS)) + permissionNames.add(Manifest.permission.RECEIVE_MMS); + break; + + case PermissionConstants.PERMISSION_GROUP_STORAGE: + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.READ_EXTERNAL_STORAGE)) + permissionNames.add(Manifest.permission.READ_EXTERNAL_STORAGE); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q && Environment.isExternalStorageLegacy())) { + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.WRITE_EXTERNAL_STORAGE)) + permissionNames.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); + break; + } + break; + + case PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS: + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && hasPermissionInManifest(context, permissionNames, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) + permissionNames.add(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + break; + + case PermissionConstants.PERMISSION_GROUP_ACCESS_MEDIA_LOCATION: + // The ACCESS_MEDIA_LOCATION permission is introduced in Android Q, meaning we should + // not handle permissions on pre Android Q devices. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) + return null; + + if(hasPermissionInManifest(context, permissionNames, Manifest.permission.ACCESS_MEDIA_LOCATION)) + permissionNames.add(Manifest.permission.ACCESS_MEDIA_LOCATION); + break; + + case PermissionConstants.PERMISSION_GROUP_ACTIVITY_RECOGNITION: + // The ACTIVITY_RECOGNITION permission is introduced in Android Q, meaning we should + // not handle permissions on pre Android Q devices. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) + return null; + + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.ACTIVITY_RECOGNITION)) + permissionNames.add(Manifest.permission.ACTIVITY_RECOGNITION); + break; + + case PermissionConstants.PERMISSION_GROUP_BLUETOOTH: + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.BLUETOOTH)) + permissionNames.add(Manifest.permission.BLUETOOTH); + break; + + case PermissionConstants.PERMISSION_GROUP_MANAGE_EXTERNAL_STORAGE: + // The MANAGE_EXTERNAL_STORAGE permission is introduced in Android R, meaning we should + // not handle permissions on pre Android R devices. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && hasPermissionInManifest(context, permissionNames, Manifest.permission.MANAGE_EXTERNAL_STORAGE )) + permissionNames.add(Manifest.permission.MANAGE_EXTERNAL_STORAGE); + break; + + case PermissionConstants.PERMISSION_GROUP_SYSTEM_ALERT_WINDOW: + if (hasPermissionInManifest(context, permissionNames, Manifest.permission.SYSTEM_ALERT_WINDOW )) + permissionNames.add(Manifest.permission.SYSTEM_ALERT_WINDOW); + break; + + case PermissionConstants.PERMISSION_GROUP_REQUEST_INSTALL_PACKAGES: + // The REQUEST_INSTALL_PACKAGES permission is introduced in Android M, meaning we should + // not handle permissions on pre Android M devices. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && hasPermissionInManifest(context, permissionNames, Manifest.permission.REQUEST_INSTALL_PACKAGES )) + permissionNames.add(Manifest.permission.REQUEST_INSTALL_PACKAGES); + break; + case PermissionConstants.PERMISSION_GROUP_ACCESS_NOTIFICATION_POLICY: + // The REQUEST_NOTIFICATION_POLICY permission is introduced in Android M, meaning we should + // not handle permissions on pre Android M devices. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && hasPermissionInManifest(context, permissionNames, Manifest.permission.ACCESS_NOTIFICATION_POLICY )) + permissionNames.add(Manifest.permission.ACCESS_NOTIFICATION_POLICY); + break; + case PermissionConstants.PERMISSION_GROUP_BLUETOOTH_SCAN: { + // The BLUETOOTH_SCAN permission is introduced in Android S, meaning we should + // not handle permissions on pre Android S devices. + String result = determineBluetoothPermission(context, Manifest.permission.BLUETOOTH_SCAN); + + if (result != null) { + permissionNames.add(result); + } + + break; + } + case PermissionConstants.PERMISSION_GROUP_BLUETOOTH_ADVERTISE: { + // The BLUETOOTH_ADVERTISE permission is introduced in Android S, meaning we should + // not handle permissions on pre Android S devices. + String result = determineBluetoothPermission(context, Manifest.permission.BLUETOOTH_ADVERTISE); + + if (result != null) { + permissionNames.add(result); + } + + break; + } + case PermissionConstants.PERMISSION_GROUP_BLUETOOTH_CONNECT: { + // The BLUETOOTH_CONNECT permission is introduced in Android S, meaning we should + // not handle permissions on pre Android S devices. + String result = determineBluetoothPermission(context, Manifest.permission.BLUETOOTH_CONNECT); + + if (result != null) { + permissionNames.add(result); + } + + break; + } + case PermissionConstants.PERMISSION_GROUP_NOTIFICATION: + // The POST_NOTIFICATIONS permission is introduced in Android 13, meaning we should + // not handle permissions on pre Android 13 devices. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && hasPermissionInManifest(context, permissionNames, Manifest.permission.POST_NOTIFICATIONS )) + permissionNames.add(Manifest.permission.POST_NOTIFICATIONS); + break; + case PermissionConstants.PERMISSION_GROUP_MEDIA_LIBRARY: + case PermissionConstants.PERMISSION_GROUP_PHOTOS: + case PermissionConstants.PERMISSION_GROUP_REMINDERS: + case PermissionConstants.PERMISSION_GROUP_UNKNOWN: + return null; + } + + return permissionNames; + } + + private static boolean hasPermissionInManifest(Context context, ArrayList confirmedPermissions, String permission) { + try { + if (confirmedPermissions != null) { + for (String r : confirmedPermissions) { + if (r.equals(permission)) { + return true; + } + } + } + + if (context == null) { + Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity or App Context."); + return false; + } + + PackageInfo info = getPackageInfo(context); + + if (info == null) { + Log.d(PermissionConstants.LOG_TAG, "Unable to get Package info, will not be able to determine permissions to request."); + return false; + } + + confirmedPermissions = new ArrayList<>(Arrays.asList(info.requestedPermissions)); + for (String r : confirmedPermissions) { + if (r.equals(permission)) { + return true; + } + } + } catch (Exception ex) { + Log.d(PermissionConstants.LOG_TAG, "Unable to check manifest for permission: ", ex); + } + return false; + } + + @PermissionConstants.PermissionStatus + static int toPermissionStatus(final Activity activity, final String permissionName, int grantResult) { + if (grantResult == PackageManager.PERMISSION_DENIED) { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && PermissionUtils.isNeverAskAgainSelected(activity, permissionName) + ? PermissionConstants.PERMISSION_STATUS_NEVER_ASK_AGAIN + : PermissionConstants.PERMISSION_STATUS_DENIED; + } + + return PermissionConstants.PERMISSION_STATUS_GRANTED; + } + + static void updatePermissionShouldShowStatus(final Activity activity, @PermissionConstants.PermissionGroup int permission) { + if (activity == null) { + return; + } + + List names = getManifestNames(activity, permission); + + if (names == null || names.isEmpty()) { + return; + } + } + + @RequiresApi(api = Build.VERSION_CODES.M) + static boolean isNeverAskAgainSelected(final Activity activity, final String name) { + if (activity == null) { + return false; + } + + final boolean shouldShowRequestPermissionRationale = ActivityCompat.shouldShowRequestPermissionRationale(activity, name); + return !shouldShowRequestPermissionRationale; + } + + private static String determineBluetoothPermission(Context context, String permission) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && hasPermissionInManifest(context, null, permission )) { + return permission; + } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + if(hasPermissionInManifest(context, null, Manifest.permission.ACCESS_FINE_LOCATION)){ + return Manifest.permission.ACCESS_FINE_LOCATION; + } else if (hasPermissionInManifest(context, null, Manifest.permission.ACCESS_COARSE_LOCATION)){ + return Manifest.permission.ACCESS_COARSE_LOCATION; + } + + return null; + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && hasPermissionInManifest(context, null, Manifest.permission.ACCESS_FINE_LOCATION)) { + return Manifest.permission.ACCESS_FINE_LOCATION; + } + + return null; + } + + // Suppress deprecation warnings since its purpose is to support to be backwards compatible with + // pre TIRAMISU versions of Android + @SuppressWarnings("deprecation") + private static PackageInfo getPackageInfo(Context context) throws PackageManager.NameNotFoundException { + final PackageManager pm = context.getPackageManager(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return pm.getPackageInfo(context.getPackageName(), PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS)); + } else { + return pm.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); + } + } +} diff --git a/android/src/main/java/com/baseflow/permissionhandler/ServiceManager.java b/android/src/main/java/com/baseflow/permissionhandler/ServiceManager.java new file mode 100644 index 0000000..53b41c3 --- /dev/null +++ b/android/src/main/java/com/baseflow/permissionhandler/ServiceManager.java @@ -0,0 +1,177 @@ +package com.baseflow.permissionhandler; + +import static android.content.Context.BLUETOOTH_SERVICE; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.location.LocationManager; +import android.net.Uri; +import android.os.Build; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; + +import java.util.List; + +final class ServiceManager { + @FunctionalInterface + interface SuccessCallback { + void onSuccess(@PermissionConstants.ServiceStatus int serviceStatus); + } + + void checkServiceStatus( + int permission, + Context context, + SuccessCallback successCallback, + ErrorCallback errorCallback) { + if(context == null) { + Log.d(PermissionConstants.LOG_TAG, "Context cannot be null."); + errorCallback.onError("PermissionHandler.ServiceManager", "Android context cannot be null."); + return; + } + + if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION || + permission == PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS || + permission == PermissionConstants.PERMISSION_GROUP_LOCATION_WHEN_IN_USE) { + final int serviceStatus = isLocationServiceEnabled(context) + ? PermissionConstants.SERVICE_STATUS_ENABLED + : PermissionConstants.SERVICE_STATUS_DISABLED; + + successCallback.onSuccess(serviceStatus); + return; + } + if(permission == PermissionConstants.PERMISSION_GROUP_BLUETOOTH){ + final int serviceStatus = isBluetoothServiceEnabled(context) + ? PermissionConstants.SERVICE_STATUS_ENABLED + : PermissionConstants.SERVICE_STATUS_DISABLED; + + successCallback.onSuccess(serviceStatus); + } + + if (permission == PermissionConstants.PERMISSION_GROUP_PHONE) { + PackageManager pm = context.getPackageManager(); + if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { + successCallback.onSuccess(PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE); + return; + } + + TelephonyManager telephonyManager = (TelephonyManager) context + .getSystemService(Context.TELEPHONY_SERVICE); + + if (telephonyManager == null || telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) { + successCallback.onSuccess(PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE); + return; + } + + List callAppsList = getCallAppsList(pm); + + if (callAppsList.isEmpty()) { + successCallback.onSuccess(PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE); + return; + } + + if (telephonyManager.getSimState() != TelephonyManager.SIM_STATE_READY) { + successCallback.onSuccess(PermissionConstants.SERVICE_STATUS_DISABLED); + return; + } + + successCallback.onSuccess(PermissionConstants.SERVICE_STATUS_ENABLED); + return; + } + + if (permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) { + final int serviceStatus = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + ? PermissionConstants.SERVICE_STATUS_ENABLED + : PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE; + successCallback.onSuccess(serviceStatus); + return; + } + + successCallback.onSuccess(PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE); + } + + private boolean isLocationServiceEnabled(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + final LocationManager locationManager = context.getSystemService(LocationManager.class); + if (locationManager == null) { + return false; + } + + return locationManager.isLocationEnabled(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + return isLocationServiceEnabledKitKat(context); + } else { + return isLocationServiceEnablePreKitKat(context); + } + } + + // Suppress deprecation warnings since its purpose is to support to be backwards compatible with + // pre Pie versions of Android. + @SuppressWarnings("deprecation") + private static boolean isLocationServiceEnabledKitKat(Context context) + { + if (Build.VERSION.SDK_INT < VERSION_CODES.KITKAT) { + return false; + } + + final int locationMode; + + try { + locationMode = Settings.Secure.getInt( + context.getContentResolver(), + Settings.Secure.LOCATION_MODE); + } catch (Settings.SettingNotFoundException e) { + e.printStackTrace(); + return false; + } + + return locationMode != Settings.Secure.LOCATION_MODE_OFF; + } + + // Suppress deprecation warnings since its purpose is to support to be backwards compatible with + // pre KitKat versions of Android. + @SuppressWarnings("deprecation") + private static boolean isLocationServiceEnablePreKitKat(Context context) + { + if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) + return false; + + final String locationProviders = Settings.Secure.getString( + context.getContentResolver(), + Settings.Secure.LOCATION_PROVIDERS_ALLOWED); + return !TextUtils.isEmpty(locationProviders); + } + // Suppress deprecation warnings since its purpose is to support to be backwards compatible with + // pre S versions of Android + @SuppressWarnings("deprecation") + private boolean isBluetoothServiceEnabled(Context context) { + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) { + return BluetoothAdapter.getDefaultAdapter().isEnabled(); + } + + BluetoothManager manager = (BluetoothManager) context.getSystemService(BLUETOOTH_SERVICE); + final BluetoothAdapter adapter = manager.getAdapter(); + return adapter.isEnabled(); + } + + // Suppress deprecation warnings since its purpose is to support to be backwards compatible with + // pre TIRAMISU versions of Android + @SuppressWarnings("deprecation") + private List getCallAppsList(PackageManager pm) { + Intent callIntent = new Intent(Intent.ACTION_CALL); + callIntent.setData(Uri.parse("tel:123123")); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return pm.queryIntentActivities(callIntent, PackageManager.ResolveInfoFlags.of(0)); + } else { + return pm.queryIntentActivities(callIntent, 0); + } + } +} diff --git a/example/.metadata b/example/.metadata new file mode 100644 index 0000000..828ed53 --- /dev/null +++ b/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 06b979c4d5e1b499745422269f01a00341257058 + channel: master + +project_type: app diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..f1bcd34 --- /dev/null +++ b/example/README.md @@ -0,0 +1,16 @@ +# permission_handler_example + +Demonstrates how to use the permission_handler plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.io/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.io/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/example/android/.settings/org.eclipse.buildship.core.prefs b/example/android/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..9851512 --- /dev/null +++ b/example/android/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,13 @@ +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) +connection.project.dir= +eclipse.preferences.version=1 +gradle.user.home= +java.home=/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle new file mode 100644 index 0000000..fd8b8db --- /dev/null +++ b/example/android/app/build.gradle @@ -0,0 +1,55 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 33 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.baseflow.permissionhandler.example" + minSdkVersion 16 + targetSdkVersion 33 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..d692acb --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..519117b --- /dev/null +++ b/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 0000000..e793a00 --- /dev/null +++ b/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..449a9f9 --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..00fa441 --- /dev/null +++ b/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..699d4b0 --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle new file mode 100644 index 0000000..622ddc5 --- /dev/null +++ b/example/android/build.gradle @@ -0,0 +1,27 @@ +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..bc6a58a --- /dev/null +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle new file mode 100644 index 0000000..5a2f14f --- /dev/null +++ b/example/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/example/lib/main.dart b/example/lib/main.dart new file mode 100644 index 0000000..e99e698 --- /dev/null +++ b/example/lib/main.dart @@ -0,0 +1,141 @@ +import 'package:baseflow_plugin_template/baseflow_plugin_template.dart'; +import 'package:flutter/material.dart'; +import 'package:permission_handler_platform_interface/permission_handler_platform_interface.dart'; + +void main() { + runApp(BaseflowPluginExample( + pluginName: 'Permission Handler', + githubURL: 'https://github.com/Baseflow/flutter-permission-handler', + pubDevURL: 'https://pub.dev/packages/permission_handler', + pages: [PermissionHandlerWidget.createPage()])); +} + +///Defines the main theme color +final MaterialColor themeMaterialColor = + BaseflowPluginExample.createMaterialColor( + const Color.fromRGBO(48, 49, 60, 1)); + +/// A Flutter application demonstrating the functionality of this plugin +class PermissionHandlerWidget extends StatefulWidget { + /// Create a page containing the functionality of this plugin + static ExamplePage createPage() { + return ExamplePage( + Icons.location_on, (context) => PermissionHandlerWidget()); + } + + @override + _PermissionHandlerWidgetState createState() => + _PermissionHandlerWidgetState(); +} + +class _PermissionHandlerWidgetState extends State { + @override + Widget build(BuildContext context) { + return Center( + child: ListView( + children: Permission.values + .where((permission) { + return permission != Permission.unknown && + permission != Permission.mediaLibrary && + permission != Permission.photos && + permission != Permission.photosAddOnly && + permission != Permission.reminders && + permission != Permission.appTrackingTransparency && + permission != Permission.criticalAlerts; + }) + .map((permission) => PermissionWidget(permission)) + .toList()), + ); + } +} + +/// Permission widget containing information about the passed [Permission] +class PermissionWidget extends StatefulWidget { + /// Constructs a [PermissionWidget] for the supplied [Permission] + const PermissionWidget(this._permission); + + final Permission _permission; + + @override + _PermissionState createState() => _PermissionState(_permission); +} + +class _PermissionState extends State { + _PermissionState(this._permission); + + final Permission _permission; + final PermissionHandlerPlatform _permissionHandler = + PermissionHandlerPlatform.instance; + PermissionStatus _permissionStatus = PermissionStatus.denied; + + @override + void initState() { + super.initState(); + + _listenForPermissionStatus(); + } + + void _listenForPermissionStatus() async { + final status = await _permissionHandler.checkPermissionStatus(_permission); + setState(() => _permissionStatus = status); + } + + Color getPermissionColor() { + switch (_permissionStatus) { + case PermissionStatus.denied: + return Colors.red; + case PermissionStatus.granted: + return Colors.green; + case PermissionStatus.limited: + return Colors.orange; + default: + return Colors.grey; + } + } + + @override + Widget build(BuildContext context) { + return ListTile( + title: Text( + _permission.toString(), + style: Theme.of(context).textTheme.bodyText1, + ), + subtitle: Text( + _permissionStatus.toString(), + style: TextStyle(color: getPermissionColor()), + ), + trailing: (_permission is PermissionWithService) + ? IconButton( + icon: const Icon( + Icons.info, + color: Colors.white, + ), + onPressed: () { + checkServiceStatus( + context, _permission as PermissionWithService); + }) + : null, + onTap: () { + requestPermission(_permission); + }, + ); + } + + void checkServiceStatus( + BuildContext context, PermissionWithService permission) async { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + (await _permissionHandler.checkServiceStatus(permission)).toString()), + )); + } + + Future requestPermission(Permission permission) async { + final status = await _permissionHandler.requestPermissions([permission]); + + setState(() { + print(status); + _permissionStatus = status[permission] ?? PermissionStatus.denied; + print(_permissionStatus); + }); + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock new file mode 100644 index 0000000..9c42656 --- /dev/null +++ b/example/pubspec.lock @@ -0,0 +1,245 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.8.2" + baseflow_plugin_template: + dependency: "direct main" + description: + path: "." + ref: "v2.1.0" + resolved-ref: "0695ef43c098d75efffe5fd6212390b853f05bf7" + url: "https://github.com/Baseflow/baseflow_plugin_template.git" + source: git + version: "2.1.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.16.0" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.11" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.1" + permission_handler_android: + dependency: "direct dev" + description: + path: ".." + relative: true + source: path + version: "10.0.0" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.7.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.9" + url_launcher: + dependency: "direct dev" + description: + name: url_launcher + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.5" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.17" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.17" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.12" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" +sdks: + dart: ">=2.17.0-0 <3.0.0" + flutter: ">=2.10.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..e7000f3 --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,37 @@ +name: permission_handler_android_example +description: Demonstrates how to use the permission_handler_android plugin. + +environment: + sdk: ">=2.15.0 <3.0.0" + +dependencies: + baseflow_plugin_template: + git: + url: https://github.com/Baseflow/baseflow_plugin_template.git + ref: v2.1.0 + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + + permission_handler_android: + # When depending on this package from a real application you should use: + # permission_handler_android: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + url_launcher: ^6.0.12 + +flutter: + uses-material-design: true + + assets: + - res/images/baseflow_logo_def_light-02.png + - res/images/poweredByBaseflowLogoLight@3x.png + - packages/baseflow_plugin_template/logo.png + - packages/baseflow_plugin_template/poweredByBaseflow.png + diff --git a/example/res/images/baseflow_logo_def_light-02.png b/example/res/images/baseflow_logo_def_light-02.png new file mode 100644 index 0000000..85f071f Binary files /dev/null and b/example/res/images/baseflow_logo_def_light-02.png differ diff --git a/example/res/images/poweredByBaseflowLogoLight.png b/example/res/images/poweredByBaseflowLogoLight.png new file mode 100644 index 0000000..6b9f1aa Binary files /dev/null and b/example/res/images/poweredByBaseflowLogoLight.png differ diff --git a/example/res/images/poweredByBaseflowLogoLight@2x.png b/example/res/images/poweredByBaseflowLogoLight@2x.png new file mode 100644 index 0000000..66901d9 Binary files /dev/null and b/example/res/images/poweredByBaseflowLogoLight@2x.png differ diff --git a/example/res/images/poweredByBaseflowLogoLight@3x.png b/example/res/images/poweredByBaseflowLogoLight@3x.png new file mode 100644 index 0000000..36fbdff Binary files /dev/null and b/example/res/images/poweredByBaseflowLogoLight@3x.png differ diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..bab0fd3 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,79 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.16.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + permission_handler_platform_interface: + dependency: "direct main" + description: + name: permission_handler_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.7.0" + plugin_platform_interface: + dependency: "direct dev" + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" +sdks: + dart: ">=2.15.0 <3.0.0" + flutter: ">=2.8.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..e8efa98 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,25 @@ +name: permission_handler_android +description: Permission plugin for Flutter. This plugin provides the Android API to request and check permissions. +version: 10.0.0 +homepage: https://github.com/baseflow/flutter-permission-handler + +environment: + sdk: ">=2.15.0 <3.0.0" + flutter: ">=2.8.0" + +flutter: + plugin: + implements: permission_handler + platforms: + android: + package: com.baseflow.permissionhandler + pluginClass: PermissionHandlerPlugin + +dependencies: + flutter: + sdk: flutter + permission_handler_platform_interface: ^3.7.0 + +dev_dependencies: + flutter_lints: ^1.0.4 + plugin_platform_interface: ^2.0.0