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
This commit is contained in:
mxyyiyi
2025-02-05 17:52:19 +08:00
parent 4dfe488d8b
commit c03e7ee9d6
3 changed files with 260 additions and 18 deletions

View File

@@ -152,8 +152,8 @@ public class BatteryOptimizeUtils {
} }
/** Sets the {@link OptimizationMode} for associated app. */ /** Sets the {@link OptimizationMode} for associated app. */
public void setAppUsageState(@OptimizationMode int mode, Action action) { public void setAppUsageState(@OptimizationMode int mode, Action action, boolean forceMode) {
if (getAppOptimizationMode() == mode) { if (!forceMode && getAppOptimizationMode() == mode) {
Log.w(TAG, "set the same optimization mode for: " + mPackageName); Log.w(TAG, "set the same optimization mode for: " + mPackageName);
return; return;
} }
@@ -161,6 +161,11 @@ public class BatteryOptimizeUtils {
mContext, mode, mUid, mPackageName, mBatteryUtils, mPowerAllowListBackend, action); 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. */ /** Return {@code true} if it is disabled for default optimized mode only. */
public boolean isDisabledForOptimizeModeOnly() { public boolean isDisabledForOptimizeModeOnly() {
return getForceBatteryOptimizeModeList(mContext).contains(mPackageName) return getForceBatteryOptimizeModeList(mContext).contains(mPackageName)

View File

@@ -16,6 +16,8 @@
package com.android.settings.fuelgauge; package com.android.settings.fuelgauge;
import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_UNRESTRICTED;
import android.Manifest; import android.Manifest;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
@@ -24,20 +26,25 @@ import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.PowerWhitelistManager; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.internal.app.AlertActivity; import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController; import com.android.internal.app.AlertController;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
public class RequestIgnoreBatteryOptimizations extends AlertActivity public class RequestIgnoreBatteryOptimizations extends AlertActivity
implements DialogInterface.OnClickListener { implements DialogInterface.OnClickListener {
private static final String TAG = "RequestIgnoreBatteryOptimizations"; private static final String TAG = "RequestIgnoreBatteryOptimizations";
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
private PowerWhitelistManager mPowerWhitelistManager; @VisibleForTesting
private String mPackageName; static BatteryOptimizeUtils sTestBatteryOptimizeUtils = null;
private ApplicationInfo mApplicationInfo;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@@ -47,8 +54,6 @@ public class RequestIgnoreBatteryOptimizations extends AlertActivity
android.view.WindowManager.LayoutParams android.view.WindowManager.LayoutParams
.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
mPowerWhitelistManager = getSystemService(PowerWhitelistManager.class);
Uri data = getIntent().getData(); Uri data = getIntent().getData();
if (data == null) { if (data == null) {
debugLog( debugLog(
@@ -56,17 +61,18 @@ public class RequestIgnoreBatteryOptimizations extends AlertActivity
finish(); finish();
return; return;
} }
mPackageName = data.getSchemeSpecificPart(); final String packageName = data.getSchemeSpecificPart();
if (mPackageName == null) { if (TextUtils.isEmpty(packageName)) {
debugLog( debugLog(
"No data supplied for IGNORE_BATTERY_OPTIMIZATION_SETTINGS in: " + getIntent()); "No data supplied for IGNORE_BATTERY_OPTIMIZATION_SETTINGS in: " + getIntent());
finish(); finish();
return; return;
} }
// Package in Unrestricted mode already ignoring the battery optimizations.
PowerManager power = getSystemService(PowerManager.class); PowerManager power = getSystemService(PowerManager.class);
if (power.isIgnoringBatteryOptimizations(mPackageName)) { if (power.isIgnoringBatteryOptimizations(packageName)) {
debugLog("Not should prompt, already ignoring optimizations: " + mPackageName); debugLog("Not should prompt, already ignoring optimizations: " + packageName);
finish(); finish();
return; return;
} }
@@ -74,29 +80,28 @@ public class RequestIgnoreBatteryOptimizations extends AlertActivity
if (getPackageManager() if (getPackageManager()
.checkPermission( .checkPermission(
Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
mPackageName) packageName)
!= PackageManager.PERMISSION_GRANTED) { != PackageManager.PERMISSION_GRANTED) {
debugLog( debugLog(
"Requested package " "Requested package "
+ mPackageName + packageName
+ " does not hold permission " + " does not hold permission "
+ Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
finish(); finish();
return; return;
} }
ApplicationInfo ai;
try { try {
ai = getPackageManager().getApplicationInfo(mPackageName, 0); mApplicationInfo = getPackageManager().getApplicationInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) { } catch (PackageManager.NameNotFoundException e) {
debugLog("Requested package doesn't exist: " + mPackageName); debugLog("Requested package doesn't exist: " + packageName);
finish(); finish();
return; return;
} }
final AlertController.AlertParams p = mAlertParams; final AlertController.AlertParams p = mAlertParams;
final CharSequence appLabel = final CharSequence appLabel =
ai.loadSafeLabel( mApplicationInfo.loadSafeLabel(
getPackageManager(), getPackageManager(),
PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
PackageItemInfo.SAFE_LABEL_FLAG_TRIM PackageItemInfo.SAFE_LABEL_FLAG_TRIM
@@ -114,7 +119,15 @@ public class RequestIgnoreBatteryOptimizations extends AlertActivity
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
switch (which) { switch (which) {
case BUTTON_POSITIVE: 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; break;
case BUTTON_NEGATIVE: case BUTTON_NEGATIVE:
break; break;

View File

@@ -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;
}
}