Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fc86da0f authored by YK Hung's avatar YK Hung Committed by Android (Google) Code Review
Browse files

Merge "Use BatteryOptimizeUtils to add packageName into...

Merge "Use BatteryOptimizeUtils to add packageName into PowerSaveWhitelistUserApps allowlist, which will force set app into Unrestricted Mode" into main
parents 8384d162 c03e7ee9
Loading
Loading
Loading
Loading
+7 −2
Original line number Diff line number Diff line
@@ -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)
+29 −16
Original line number Diff line number Diff line
@@ -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;
+224 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}