opt AndroidPermissionManager
This commit is contained in:
parent
63185a5bcb
commit
8cd9f8745d
@ -88,6 +88,14 @@ class MainActivity : FlutterActivity() {
|
|||||||
result.success(false)
|
result.success(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
START_ACTION -> {
|
||||||
|
if (call.arguments is String) {
|
||||||
|
startAction(context, call.arguments as String)
|
||||||
|
result.success(true)
|
||||||
|
} else {
|
||||||
|
result.success(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
"check_video_permission" -> {
|
"check_video_permission" -> {
|
||||||
mainService?.let {
|
mainService?.let {
|
||||||
result.success(it.checkMediaPermission())
|
result.success(it.checkMediaPermission())
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.carriez.flutter_hbb
|
package com.carriez.flutter_hbb
|
||||||
|
|
||||||
|
import android.Manifest.permission.*
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@ -35,6 +36,10 @@ const val REQ_REQUEST_MEDIA_PROJECTION = 201
|
|||||||
// Activity responseCode
|
// Activity responseCode
|
||||||
const val RES_FAILED = -100
|
const val RES_FAILED = -100
|
||||||
|
|
||||||
|
// Flutter channel
|
||||||
|
const val START_ACTION = "start_action";
|
||||||
|
const val IGNORE_BATTERY_OPTIMIZATIONS = "ignore_battery_optimizations";
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("ConstantLocale")
|
@SuppressLint("ConstantLocale")
|
||||||
val LOCAL_NAME = Locale.getDefault().toString()
|
val LOCAL_NAME = Locale.getDefault().toString()
|
||||||
@ -44,56 +49,10 @@ data class Info(
|
|||||||
var width: Int, var height: Int, var scale: Int, var dpi: Int
|
var width: Int, var height: Int, var scale: Int, var dpi: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
fun testVP9Support(): Boolean {
|
|
||||||
return true
|
|
||||||
val res = MediaCodecList(MediaCodecList.ALL_CODECS)
|
|
||||||
.findEncoderForFormat(
|
|
||||||
MediaFormat.createVideoFormat(
|
|
||||||
MediaFormat.MIMETYPE_VIDEO_VP9,
|
|
||||||
SCREEN_INFO.width,
|
|
||||||
SCREEN_INFO.width
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return res != null
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.M)
|
@RequiresApi(Build.VERSION_CODES.M)
|
||||||
fun requestPermission(context: Context, type: String) {
|
fun requestPermission(context: Context, type: String) {
|
||||||
val permission = when (type) {
|
|
||||||
"ignore_battery_optimizations" -> {
|
|
||||||
try {
|
|
||||||
context.startActivity(Intent(ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
|
|
||||||
data = Uri.parse("package:" + context.packageName)
|
|
||||||
})
|
|
||||||
} catch (e:Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
"application_details_settings" -> {
|
|
||||||
try {
|
|
||||||
context.startActivity(Intent(ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
data = Uri.parse("package:" + context.packageName)
|
|
||||||
})
|
|
||||||
} catch (e:Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
"audio" -> {
|
|
||||||
Permission.RECORD_AUDIO
|
|
||||||
}
|
|
||||||
"file" -> {
|
|
||||||
Permission.MANAGE_EXTERNAL_STORAGE
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
XXPermissions.with(context)
|
XXPermissions.with(context)
|
||||||
.permission(permission)
|
.permission(type)
|
||||||
.request { _, all ->
|
.request { _, all ->
|
||||||
if (all) {
|
if (all) {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
@ -108,22 +67,23 @@ fun requestPermission(context: Context, type: String) {
|
|||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.M)
|
@RequiresApi(Build.VERSION_CODES.M)
|
||||||
fun checkPermission(context: Context, type: String): Boolean {
|
fun checkPermission(context: Context, type: String): Boolean {
|
||||||
val permission = when (type) {
|
if (IGNORE_BATTERY_OPTIMIZATIONS == type) {
|
||||||
"ignore_battery_optimizations" -> {
|
val pw = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
val pw = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
return pw.isIgnoringBatteryOptimizations(context.packageName)
|
||||||
return pw.isIgnoringBatteryOptimizations(context.packageName)
|
}
|
||||||
}
|
return XXPermissions.isGranted(context, type)
|
||||||
"audio" -> {
|
}
|
||||||
Permission.RECORD_AUDIO
|
|
||||||
}
|
@RequiresApi(Build.VERSION_CODES.M)
|
||||||
"file" -> {
|
fun startAction(context: Context, action: String) {
|
||||||
Permission.MANAGE_EXTERNAL_STORAGE
|
try {
|
||||||
}
|
context.startActivity(Intent(action).apply {
|
||||||
else -> {
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
return false
|
data = Uri.parse("package:" + context.packageName)
|
||||||
}
|
})
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
return XXPermissions.isGranted(context, permission)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AudioReader(val bufSize: Int, private val maxFrames: Int) {
|
class AudioReader(val bufSize: Int, private val maxFrames: Int) {
|
||||||
|
@ -907,21 +907,14 @@ class AccessibilityListener extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PermissionManager {
|
class AndroidPermissionManager {
|
||||||
static Completer<bool>? _completer;
|
static Completer<bool>? _completer;
|
||||||
static Timer? _timer;
|
static Timer? _timer;
|
||||||
static var _current = "";
|
static var _current = "";
|
||||||
|
|
||||||
static final permissions = [
|
|
||||||
"audio",
|
|
||||||
"file",
|
|
||||||
"ignore_battery_optimizations",
|
|
||||||
"application_details_settings"
|
|
||||||
];
|
|
||||||
|
|
||||||
static bool isWaitingFile() {
|
static bool isWaitingFile() {
|
||||||
if (_completer != null) {
|
if (_completer != null) {
|
||||||
return !_completer!.isCompleted && _current == "file";
|
return !_completer!.isCompleted && _current == kManageExternalStorage;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -930,9 +923,6 @@ class PermissionManager {
|
|||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
return Future.value(true);
|
return Future.value(true);
|
||||||
}
|
}
|
||||||
if (!permissions.contains(type)) {
|
|
||||||
return Future.error("Wrong permission!$type");
|
|
||||||
}
|
|
||||||
return gFFI.invokeMethod("check_permission", type);
|
return gFFI.invokeMethod("check_permission", type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -940,17 +930,16 @@ class PermissionManager {
|
|||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
return Future.value(true);
|
return Future.value(true);
|
||||||
}
|
}
|
||||||
if (!permissions.contains(type)) {
|
|
||||||
return Future.error("Wrong permission!$type");
|
|
||||||
}
|
|
||||||
|
|
||||||
gFFI.invokeMethod("request_permission", type);
|
gFFI.invokeMethod("request_permission", type);
|
||||||
if (type == "ignore_battery_optimizations") {
|
|
||||||
|
// kIgnoreBatteryOptimizations permission doesn't depend on callback result, the result will be checked and updated on page resume
|
||||||
|
if (type == kIgnoreBatteryOptimizations) {
|
||||||
return Future.value(false);
|
return Future.value(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_current = type;
|
_current = type;
|
||||||
_completer = Completer<bool>();
|
_completer = Completer<bool>();
|
||||||
gFFI.invokeMethod("request_permission", type);
|
|
||||||
|
|
||||||
// timeout
|
// timeout
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
@ -1484,8 +1473,8 @@ connect(BuildContext context, String id,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isFileTransfer) {
|
if (isFileTransfer) {
|
||||||
if (!await PermissionManager.check("file")) {
|
if (!await AndroidPermissionManager.check(kManageExternalStorage)) {
|
||||||
if (!await PermissionManager.request("file")) {
|
if (!await AndroidPermissionManager.request(kManageExternalStorage)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,11 +59,12 @@ const double kDesktopFileTransferMaximumWidth = 300;
|
|||||||
const double kDesktopFileTransferRowHeight = 30.0;
|
const double kDesktopFileTransferRowHeight = 30.0;
|
||||||
const double kDesktopFileTransferHeaderHeight = 25.0;
|
const double kDesktopFileTransferHeaderHeight = 25.0;
|
||||||
|
|
||||||
EdgeInsets get kDragToResizeAreaPadding => !kUseCompatibleUiMode && Platform.isLinux
|
EdgeInsets get kDragToResizeAreaPadding =>
|
||||||
? stateGlobal.fullscreen || stateGlobal.maximize
|
!kUseCompatibleUiMode && Platform.isLinux
|
||||||
? EdgeInsets.zero
|
? stateGlobal.fullscreen || stateGlobal.maximize
|
||||||
: EdgeInsets.all(5.0)
|
? EdgeInsets.zero
|
||||||
: EdgeInsets.zero;
|
: EdgeInsets.all(5.0)
|
||||||
|
: EdgeInsets.zero;
|
||||||
// https://en.wikipedia.org/wiki/Non-breaking_space
|
// https://en.wikipedia.org/wiki/Non-breaking_space
|
||||||
const int $nbsp = 0x00A0;
|
const int $nbsp = 0x00A0;
|
||||||
|
|
||||||
@ -136,6 +137,21 @@ const kRemoteAudioDualWay = 'dual-way';
|
|||||||
|
|
||||||
const kIgnoreDpi = true;
|
const kIgnoreDpi = true;
|
||||||
|
|
||||||
|
/// Android constants
|
||||||
|
const kActionApplicationDetailsSettings =
|
||||||
|
"android.settings.APPLICATION_DETAILS_SETTINGS";
|
||||||
|
const kActionRequestIgnoreBatteryOptimizations =
|
||||||
|
"android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
|
||||||
|
const kRecordAudio = "android.permission.RECORD_AUDIO";
|
||||||
|
const kManageExternalStorage = "android.permission.MANAGE_EXTERNAL_STORAGE";
|
||||||
|
const kSystemAlertWindow = "android.permission.SYSTEM_ALERT_WINDOW";
|
||||||
|
|
||||||
|
/// [kIgnoreBatteryOptimizations] not a Android Permission, it is a custom key, used in `ignore battery optimizations` check
|
||||||
|
const kIgnoreBatteryOptimizations = "ignore_battery_optimizations";
|
||||||
|
|
||||||
|
/// Android channel invoke type key
|
||||||
|
const kStartAction = "start_action";
|
||||||
|
|
||||||
/// flutter/packages/flutter/lib/src/services/keyboard_key.dart -> _keyLabels
|
/// flutter/packages/flutter/lib/src/services/keyboard_key.dart -> _keyLabels
|
||||||
/// see [LogicalKeyboardKey.keyLabel]
|
/// see [LogicalKeyboardKey.keyLabel]
|
||||||
const Map<int, String> logicalKeyMap = <int, String>{
|
const Map<int, String> logicalKeyMap = <int, String>{
|
||||||
|
@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../common/widgets/dialog.dart';
|
import '../../common/widgets/dialog.dart';
|
||||||
|
import '../../consts.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
import '../../models/server_model.dart';
|
import '../../models/server_model.dart';
|
||||||
import 'home_page.dart';
|
import 'home_page.dart';
|
||||||
@ -150,10 +151,11 @@ class _ServerPageState extends State<ServerPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void checkService() async {
|
void checkService() async {
|
||||||
gFFI.invokeMethod("check_service"); // jvm
|
gFFI.invokeMethod("check_service");
|
||||||
// for Android 10/11,MANAGE_EXTERNAL_STORAGE permission from a system setting page
|
// for Android 10/11, request MANAGE_EXTERNAL_STORAGE permission from system setting page
|
||||||
if (PermissionManager.isWaitingFile() && !gFFI.serverModel.fileOk) {
|
if (AndroidPermissionManager.isWaitingFile() && !gFFI.serverModel.fileOk) {
|
||||||
PermissionManager.complete("file", await PermissionManager.check("file"));
|
AndroidPermissionManager.complete(kManageExternalStorage,
|
||||||
|
await AndroidPermissionManager.check(kManageExternalStorage));
|
||||||
debugPrint("file permission finished");
|
debugPrint("file permission finished");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -567,7 +569,7 @@ void androidChannelInit() {
|
|||||||
{
|
{
|
||||||
var type = arguments["type"] as String;
|
var type = arguments["type"] as String;
|
||||||
var result = arguments["result"] as bool;
|
var result = arguments["result"] as bool;
|
||||||
PermissionManager.complete(type, result);
|
AndroidPermissionManager.complete(type, result);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "on_media_projection_canceled":
|
case "on_media_projection_canceled":
|
||||||
|
@ -10,6 +10,7 @@ import 'package:url_launcher/url_launcher.dart';
|
|||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../common/widgets/dialog.dart';
|
import '../../common/widgets/dialog.dart';
|
||||||
import '../../common/widgets/login.dart';
|
import '../../common/widgets/login.dart';
|
||||||
|
import '../../consts.dart';
|
||||||
import '../../models/model.dart';
|
import '../../models/model.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
import '../widgets/dialog.dart';
|
import '../widgets/dialog.dart';
|
||||||
@ -133,7 +134,8 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> updateIgnoreBatteryStatus() async {
|
Future<bool> updateIgnoreBatteryStatus() async {
|
||||||
final res = await PermissionManager.check("ignore_battery_optimizations");
|
final res =
|
||||||
|
await AndroidPermissionManager.check(kIgnoreBatteryOptimizations);
|
||||||
if (_ignoreBatteryOpt != res) {
|
if (_ignoreBatteryOpt != res) {
|
||||||
_ignoreBatteryOpt = res;
|
_ignoreBatteryOpt = res;
|
||||||
return true;
|
return true;
|
||||||
@ -265,7 +267,8 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
]),
|
]),
|
||||||
onToggle: (v) async {
|
onToggle: (v) async {
|
||||||
if (v) {
|
if (v) {
|
||||||
PermissionManager.request("ignore_battery_optimizations");
|
gFFI.invokeMethod(
|
||||||
|
kStartAction, kActionRequestIgnoreBatteryOptimizations);
|
||||||
} else {
|
} else {
|
||||||
final res = await gFFI.dialogManager
|
final res = await gFFI.dialogManager
|
||||||
.show<bool>((setState, close) => CustomAlertDialog(
|
.show<bool>((setState, close) => CustomAlertDialog(
|
||||||
@ -282,7 +285,8 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
],
|
],
|
||||||
));
|
));
|
||||||
if (res == true) {
|
if (res == true) {
|
||||||
PermissionManager.request("application_details_settings");
|
gFFI.invokeMethod(
|
||||||
|
kStartAction, kActionApplicationDetailsSettings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hbb/consts.dart';
|
||||||
import 'package:flutter_hbb/main.dart';
|
import 'package:flutter_hbb/main.dart';
|
||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -154,7 +155,8 @@ class ServerModel with ChangeNotifier {
|
|||||||
/// file true by default (if permission on)
|
/// file true by default (if permission on)
|
||||||
checkAndroidPermission() async {
|
checkAndroidPermission() async {
|
||||||
// audio
|
// audio
|
||||||
if (androidVersion < 30 || !await PermissionManager.check("audio")) {
|
if (androidVersion < 30 ||
|
||||||
|
!await AndroidPermissionManager.check(kRecordAudio)) {
|
||||||
_audioOk = false;
|
_audioOk = false;
|
||||||
bind.mainSetOption(key: "enable-audio", value: "N");
|
bind.mainSetOption(key: "enable-audio", value: "N");
|
||||||
} else {
|
} else {
|
||||||
@ -163,7 +165,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// file
|
// file
|
||||||
if (!await PermissionManager.check("file")) {
|
if (!await AndroidPermissionManager.check(kManageExternalStorage)) {
|
||||||
_fileOk = false;
|
_fileOk = false;
|
||||||
bind.mainSetOption(key: "enable-file-transfer", value: "N");
|
bind.mainSetOption(key: "enable-file-transfer", value: "N");
|
||||||
} else {
|
} else {
|
||||||
@ -229,8 +231,8 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleAudio() async {
|
toggleAudio() async {
|
||||||
if (!_audioOk && !await PermissionManager.check("audio")) {
|
if (!_audioOk && !await AndroidPermissionManager.check(kRecordAudio)) {
|
||||||
final res = await PermissionManager.request("audio");
|
final res = await AndroidPermissionManager.request(kRecordAudio);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
// TODO handle fail
|
// TODO handle fail
|
||||||
return;
|
return;
|
||||||
@ -243,8 +245,10 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleFile() async {
|
toggleFile() async {
|
||||||
if (!_fileOk && !await PermissionManager.check("file")) {
|
if (!_fileOk &&
|
||||||
final res = await PermissionManager.request("file");
|
!await AndroidPermissionManager.check(kManageExternalStorage)) {
|
||||||
|
final res =
|
||||||
|
await AndroidPermissionManager.request(kManageExternalStorage);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
// TODO handle fail
|
// TODO handle fail
|
||||||
return;
|
return;
|
||||||
@ -561,7 +565,8 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> closeAll() async {
|
Future<void> closeAll() async {
|
||||||
await Future.wait(_clients.map((client) => bind.cmCloseConnection(connId: client.id)));
|
await Future.wait(
|
||||||
|
_clients.map((client) => bind.cmCloseConnection(connId: client.id)));
|
||||||
_clients.clear();
|
_clients.clear();
|
||||||
tabController.state.value.tabs.clear();
|
tabController.state.value.tabs.clear();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user