From 4c44d51c70f85aa944f0c808f299e02fe80012a3 Mon Sep 17 00:00:00 2001 From: Tim Froidcoeur Date: Mon, 11 Mar 2024 15:13:33 +0100 Subject: [PATCH 1/4] update android manifest for sdk 31 fix android manifest by adding mandatory attribute android:exported. Signed-off-by: Tim Froidcoeur --- example/android/app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index bba6e7a..a911e08 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -13,6 +13,7 @@ android:icon="@mipmap/ic_launcher"> Date: Mon, 11 Mar 2024 15:14:20 +0100 Subject: [PATCH 2/4] example: fix compilation handle nullable bool defaults Signed-off-by: Tim Froidcoeur --- example/lib/main.dart | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index fde994a..680a924 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -28,7 +28,7 @@ class _MyAppState extends State { child: Text("Is Auto Start Enabled"), onPressed: () async { bool isAutoStartEnabled = - await DisableBatteryOptimization.isAutoStartEnabled; + await DisableBatteryOptimization.isAutoStartEnabled ?? false; print( "Auto start is ${isAutoStartEnabled ? "Enabled" : "Disabled"}"); }), @@ -36,8 +36,7 @@ class _MyAppState extends State { child: Text("Is Battery optimization disabled"), onPressed: () async { bool isBatteryOptimizationDisabled = - await DisableBatteryOptimization - .isBatteryOptimizationDisabled; + await DisableBatteryOptimization.isBatteryOptimizationDisabled ?? false; print( "Battery optimization is ${!isBatteryOptimizationDisabled ? "Enabled" : "Disabled"}"); }), @@ -45,8 +44,7 @@ class _MyAppState extends State { child: Text("Is Manufacturer Battery optimization disabled"), onPressed: () async { bool isManBatteryOptimizationDisabled = - await DisableBatteryOptimization - .isManufacturerBatteryOptimizationDisabled; + await DisableBatteryOptimization.isManufacturerBatteryOptimizationDisabled ?? false; print( "Manufacturer Battery optimization is ${!isManBatteryOptimizationDisabled ? "Enabled" : "Disabled"}"); }), @@ -54,8 +52,7 @@ class _MyAppState extends State { child: Text("Are All Battery optimizations disabled"), onPressed: () async { bool isAllBatteryOptimizationDisabled = - await DisableBatteryOptimization - .isAllBatteryOptimizationDisabled; + await DisableBatteryOptimization.isAllBatteryOptimizationDisabled ?? false; print( "All Battery optimizations are disabled ${isAllBatteryOptimizationDisabled ? "True" : "False"}"); }), From c5b14746cd937fde3c04758369303caeb18a48b0 Mon Sep 17 00:00:00 2001 From: Tim Froidcoeur Date: Tue, 12 Mar 2024 15:13:51 +0100 Subject: [PATCH 3/4] wait for the final dialog to close make the flutter calls await the closing of the system dialog. this requires removing the FLAG_ACTIVITY_NEW_TASK flag from the ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS activity. Signed-off-by: Tim Froidcoeur --- .../DisableBatteryOptimizationPlugin.java | 86 +++++++++++++------ .../utils/BatteryOptimizationUtil.java | 9 +- .../MainActivity.java | 3 +- example/lib/main.dart | 10 ++- 4 files changed, 77 insertions(+), 31 deletions(-) diff --git a/android/src/main/java/in/jvapps/disable_battery_optimization/DisableBatteryOptimizationPlugin.java b/android/src/main/java/in/jvapps/disable_battery_optimization/DisableBatteryOptimizationPlugin.java index 24e5465..3f0a00e 100644 --- a/android/src/main/java/in/jvapps/disable_battery_optimization/DisableBatteryOptimizationPlugin.java +++ b/android/src/main/java/in/jvapps/disable_battery_optimization/DisableBatteryOptimizationPlugin.java @@ -12,6 +12,7 @@ import java.util.List; import in.jvapps.disable_battery_optimization.utils.BatteryOptimizationUtil; +import in.jvapps.disable_battery_optimization.utils.BatteryOptimizationUtil.OnBatteryOptimizationDone; import in.jvapps.disable_battery_optimization.utils.PrefKeys; import in.jvapps.disable_battery_optimization.utils.PrefUtils; import io.flutter.embedding.engine.plugins.FlutterPlugin; @@ -26,10 +27,12 @@ /** * DisableBatteryOptimizationPlugin */ -public class DisableBatteryOptimizationPlugin implements FlutterPlugin, ActivityAware, MethodCallHandler { +public class DisableBatteryOptimizationPlugin implements FlutterPlugin, ActivityAware, MethodCallHandler, + PluginRegistry.ActivityResultListener{ private Context mContext; private Activity mActivity; + private BatteryOptimizationUtil.OnBatteryOptimizationDone onBatteryOptimizationDone; // These are null when not using v2 embedding. private MethodChannel channel; @@ -44,6 +47,21 @@ public class DisableBatteryOptimizationPlugin implements FlutterPlugin, Activity private String manBatteryMessage; + @Override + public boolean onActivityResult(int requestCode, int resultCode, Intent intent){ + Log.e(TAG, "onActivityResult " + requestCode +" " + resultCode); + switch (requestCode) { + case REQUEST_DISABLE_BATTERY_OPTIMIZATIONS: + if (onBatteryOptimizationDone != null) + onBatteryOptimizationDone.onBatteryOptimizationDone(true); + else + Log.e(TAG, "no callback for onActivityResult"); + + onBatteryOptimizationDone = null; + } + return true; + } + // This static function is optional and equivalent to onAttachedToEngine. It supports the old // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting // plugin registration via this function while apps migrate to use the new Android APIs @@ -99,8 +117,9 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { if (arguments != null) { manBatteryTitle = String.valueOf(arguments.get(0)); manBatteryMessage = String.valueOf(arguments.get(1)); - showManBatteryOptimizationDisabler(false); - result.success(true); + showManBatteryOptimizationDisabler(false, (res) -> { + result.success(res); + }); } else { Log.e(TAG, "Unable to request disable manufacturer battery optimization. Arguments are null"); result.success(false); @@ -112,8 +131,9 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { break; case "showDisableBatteryOptimization": try { - showIgnoreBatteryPermissions(); - result.success(true); + showIgnoreBatteryPermissions((res) -> { + result.success(res); + }); } catch (Exception ex) { Log.e(TAG, "Exception in showDisableBatteryOptimization. " + ex.toString()); result.success(false); @@ -127,8 +147,9 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { autoStartMessage = String.valueOf(arguments.get(1)); manBatteryTitle = String.valueOf(arguments.get(2)); manBatteryMessage = String.valueOf(arguments.get(3)); - handleIgnoreAllBatteryPermission(); - result.success(true); + handleIgnoreAllBatteryPermission((res) -> { + result.success(res); + }); } else { Log.e(TAG, "Unable to request disable all optimizations. Arguments are null"); result.success(false); @@ -157,7 +178,6 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { @Override public void onAttachedToEngine(FlutterPluginBinding binding) { - channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME); mContext = binding.getApplicationContext(); } @@ -171,6 +191,7 @@ public void onAttachedToActivity(ActivityPluginBinding binding) { mActivity = binding.getActivity(); mContext = mActivity.getApplicationContext(); channel.setMethodCallHandler(this); + binding.addActivityResultListener(this); } @Override @@ -201,7 +222,7 @@ private void showAutoStartEnabler(@NonNull final BatteryOptimizationUtil.OnBatte ); } - private void showManBatteryOptimizationDisabler(boolean isRequestNativeBatteryOptimizationDisabler) { + private void showManBatteryOptimizationDisabler(boolean isRequestNativeBatteryOptimizationDisabler, final BatteryOptimizationUtil.OnBatteryOptimizationDone doneCallback) { BatteryOptimizationUtil.showBatteryOptimizationDialog( mActivity, KillerManager.Actions.ACTION_POWERSAVING, @@ -210,50 +231,67 @@ private void showManBatteryOptimizationDisabler(boolean isRequestNativeBatteryOp () -> { setManBatteryOptimization(true); if (isRequestNativeBatteryOptimizationDisabler) { - showIgnoreBatteryPermissions(); + showIgnoreBatteryPermissions(doneCallback); + } else { + doneCallback.onBatteryOptimizationDone(true); } }, () -> { if (isRequestNativeBatteryOptimizationDisabler) { - showIgnoreBatteryPermissions(); + showIgnoreBatteryPermissions(doneCallback); + } else { + doneCallback.onBatteryOptimizationDone(true); } } ); } - private void showIgnoreBatteryPermissions() { + private void showIgnoreBatteryPermissions(@NonNull final BatteryOptimizationUtil.OnBatteryOptimizationDone doneCallback) { + boolean result = false; if (!BatteryOptimizationUtil.isIgnoringBatteryOptimizations(mContext)) { - final Intent ignoreBatteryOptimizationsIntent = BatteryOptimizationUtil.getIgnoreBatteryOptimizationsIntent(mContext); - if (ignoreBatteryOptimizationsIntent != null) { - mContext.startActivity(ignoreBatteryOptimizationsIntent); + final Intent ignoreBatteryOptimizationsIntent = BatteryOptimizationUtil + .getIgnoreBatteryOptimizationsIntent(mContext); + if (ignoreBatteryOptimizationsIntent != null && onBatteryOptimizationDone == null) { + onBatteryOptimizationDone = doneCallback; + mActivity.startActivityForResult(ignoreBatteryOptimizationsIntent, REQUEST_DISABLE_BATTERY_OPTIMIZATIONS); + return; + } else if ( onBatteryOptimizationDone != null ){ + Log.w(TAG, "Can't ignore the battery optimization as another battery system dialog is already running"); + result = false; } else { - Log.i(TAG, "Can't ignore the battery optimization as the intent is null"); + Log.w(TAG, "Can't ignore the battery optimization as the intent is null"); + result = false; } } else { Log.i(TAG, "Battery optimization is already disabled"); + result = true; } + + if (doneCallback != null) + doneCallback.onBatteryOptimizationDone(result); } - private void handleIgnoreAllBatteryPermission() { + private void handleIgnoreAllBatteryPermission(@NonNull final BatteryOptimizationUtil.OnBatteryOptimizationDone doneCallback) { boolean isManBatteryOptimizationDisabled = getManBatteryOptimization(); if (!getManAutoStart()) { showAutoStartEnabler(() -> { setManAutoStart(true); if (!isManBatteryOptimizationDisabled) - showManBatteryOptimizationDisabler(true); + showManBatteryOptimizationDisabler(true, doneCallback); else - showIgnoreBatteryPermissions(); + showIgnoreBatteryPermissions(doneCallback); }, () -> { if (!isManBatteryOptimizationDisabled) - showManBatteryOptimizationDisabler(true); - else - showIgnoreBatteryPermissions(); + showManBatteryOptimizationDisabler(true, doneCallback); + else { + showIgnoreBatteryPermissions(doneCallback); + } }); } else { if (!isManBatteryOptimizationDisabled) - showManBatteryOptimizationDisabler(true); + showManBatteryOptimizationDisabler(true, doneCallback); else - showIgnoreBatteryPermissions(); + showIgnoreBatteryPermissions(doneCallback); } } diff --git a/android/src/main/java/in/jvapps/disable_battery_optimization/utils/BatteryOptimizationUtil.java b/android/src/main/java/in/jvapps/disable_battery_optimization/utils/BatteryOptimizationUtil.java index 7884408..191b41b 100644 --- a/android/src/main/java/in/jvapps/disable_battery_optimization/utils/BatteryOptimizationUtil.java +++ b/android/src/main/java/in/jvapps/disable_battery_optimization/utils/BatteryOptimizationUtil.java @@ -43,7 +43,8 @@ public static Intent getIgnoreBatteryOptimizationsIntent(Context context) { String sb = "package:" + context.getApplicationContext().getPackageName(); @SuppressLint("BatteryLife") Intent intent = new Intent(ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Uri.parse(sb)); - intent.setFlags(FLAG_ACTIVITY_NEW_TASK); + // NOT using FLAG_ACTIVITY_NEW_TASK to allow having an activity result callback at the end of the activity + // according to Gemini, this should be ok security wise for this particular intent return intent.resolveActivity(context.getPackageManager()) == null ? getAppSettingsIntent(context) : intent; } @@ -91,4 +92,8 @@ public interface OnBatteryOptimizationCanceled { void onBatteryOptimizationCanceled(); } -} \ No newline at end of file + public interface OnBatteryOptimizationDone { + void onBatteryOptimizationDone(boolean result); + } + +} diff --git a/example/android/app/src/main/java/in/jvapps/disable_battery_optimization_example/MainActivity.java b/example/android/app/src/main/java/in/jvapps/disable_battery_optimization_example/MainActivity.java index bb50ccf..0d2edb4 100644 --- a/example/android/app/src/main/java/in/jvapps/disable_battery_optimization_example/MainActivity.java +++ b/example/android/app/src/main/java/in/jvapps/disable_battery_optimization_example/MainActivity.java @@ -3,4 +3,5 @@ import io.flutter.embedding.android.FlutterActivity; -public class MainActivity extends FlutterActivity {} +public class MainActivity extends FlutterActivity { +} diff --git a/example/lib/main.dart b/example/lib/main.dart index 680a924..cae1c39 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -65,22 +65,24 @@ class _MyAppState extends State { }), MaterialButton( child: Text("Disable Battery Optimizations"), - onPressed: () { + onPressed: () async { DisableBatteryOptimization .showDisableBatteryOptimizationSettings(); + await DisableBatteryOptimization.showDisableBatteryOptimizationSettings(); }), MaterialButton( child: Text("Disable Manufacturer Battery Optimizations"), - onPressed: () { - DisableBatteryOptimization + onPressed: () async { + await DisableBatteryOptimization .showDisableManufacturerBatteryOptimizationSettings( "Your device has additional battery optimization", "Follow the steps and disable the optimizations to allow smooth functioning of this app"); }), MaterialButton( child: Text("Disable all Optimizations"), - onPressed: () { + onPressed: () async { DisableBatteryOptimization.showDisableAllOptimizationsSettings( + await DisableBatteryOptimization.showDisableAllOptimizationsSettings( "Enable Auto Start", "Follow the steps and enable the auto start of this app", "Your device has additional battery optimization", From 4afd5a49ccf75a9952244bc5e3ab83dcd5821b97 Mon Sep 17 00:00:00 2001 From: Tim Froidcoeur Date: Wed, 13 Mar 2024 10:31:31 +0100 Subject: [PATCH 4/4] make the dialogs non-cancellable to make sure the done callback is always called, make the dialog non-cancellable. There seem not to be (obvious) cancel/dismiss callbacks in java on the com.afollestad.materialdialogs.MaterialDialog class, so easiest is to just disable the possibility to cancel/dismiss the dialog by tapping outside of it. Signed-off-by: Tim Froidcoeur --- .../ui/DialogKillerManagerBuilder.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/in/jvapps/disable_battery_optimization/ui/DialogKillerManagerBuilder.java b/android/src/main/java/in/jvapps/disable_battery_optimization/ui/DialogKillerManagerBuilder.java index 4d84ca7..fb7f9db 100644 --- a/android/src/main/java/in/jvapps/disable_battery_optimization/ui/DialogKillerManagerBuilder.java +++ b/android/src/main/java/in/jvapps/disable_battery_optimization/ui/DialogKillerManagerBuilder.java @@ -141,6 +141,8 @@ public void show() { negativeBtnStr = mContext.getText(android.R.string.cancel).toString(); } + builder.cancelable(false); + builder.positiveText(positiveBtnStr) .customView(R.layout.md_dialog_custom_view, false) .onPositive(new MaterialDialog.SingleButtonCallback() { @@ -242,4 +244,4 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { helpImageView.setVisibility(View.GONE); } } -} \ No newline at end of file +}