Commit 8d86c490 by Maurits van Beusekom

Migrate to FlutterPlugin API on Android

parent e3945eb1
## vNext
* Android: Migrate to FlutterPlugin Android API (better support for Add-to-App);
* Android: Suppress JAVA warnings which are generated to old platform code (only executes on older platforms).
## 4.3.0
* Allow requesting location permissions when location services are disabled (on iOS this will redirect the user to the Location settings page);
......
......@@ -244,11 +244,21 @@ enum PermissionGroup {
/// Android: None
/// iOS: MPMediaLibrary
mediaLibrary
mediaLibrary,
/// Android: Check notification enable
/// iOS: Check and request notification permission
notification
notification,
/// Android Q: Check and request permissions to read from the media location (ACCESS_MEDIA_LOCATION)
/// Android pre-Q: Nothing
/// iOS: Nothing
accessMediaLocation,
/// Android Q: Check and request permissions to access the Activity Recognition API
/// Android pre-Q: Nothing
/// iOS: Nothing (should implement access to CMMotionActivity, see issue #219)
activityRecognition,
}
```
......@@ -261,9 +271,6 @@ enum PermissionStatus {
/// Permission to access the requested feature is denied by the user.
denied,
/// Permissions to access the feature is granted by the user but the feature is disabled.
disabled,
/// Permission to access the requested feature is granted by the user.
granted,
......
......@@ -32,6 +32,10 @@ android {
lintOptions {
disable 'InvalidPackage'
}
compileOptions {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
}
dependencies {
......
package com.baseflow.permissionhandler;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
final class AppSettingsManager {
boolean openAppSettings(Context applicationContext) {
if (applicationContext == null) {
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity or App Context.");
return false;
}
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:" + applicationContext.getPackageName()));
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
applicationContext.startActivity(settingsIntent);
return true;
} catch (Exception ex) {
return false;
}
}
}
package com.baseflow.permissionhandler;
import android.app.Activity;
import android.content.Context;
import androidx.annotation.NonNull;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.Result;
import com.baseflow.permissionhandler.PermissionManager.ActivityRegistry;
import com.baseflow.permissionhandler.PermissionManager.PermissionRegistry;
import java.util.List;
final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {
private final Context applicationContext;
private final Activity activity;
private final AppSettingsManager appSettingsManager;
private final PermissionManager permissionManager;
private final ServiceManager serviceManager;
private final ActivityRegistry activityRegistry;
private final PermissionRegistry permissionRegistry;
private final MethodChannel methodChannel;
MethodCallHandlerImpl(
Context applicationContext,
Activity activity,
BinaryMessenger messenger,
AppSettingsManager appSettingsManager,
PermissionManager permissionManager,
ServiceManager serviceManager,
ActivityRegistry activityRegistry,
PermissionRegistry permissionRegistry) {
this.applicationContext = applicationContext;
this.activity = activity;
this.appSettingsManager = appSettingsManager;
this.permissionManager = permissionManager;
this.serviceManager = serviceManager;
this.activityRegistry = activityRegistry;
this.permissionRegistry = permissionRegistry;
methodChannel = new MethodChannel(
messenger,
"flutter.baseflow.com/permissions/methods");
methodChannel.setMethodCallHandler(this);
}
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
{
switch (call.method) {
case "checkPermissionStatus": {
@PermissionConstants.PermissionGroup final int permission = (int) call.arguments;
@PermissionConstants.PermissionStatus final int permissionStatus =
permissionManager.checkPermissionStatus(
permission,
activity);
result.success(permissionStatus);
break;
}
case "checkServiceStatus": {
@PermissionConstants.PermissionGroup final int permission = (int) call.arguments;
@PermissionConstants.ServiceStatus final int serviceStatus =
serviceManager.checkServiceStatus(
permission,
activity);
result.success(serviceStatus);
break;
}
case "requestPermissions":
final List<Integer> permissions = call.arguments();
permissionManager.requestPermissions(
permissions,
activity,
activityRegistry,
permissionRegistry,
result::success,
(String errorCode, String errorDiscription) -> {
result.error(errorCode, errorDiscription, null);
});
break;
case "shouldShowRequestPermissionRationale": {
@PermissionConstants.PermissionGroup final int permission = (int) call.arguments;
final boolean showRationale = permissionManager
.shouldShowRequestPermissionRationale(permission, activity);
result.success(showRationale);
break;
}
case "openAppSettings":
boolean isOpen = appSettingsManager.openAppSettings(applicationContext);
result.success(isOpen);
break;
default:
result.notImplemented();
break;
}
}
void stopListening() {
methodChannel.setMethodCallHandler(null);
}
}
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 = 5672353;
//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_REMINDERS = 10;
static final int PERMISSION_GROUP_SENSORS = 11;
static final int PERMISSION_GROUP_SMS = 12;
static final int PERMISSION_GROUP_SPEECH = 13;
static final int PERMISSION_GROUP_STORAGE = 14;
static final int PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS = 15;
static final int PERMISSION_GROUP_NOTIFICATION = 16;
static final int PERMISSION_GROUP_ACCESS_MEDIA_LOCATION = 17;
static final int PERMISSION_GROUP_ACTIVITY_RECOGNITION = 18;
static final int PERMISSION_GROUP_UNKNOWN = 19;
@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,
})
@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_UNKNOWN = 3;
static final int PERMISSION_STATUS_NEWER_ASK_AGAIN = 4;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
PERMISSION_STATUS_DENIED,
PERMISSION_STATUS_GRANTED,
PERMISSION_STATUS_RESTRICTED,
PERMISSION_STATUS_UNKNOWN,
PERMISSION_STATUS_NEWER_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;
static final int SERVICE_STATUS_UNKNOWN = 3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
SERVICE_STATUS_DISABLED,
SERVICE_STATUS_ENABLED,
SERVICE_STATUS_NOT_APPLICABLE,
SERVICE_STATUS_UNKNOWN,
})
@interface ServiceStatus {
}
}
package com.baseflow.permissionhandler;
import android.app.Activity;
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 {
@PermissionConstants.ServiceStatus
int checkServiceStatus(
int permission,
Activity activity) {
if (activity == null) {
Log.d(PermissionConstants.LOG_TAG, "Unable to detect current Activity or App Context.");
return PermissionConstants.SERVICE_STATUS_UNKNOWN;
}
if (permission == PermissionConstants.PERMISSION_GROUP_LOCATION ||
permission == PermissionConstants.PERMISSION_GROUP_LOCATION_ALWAYS ||
permission == PermissionConstants.PERMISSION_GROUP_LOCATION_WHEN_IN_USE) {
return isLocationServiceEnabled(activity)
? PermissionConstants.SERVICE_STATUS_ENABLED
: PermissionConstants.SERVICE_STATUS_DISABLED;
}
if (permission == PermissionConstants.PERMISSION_GROUP_PHONE) {
PackageManager pm = activity.getPackageManager();
if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
return PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE;
}
TelephonyManager telephonyManager = (TelephonyManager) activity
.getSystemService(Context.TELEPHONY_SERVICE);
if (telephonyManager == null || telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) {
return PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE;
}
Intent callIntent = new Intent(Intent.ACTION_CALL);
callIntent.setData(Uri.parse("tel:123123"));
List<ResolveInfo> callAppsList = pm.queryIntentActivities(callIntent, 0);
if (callAppsList.isEmpty()) {
return PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE;
}
if (telephonyManager.getSimState() != TelephonyManager.SIM_STATE_READY) {
return PermissionConstants.SERVICE_STATUS_DISABLED;
}
return PermissionConstants.SERVICE_STATUS_ENABLED;
}
if (permission == PermissionConstants.PERMISSION_GROUP_IGNORE_BATTERY_OPTIMIZATIONS) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
? PermissionConstants.SERVICE_STATUS_ENABLED
: PermissionConstants.SERVICE_STATUS_NOT_APPLICABLE;
}
return 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 isLocationServiceEnablePrePie(context);
} else {
return isLocationServiceEnablePreKitKat(context);
}
}
// Suppress deprecation warnings since it's purpose is to support to be backwards compatible with
// pre Pie versions of Android.
@SuppressWarnings("deprecation")
private static boolean isLocationServiceEnablePrePie(Context context)
{
if (VERSION.SDK_INT < VERSION_CODES.P)
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 it's 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);
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.permissionhandlerexample">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- Permissions options for the `contacts` group -->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<!-- Permissions options for the `storage` group -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- Permissions options for the `camera` group -->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- Permissions options for the `sms` group -->
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH"/>
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
<!-- Permissions options for the `phone` group -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.ADD_VOICEMAIL"/>
<uses-permission android:name="android.permission.USE_SIP"/>
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<!-- Permissions options for the `calendar` group -->
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<!-- Permissions options for the `location` group -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Permissions options for the `microphone` or `speech` group -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- Permissions options for the `sensors` group -->
<uses-permission android:name="android.permission.BODY_SENSORS" />
<!-- Permissions options for the `access_media_location` group -->
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
</manifest>
......@@ -51,28 +51,30 @@
<!-- Permissions options for the `sensors` group -->
<uses-permission android:name="android.permission.BODY_SENSORS" />
<!-- Permissions options for the `access_media_location` group -->
<!-- Permissions options for the `accessMediaLocation` group -->
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<!-- Permissions options for the `activityRecognition` group -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<application
android:name="io.flutter.app.FlutterApplication"
android:icon="@mipmap/ic_launcher"
android:label="permission_handler_example"
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
<activity android:name="io.flutter.embedding.android.FlutterActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:theme="@android:style/Theme.Black.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
package com.example.permissionhandlerexample;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment