From b9cfe590e6f2ca13990c696802e285d747ac7067 Mon Sep 17 00:00:00 2001 From: datang Date: Mon, 29 Aug 2022 11:22:46 +0800 Subject: [PATCH] all --- .dart_tool/package_config.json | 80 +++ .dart_tool/package_config_subset | 49 ++ .dart_tool/version | 1 + .idea/libraries/Dart_Packages.xml | 300 +++++++++++ .idea/libraries/Dart_SDK.xml | 27 + .idea/libraries/Flutter_Plugins.xml | 7 + .idea/modules.xml | 8 + .idea/permission_handler_android.iml | 18 + .idea/vcs.xml | 6 + .packages | 18 + AUTHORS | 8 + CHANGELOG.md | 12 + LICENSE | 21 + android/.classpath | 6 + .../org.eclipse.buildship.core.prefs | 13 + android/build.gradle | 40 ++ android/gradle.properties | 2 + .../gradle/wrapper/gradle-wrapper.properties | 5 + android/settings.gradle | 1 + android/src/main/AndroidManifest.xml | 3 + .../permissionhandler/AppSettingsManager.java | 39 ++ .../permissionhandler/ErrorCallback.java | 6 + .../MethodCallHandlerImpl.java | 104 ++++ .../PermissionConstants.java | 118 +++++ .../PermissionHandlerPlugin.java | 154 ++++++ .../permissionhandler/PermissionManager.java | 497 ++++++++++++++++++ .../permissionhandler/PermissionUtils.java | 407 ++++++++++++++ .../permissionhandler/ServiceManager.java | 177 +++++++ example/.metadata | 10 + example/README.md | 16 + .../org.eclipse.buildship.core.prefs | 13 + example/android/app/build.gradle | 55 ++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 103 ++++ .../com/example/example/MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 8 + .../app/src/profile/AndroidManifest.xml | 52 ++ example/android/build.gradle | 27 + example/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 6 + example/android/settings.gradle | 15 + example/lib/main.dart | 141 +++++ example/pubspec.lock | 245 +++++++++ example/pubspec.yaml | 37 ++ .../res/images/baseflow_logo_def_light-02.png | Bin 0 -> 22696 bytes .../res/images/poweredByBaseflowLogoLight.png | Bin 0 -> 4561 bytes .../images/poweredByBaseflowLogoLight@2x.png | Bin 0 -> 10820 bytes .../images/poweredByBaseflowLogoLight@3x.png | Bin 0 -> 17550 bytes pubspec.lock | 79 +++ pubspec.yaml | 25 + 58 files changed, 3017 insertions(+) create mode 100644 .dart_tool/package_config.json create mode 100644 .dart_tool/package_config_subset create mode 100644 .dart_tool/version create mode 100644 .idea/libraries/Dart_Packages.xml create mode 100644 .idea/libraries/Dart_SDK.xml create mode 100644 .idea/libraries/Flutter_Plugins.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/permission_handler_android.iml create mode 100644 .idea/vcs.xml create mode 100644 .packages create mode 100644 AUTHORS create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 android/.classpath create mode 100644 android/.settings/org.eclipse.buildship.core.prefs create mode 100644 android/build.gradle create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100644 android/settings.gradle create mode 100644 android/src/main/AndroidManifest.xml create mode 100644 android/src/main/java/com/baseflow/permissionhandler/AppSettingsManager.java create mode 100644 android/src/main/java/com/baseflow/permissionhandler/ErrorCallback.java create mode 100644 android/src/main/java/com/baseflow/permissionhandler/MethodCallHandlerImpl.java create mode 100644 android/src/main/java/com/baseflow/permissionhandler/PermissionConstants.java create mode 100644 android/src/main/java/com/baseflow/permissionhandler/PermissionHandlerPlugin.java create mode 100644 android/src/main/java/com/baseflow/permissionhandler/PermissionManager.java create mode 100644 android/src/main/java/com/baseflow/permissionhandler/PermissionUtils.java create mode 100644 android/src/main/java/com/baseflow/permissionhandler/ServiceManager.java create mode 100644 example/.metadata create mode 100644 example/README.md create mode 100644 example/android/.settings/org.eclipse.buildship.core.prefs create mode 100644 example/android/app/build.gradle create mode 100644 example/android/app/src/debug/AndroidManifest.xml create mode 100644 example/android/app/src/main/AndroidManifest.xml create mode 100644 example/android/app/src/main/kotlin/com/example/example/MainActivity.kt create mode 100644 example/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/values-night/styles.xml create mode 100644 example/android/app/src/main/res/values/styles.xml create mode 100644 example/android/app/src/profile/AndroidManifest.xml create mode 100644 example/android/build.gradle create mode 100644 example/android/gradle.properties create mode 100644 example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 example/android/settings.gradle create mode 100644 example/lib/main.dart create mode 100644 example/pubspec.lock create mode 100644 example/pubspec.yaml create mode 100644 example/res/images/baseflow_logo_def_light-02.png create mode 100644 example/res/images/poweredByBaseflowLogoLight.png create mode 100644 example/res/images/poweredByBaseflowLogoLight@2x.png create mode 100644 example/res/images/poweredByBaseflowLogoLight@3x.png create mode 100644 pubspec.lock create mode 100644 pubspec.yaml 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 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..85f071f275a6572658078b2e05b773cf5e006907 GIT binary patch literal 22696 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

9yGp11G?`E!)dXaQu2W~J` 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.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