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

Commit f2321ad5 authored by Wesley.CW Wang's avatar Wesley.CW Wang
Browse files

Add utils class for app usage operation

 - An util class to control usage state

Bug: 178197718
Test: make SettingsRoboTests
Change-Id: I9c1d37610a2e52d484228ef8e5c7af49e12f82aa
parent 1073196a
Loading
Loading
Loading
Loading
+121 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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 android.app.AppOpsManager;
import android.content.Context;
import android.util.Log;

import androidx.annotation.VisibleForTesting;

import com.android.settingslib.fuelgauge.PowerAllowlistBackend;

/** A utility class for application usage operation. */
public class BatteryOptimizeUtils {
    private static final String TAG = "BatteryOptimizeUtils";

    @VisibleForTesting AppOpsManager mAppOpsManager;
    @VisibleForTesting BatteryUtils mBatteryUtils;
    @VisibleForTesting PowerAllowlistBackend mPowerAllowListBackend;
    private final String mPackageName;
    private final int mUid;

    private int mMode;
    private boolean mAllowListed;

    /**
     * Usage type of application.
     */
    public enum AppUsageState {
        UNKNOWN,
        RESTRICTED,
        UNRESTRICTED,
        OPTIMIZED,
    }

    public BatteryOptimizeUtils(Context context, int uid, String packageName) {
        mUid = uid;
        mPackageName = packageName;
        mAppOpsManager = context.getSystemService(AppOpsManager.class);
        mBatteryUtils = BatteryUtils.getInstance(context);
        mPowerAllowListBackend = PowerAllowlistBackend.getInstance(context);
        mMode = mAppOpsManager
                .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mPackageName);
        mAllowListed = mPowerAllowListBackend.isAllowlisted(mPackageName);
    }

    public AppUsageState getAppUsageState() {
        refreshState();
        if (!mAllowListed && mMode == AppOpsManager.MODE_IGNORED) {
            return AppUsageState.RESTRICTED;
        } else if (mAllowListed && mMode == AppOpsManager.MODE_ALLOWED) {
            return AppUsageState.UNRESTRICTED;
        } else if (!mAllowListed && mMode == AppOpsManager.MODE_ALLOWED) {
            return AppUsageState.OPTIMIZED;
        } else {
            Log.d(TAG, "get unknown app usage state.");
            return AppUsageState.UNKNOWN;
        }
    }

    public void setAppUsageState(AppUsageState state) {
        switch (state) {
            case RESTRICTED:
                mBatteryUtils.setForceAppStandby(mUid, mPackageName, AppOpsManager.MODE_IGNORED);
                mPowerAllowListBackend.removeApp(mPackageName);
                break;
            case UNRESTRICTED:
                mBatteryUtils.setForceAppStandby(mUid, mPackageName, AppOpsManager.MODE_ALLOWED);
                mPowerAllowListBackend.addApp(mPackageName);
                break;
            case OPTIMIZED:
                mBatteryUtils.setForceAppStandby(mUid, mPackageName, AppOpsManager.MODE_ALLOWED);
                mPowerAllowListBackend.removeApp(mPackageName);
                break;
            default:
                Log.d(TAG, "set unknown app usage state.");
        }
    }

    /**
     * Return {@code true} if package name is valid (can get an uid).
     */
    public boolean isValidPackageName() {
        return mBatteryUtils.getPackageUid(mPackageName) != BatteryUtils.UID_NULL;
    }

    /**
     * Return {@code true} if this package is system or default active app.
     */
    public boolean isSystemOrDefaultApp() {
        mPowerAllowListBackend.refreshList();

        return mPowerAllowListBackend.isSysAllowlisted(mPackageName)
                || mPowerAllowListBackend.isDefaultActiveApp(mPackageName);
    }

    private void refreshState() {
        mPowerAllowListBackend.refreshList();
        mAllowListed = mPowerAllowListBackend.isAllowlisted(mPackageName);
        mMode = mAppOpsManager
                .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mPackageName);
        Log.d(TAG, String.format("refresh %s state, allowlisted = %s, mode = %d",
                mPackageName,
                mAllowListed,
                mMode));
    }
}
+146 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.AppUsageState.OPTIMIZED;
import static com.android.settings.fuelgauge.BatteryOptimizeUtils.AppUsageState.RESTRICTED;
import static com.android.settings.fuelgauge.BatteryOptimizeUtils.AppUsageState.UNRESTRICTED;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.AppOpsManager;
import android.content.Context;

import com.android.settingslib.fuelgauge.PowerAllowlistBackend;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;

@RunWith(RobolectricTestRunner.class)
public class BatteryOptimizeUtilsTest {

    private static final int UID = 12345;
    private static final String PACKAGE_NAME = "com.android.app";

    @Mock BatteryUtils mockBatteryUtils;
    @Mock AppOpsManager mockAppOpsManager;
    @Mock PowerAllowlistBackend mockBackend;

    private Context mContext;
    private BatteryOptimizeUtils mBatteryOptimizeUtils;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mContext = spy(RuntimeEnvironment.application);
        mBatteryOptimizeUtils = spy(new BatteryOptimizeUtils(mContext, UID, PACKAGE_NAME));
        mBatteryOptimizeUtils.mAppOpsManager = mockAppOpsManager;
        mBatteryOptimizeUtils.mBatteryUtils = mockBatteryUtils;
        mBatteryOptimizeUtils.mPowerAllowListBackend = mockBackend;
    }

    @Test
    public void testGetAppUsageState_returnRestricted() {
        when(mockBackend.isAllowlisted(anyString())).thenReturn(false);
        when(mockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString()))
                .thenReturn(AppOpsManager.MODE_IGNORED);

        assertThat(mBatteryOptimizeUtils.getAppUsageState()).isEqualTo(RESTRICTED);
    }

    @Test
    public void testGetAppUsageState_returnUnrestricted() {
        when(mockBackend.isAllowlisted(anyString())).thenReturn(true);
        when(mockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString()))
                .thenReturn(AppOpsManager.MODE_ALLOWED);

        assertThat(mBatteryOptimizeUtils.getAppUsageState()).isEqualTo(UNRESTRICTED);
    }

    @Test
    public void testGetAppUsageState_returnOptimized() {
        when(mockBackend.isAllowlisted(anyString())).thenReturn(false);
        when(mockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), anyString()))
                .thenReturn(AppOpsManager.MODE_ALLOWED);

        assertThat(mBatteryOptimizeUtils.getAppUsageState()).isEqualTo(OPTIMIZED);
    }

    @Test
    public void testIsSystemOrDefaultApp_isSystemOrDefaultApp_returnTrue() {
        when(mockBackend.isAllowlisted(anyString())).thenReturn(true);
        when(mockBackend.isDefaultActiveApp(anyString())).thenReturn(true);

        assertThat(mBatteryOptimizeUtils.isSystemOrDefaultApp()).isTrue();
    }

    @Test
    public void testIsSystemOrDefaultApp_notSystemOrDefaultApp_returnFalse() {
        assertThat(mBatteryOptimizeUtils.isSystemOrDefaultApp()).isFalse();
    }

    @Test
    public void testIsValidPackageName_InvalidPackageName_returnFalse() {
        final BatteryOptimizeUtils testBatteryOptimizeUtils =
                new BatteryOptimizeUtils(mContext, UID, null);

        assertThat(testBatteryOptimizeUtils.isValidPackageName()).isFalse();
    }

    @Test
    public void testIsValidPackageName_validPackageName_returnTrue() {
        assertThat(mBatteryOptimizeUtils.isValidPackageName()).isTrue();
    }

    @Test
    public void testSetAppUsageState_Restricted_verifyAction() {
        mBatteryOptimizeUtils.setAppUsageState(RESTRICTED);

        verify(mockBatteryUtils).setForceAppStandby(UID,
                PACKAGE_NAME, AppOpsManager.MODE_IGNORED);
        verify(mockBackend).removeApp(PACKAGE_NAME);
    }

    @Test
    public void  testSetAppUsageState_Unrestricted_verifyAction() {
        mBatteryOptimizeUtils.setAppUsageState(UNRESTRICTED);

        verify(mockBatteryUtils).setForceAppStandby(UID,
                PACKAGE_NAME, AppOpsManager.MODE_ALLOWED);
        verify(mockBackend).addApp(PACKAGE_NAME);
    }

    @Test
    public void  testSetAppUsageState_Optimized_verifyAction() {
        mBatteryOptimizeUtils.setAppUsageState(OPTIMIZED);

        verify(mockBatteryUtils).setForceAppStandby(UID,
                PACKAGE_NAME, AppOpsManager.MODE_ALLOWED);
        verify(mockBackend).removeApp(PACKAGE_NAME);
    }
}