diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt index 126e169da..e4b42cf08 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/MainActivity.kt @@ -18,6 +18,7 @@ import android.provider.Settings import android.util.Log import android.view.WindowManager import androidx.annotation.RequiresApi +import com.hjq.permissions.XXPermissions import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel @@ -76,7 +77,7 @@ class MainActivity : FlutterActivity() { } "check_permission" -> { if (call.arguments is String) { - result.success(checkPermission(context, call.arguments as String)) + result.success(XXPermissions.isGranted(context, call.arguments as String)) } else { result.success(false) } @@ -115,10 +116,6 @@ class MainActivity : FlutterActivity() { ) result.success(true) } - "init_input" -> { - initInput() - result.success(true) - } "stop_input" -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { InputService.ctx?.disableSelf() @@ -177,13 +174,6 @@ class MainActivity : FlutterActivity() { } } - private fun initInput() { - val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS) - if (intent.resolveActivity(packageManager) != null) { - startActivity(intent) - } - } - override fun onResume() { super.onResume() val inputPer = InputService.isOpen diff --git a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/common.kt b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/common.kt index 2d53ea010..0c34c2223 100644 --- a/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/common.kt +++ b/flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/common.kt @@ -13,6 +13,7 @@ import android.os.Build import android.os.Handler import android.os.Looper import android.os.PowerManager +import android.provider.Settings import android.provider.Settings.* import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat.getSystemService @@ -41,8 +42,6 @@ const val START_ACTION = "start_action" const val GET_START_ON_BOOT_OPT = "get_start_on_boot_opt" const val SET_START_ON_BOOT_OPT = "set_start_on_boot_opt" -const val IGNORE_BATTERY_OPTIMIZATIONS = "ignore_battery_optimizations" - const val KEY_SHARED_PREFERENCES = "KEY_SHARED_PREFERENCES" const val KEY_START_ON_BOOT_OPT = "KEY_START_ON_BOOT_OPT" @@ -70,21 +69,15 @@ fun requestPermission(context: Context, type: String) { } } -@RequiresApi(Build.VERSION_CODES.M) -fun checkPermission(context: Context, type: String): Boolean { - if (IGNORE_BATTERY_OPTIMIZATIONS == type) { - val pw = context.getSystemService(Context.POWER_SERVICE) as PowerManager - return pw.isIgnoringBatteryOptimizations(context.packageName) - } - return XXPermissions.isGranted(context, type) -} - @RequiresApi(Build.VERSION_CODES.M) fun startAction(context: Context, action: String) { try { context.startActivity(Intent(action).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - data = Uri.parse("package:" + context.packageName) + // don't pass package name when launch ACTION_ACCESSIBILITY_SETTINGS + if (ACTION_ACCESSIBILITY_SETTINGS != action) { + data = Uri.parse("package:" + context.packageName) + } }) } catch (e: Exception) { e.printStackTrace() diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 89b2c1b28..155fcc743 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -931,6 +931,8 @@ class AndroidPermissionManager { gFFI.invokeMethod(AndroidChannel.kStartAction, action); } + /// We use XXPermissions to request permissions, + /// for supported types, see https://github.com/getActivity/XXPermissions/blob/e46caea32a64ad7819df62d448fb1c825481cd28/library/src/main/java/com/hjq/permissions/Permission.java static Future request(String type) { if (isDesktop) { return Future.value(true); @@ -938,17 +940,16 @@ class AndroidPermissionManager { gFFI.invokeMethod("request_permission", type); - // 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); + // clear last task + if (_completer?.isCompleted == false) { + _completer?.complete(false); } + _timer?.cancel(); _current = type; _completer = Completer(); - // timeout - _timer?.cancel(); - _timer = Timer(Duration(seconds: 60), () { + _timer = Timer(Duration(seconds: 120), () { if (_completer == null) return; if (!_completer!.isCompleted) { _completer!.complete(false); diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 3edafbf62..b075ee76f 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -140,17 +140,14 @@ const kIgnoreDpi = true; /// Android constants const kActionApplicationDetailsSettings = "android.settings.APPLICATION_DETAILS_SETTINGS"; -const kActionRequestIgnoreBatteryOptimizations = - "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"; const kActionAccessibilitySettings = "android.settings.ACCESSIBILITY_SETTINGS"; const kRecordAudio = "android.permission.RECORD_AUDIO"; const kManageExternalStorage = "android.permission.MANAGE_EXTERNAL_STORAGE"; +const kRequestIgnoreBatteryOptimizations = + "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"; 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 class AndroidChannel { static final kStartAction = "start_action"; diff --git a/flutter/lib/mobile/pages/settings_page.dart b/flutter/lib/mobile/pages/settings_page.dart index 397c117f7..d4887bb58 100644 --- a/flutter/lib/mobile/pages/settings_page.dart +++ b/flutter/lib/mobile/pages/settings_page.dart @@ -65,7 +65,6 @@ class _SettingsState extends State with WidgetsBindingObserver { update = true; } - // TODO need input // start on boot depends on ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS and SYSTEM_ALERT_WINDOW var enableStartOnBoot = await gFFI.invokeMethod(AndroidChannel.kGetStartOnBootOpt); @@ -162,8 +161,8 @@ class _SettingsState extends State with WidgetsBindingObserver { } Future checkAndUpdateIgnoreBatteryStatus() async { - final res = - await AndroidPermissionManager.check(kIgnoreBatteryOptimizations); + final res = await AndroidPermissionManager.check( + kRequestIgnoreBatteryOptimizations); if (_ignoreBatteryOpt != res) { _ignoreBatteryOpt = res; return true; @@ -305,8 +304,8 @@ class _SettingsState extends State with WidgetsBindingObserver { ]), onToggle: (v) async { if (v) { - AndroidPermissionManager.startAction( - kActionRequestIgnoreBatteryOptimizations); + await AndroidPermissionManager.request( + kRequestIgnoreBatteryOptimizations); } else { final res = await gFFI.dialogManager .show((setState, close) => CustomAlertDialog( @@ -332,17 +331,34 @@ class _SettingsState extends State with WidgetsBindingObserver { enhancementsTiles.add(SettingsTile.switchTile( initialValue: _enableStartOnBoot, title: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("$translate('Start on Boot') (beta)"), + Text("${translate('Start on Boot')} (beta)"), Text( '* ${translate('Start the screen recording service on boot, which requires special permissions')}', style: Theme.of(context).textTheme.bodySmall), ]), - onToggle: (v) async { - if (v) { - // TODO - } else { - gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, false); + onToggle: (toValue) async { + if (toValue) { + // 1. request kIgnoreBatteryOptimizations + if (!await AndroidPermissionManager.check( + kRequestIgnoreBatteryOptimizations)) { + if (!await AndroidPermissionManager.request( + kRequestIgnoreBatteryOptimizations)) { + return; + } + } + + // 2. request kSystemAlertWindow + if (!await AndroidPermissionManager.check(kSystemAlertWindow)) { + if (!await AndroidPermissionManager.request(kSystemAlertWindow)) { + return; + } + } + + // (Optional) 3. request input permission } + setState(() => _enableStartOnBoot = toValue); + + gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, toValue); })); return SettingsList( @@ -446,7 +462,6 @@ class _SettingsState extends State with WidgetsBindingObserver { } bool canStartOnBoot() { - // TODO need input // start on boot depends on ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS and SYSTEM_ALERT_WINDOW if (_hasIgnoreBattery && !_ignoreBatteryOpt) { return false;