From c03e7ee9d6e66323408e522e8e2fead1a70d867d Mon Sep 17 00:00:00 2001 From: mxyyiyi Date: Wed, 5 Feb 2025 17:52:19 +0800 Subject: [PATCH] Use BatteryOptimizeUtils to add packageName into PowerSaveWhitelistUserApps allowlist, which will force set app into Unrestricted Mode Fix: 372831500 Fix: 393033745 Test: test manually by app "Baidu Cloud"> auto-backup settings > add into battery allowlist. Test: atest RequestIgnoreBatteryOptimizationsTest Flag: EXEMPT for simple fix Change-Id: Ia0b232389b1c11b48724af750721e6af4313deaf --- .../fuelgauge/BatteryOptimizeUtils.java | 9 +- .../RequestIgnoreBatteryOptimizations.java | 45 ++-- ...RequestIgnoreBatteryOptimizationsTest.java | 224 ++++++++++++++++++ 3 files changed, 260 insertions(+), 18 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizationsTest.java diff --git a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java index 6c95823853f..8bb526f87b8 100644 --- a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java @@ -152,8 +152,8 @@ public class BatteryOptimizeUtils { } /** Sets the {@link OptimizationMode} for associated app. */ - public void setAppUsageState(@OptimizationMode int mode, Action action) { - if (getAppOptimizationMode() == mode) { + public void setAppUsageState(@OptimizationMode int mode, Action action, boolean forceMode) { + if (!forceMode && getAppOptimizationMode() == mode) { Log.w(TAG, "set the same optimization mode for: " + mPackageName); return; } @@ -161,6 +161,11 @@ public class BatteryOptimizeUtils { mContext, mode, mUid, mPackageName, mBatteryUtils, mPowerAllowListBackend, action); } + /** Sets the {@link OptimizationMode} for associated app. */ + public void setAppUsageState(@OptimizationMode int mode, Action action) { + setAppUsageState(mode, action, /* forceMode= */ false); + } + /** Return {@code true} if it is disabled for default optimized mode only. */ public boolean isDisabledForOptimizeModeOnly() { return getForceBatteryOptimizeModeList(mContext).contains(mPackageName) diff --git a/src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizations.java b/src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizations.java index d948cc0f13a..6cb6442628b 100644 --- a/src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizations.java +++ b/src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizations.java @@ -16,6 +16,8 @@ package com.android.settings.fuelgauge; +import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_UNRESTRICTED; + import android.Manifest; import android.content.DialogInterface; import android.content.pm.ApplicationInfo; @@ -24,20 +26,25 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.PowerManager; -import android.os.PowerWhitelistManager; +import android.text.TextUtils; import android.util.Log; +import androidx.annotation.VisibleForTesting; + import com.android.internal.app.AlertActivity; import com.android.internal.app.AlertController; import com.android.settings.R; +import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action; public class RequestIgnoreBatteryOptimizations extends AlertActivity implements DialogInterface.OnClickListener { private static final String TAG = "RequestIgnoreBatteryOptimizations"; private static final boolean DEBUG = false; - private PowerWhitelistManager mPowerWhitelistManager; - private String mPackageName; + @VisibleForTesting + static BatteryOptimizeUtils sTestBatteryOptimizeUtils = null; + + private ApplicationInfo mApplicationInfo; @Override public void onCreate(Bundle savedInstanceState) { @@ -47,8 +54,6 @@ public class RequestIgnoreBatteryOptimizations extends AlertActivity android.view.WindowManager.LayoutParams .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); - mPowerWhitelistManager = getSystemService(PowerWhitelistManager.class); - Uri data = getIntent().getData(); if (data == null) { debugLog( @@ -56,17 +61,18 @@ public class RequestIgnoreBatteryOptimizations extends AlertActivity finish(); return; } - mPackageName = data.getSchemeSpecificPart(); - if (mPackageName == null) { + final String packageName = data.getSchemeSpecificPart(); + if (TextUtils.isEmpty(packageName)) { debugLog( "No data supplied for IGNORE_BATTERY_OPTIMIZATION_SETTINGS in: " + getIntent()); finish(); return; } + // Package in Unrestricted mode already ignoring the battery optimizations. PowerManager power = getSystemService(PowerManager.class); - if (power.isIgnoringBatteryOptimizations(mPackageName)) { - debugLog("Not should prompt, already ignoring optimizations: " + mPackageName); + if (power.isIgnoringBatteryOptimizations(packageName)) { + debugLog("Not should prompt, already ignoring optimizations: " + packageName); finish(); return; } @@ -74,29 +80,28 @@ public class RequestIgnoreBatteryOptimizations extends AlertActivity if (getPackageManager() .checkPermission( Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, - mPackageName) + packageName) != PackageManager.PERMISSION_GRANTED) { debugLog( "Requested package " - + mPackageName + + packageName + " does not hold permission " + Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); finish(); return; } - ApplicationInfo ai; try { - ai = getPackageManager().getApplicationInfo(mPackageName, 0); + mApplicationInfo = getPackageManager().getApplicationInfo(packageName, 0); } catch (PackageManager.NameNotFoundException e) { - debugLog("Requested package doesn't exist: " + mPackageName); + debugLog("Requested package doesn't exist: " + packageName); finish(); return; } final AlertController.AlertParams p = mAlertParams; final CharSequence appLabel = - ai.loadSafeLabel( + mApplicationInfo.loadSafeLabel( getPackageManager(), PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, PackageItemInfo.SAFE_LABEL_FLAG_TRIM @@ -114,7 +119,15 @@ public class RequestIgnoreBatteryOptimizations extends AlertActivity public void onClick(DialogInterface dialog, int which) { switch (which) { case BUTTON_POSITIVE: - mPowerWhitelistManager.addToWhitelist(mPackageName); + BatteryOptimizeUtils batteryOptimizeUtils = + sTestBatteryOptimizeUtils != null + ? sTestBatteryOptimizeUtils + : new BatteryOptimizeUtils( + getApplicationContext(), + mApplicationInfo.uid, + mApplicationInfo.packageName); + batteryOptimizeUtils.setAppUsageState( + MODE_UNRESTRICTED, Action.APPLY, /* forceMode= */ true); break; case BUTTON_NEGATIVE: break; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizationsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizationsTest.java new file mode 100644 index 00000000000..b7ffa38285b --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizationsTest.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.fuelgauge; + +import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_RESTRICTED; +import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_UNRESTRICTED; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import android.Manifest; +import android.app.AppOpsManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.PowerManager; + +import com.android.settings.testutils.shadow.ShadowUtils; +import com.android.settingslib.fuelgauge.PowerAllowlistBackend; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowUtils.class}) +public class RequestIgnoreBatteryOptimizationsTest { + private static final int UID = 12345; + private static final String PACKAGE_NAME = "com.android.app"; + private static final String UNKNOWN_PACKAGE_NAME = "com.android.unknown"; + private static final String PACKAGE_LABEL = "app"; + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + private Context mContext; + private RequestIgnoreBatteryOptimizations mActivity; + private BatteryOptimizeUtils mBatteryOptimizeUtils; + private PowerAllowlistBackend mPowerAllowlistBackend; + + @Mock private PowerManager mMockPowerManager; + @Mock private PackageManager mMockPackageManager; + @Mock private ApplicationInfo mMockApplicationInfo; + @Mock private BatteryUtils mMockBatteryUtils; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + mActivity = spy(Robolectric.setupActivity(RequestIgnoreBatteryOptimizations.class)); + mBatteryOptimizeUtils = spy(new BatteryOptimizeUtils(mContext, UID, PACKAGE_NAME)); + mPowerAllowlistBackend = spy(PowerAllowlistBackend.getInstance(mContext)); + mBatteryOptimizeUtils.mPowerAllowListBackend = mPowerAllowlistBackend; + mBatteryOptimizeUtils.mBatteryUtils = mMockBatteryUtils; + RequestIgnoreBatteryOptimizations.sTestBatteryOptimizeUtils = mBatteryOptimizeUtils; + + when(mActivity.getApplicationContext()).thenReturn(mContext); + doReturn(mMockPowerManager).when(mActivity).getSystemService(PowerManager.class); + doReturn(mMockPackageManager).when(mActivity).getPackageManager(); + doReturn(mMockApplicationInfo) + .when(mMockPackageManager) + .getApplicationInfo(PACKAGE_NAME, 0); + doThrow(new PackageManager.NameNotFoundException("")) + .when(mMockPackageManager) + .getApplicationInfo(UNKNOWN_PACKAGE_NAME, 0); + doReturn(PACKAGE_LABEL) + .when(mMockApplicationInfo) + .loadSafeLabel( + mMockPackageManager, + PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, + PackageItemInfo.SAFE_LABEL_FLAG_TRIM + | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE); + + doReturn(PackageManager.PERMISSION_GRANTED) + .when(mMockPackageManager) + .checkPermission( + eq(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS), anyString()); + } + + @After + public void tearDown() { + ShadowUtils.reset(); + PowerAllowlistBackend.resetInstance(); + } + + @Test + public void onCreate_withIntent_shouldNotFinish() { + mActivity.setIntent(createIntent(PACKAGE_NAME)); + + mActivity.onCreate(new Bundle()); + + verify(mActivity, never()).finish(); + } + + @Test + public void onCreate_withNoDataIntent_shouldFinish() { + mActivity.setIntent(new Intent()); + + mActivity.onCreate(new Bundle()); + + verify(mActivity).finish(); + } + + @Test + public void onCreate_withEmptyPackageName_shouldFinish() { + mActivity.setIntent(createIntent("")); + + mActivity.onCreate(new Bundle()); + + verify(mActivity).finish(); + } + + @Test + public void onCreate_withPkgAlreadyIgnoreOptimization_shouldFinish() { + mActivity.setIntent(createIntent(PACKAGE_NAME)); + doReturn(true).when(mMockPowerManager).isIgnoringBatteryOptimizations(PACKAGE_NAME); + + mActivity.onCreate(new Bundle()); + + verify(mActivity).finish(); + } + + @Test + public void onCreate_withPkgWithoutPermission_shouldFinish() { + mActivity.setIntent(createIntent(PACKAGE_NAME)); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mMockPackageManager) + .checkPermission( + Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, PACKAGE_NAME); + + mActivity.onCreate(new Bundle()); + + verify(mActivity).finish(); + } + + @Test + public void onCreate_withPkgNameNotFound_shouldFinish() { + mActivity.setIntent(createIntent(UNKNOWN_PACKAGE_NAME)); + + mActivity.onCreate(new Bundle()); + + verify(mActivity).finish(); + } + + @Test + public void onClick_clickNegativeButton_doNothing() { + mActivity.onClick(null, DialogInterface.BUTTON_NEGATIVE); + + verifyNoInteractions(mBatteryOptimizeUtils); + } + + @Test + public void onClick_clickPositiveButtonWithUnrestrictedMode_addAllowlist() { + when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(MODE_UNRESTRICTED); + + mActivity.onClick(null, DialogInterface.BUTTON_POSITIVE); + + verify(mBatteryOptimizeUtils) + .setAppUsageState( + MODE_UNRESTRICTED, + BatteryOptimizeHistoricalLogEntry.Action.APPLY, + /* forceMode= */ true); + verify(mPowerAllowlistBackend).addApp(PACKAGE_NAME, UID); + verify(mMockBatteryUtils).setForceAppStandby(UID, PACKAGE_NAME, AppOpsManager.MODE_ALLOWED); + } + + @Test + public void onClick_clickPositiveButtonWithRestrictedMode_addAllowlistAndSetStandby() { + when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(MODE_RESTRICTED); + doNothing().when(mMockBatteryUtils).setForceAppStandby(anyInt(), anyString(), anyInt()); + + mActivity.onClick(null, DialogInterface.BUTTON_POSITIVE); + + verify(mBatteryOptimizeUtils) + .setAppUsageState( + MODE_UNRESTRICTED, + BatteryOptimizeHistoricalLogEntry.Action.APPLY, + /* forceMode= */ true); + verify(mPowerAllowlistBackend).addApp(PACKAGE_NAME, UID); + verify(mMockBatteryUtils).setForceAppStandby(UID, PACKAGE_NAME, AppOpsManager.MODE_ALLOWED); + } + + private Intent createIntent(String packageName) { + final Intent intent = new Intent(); + intent.setData(new Uri.Builder().scheme("package").opaquePart(packageName).build()); + return intent; + } +}