diff --git a/res/xml/power_usage_detail.xml b/res/xml/power_usage_detail.xml index 95c1038c8f1..0258f8de648 100644 --- a/res/xml/power_usage_detail.xml +++ b/res/xml/power_usage_detail.xml @@ -30,6 +30,30 @@ android:key="action_buttons" android:order="-9999"/> + + + + + + + + + + @@ -63,4 +87,11 @@ + + \ No newline at end of file diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java index f4bdfb3f899..f28ae00860e 100644 --- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.UserHandle; +import android.text.Html; import android.text.TextUtils; import android.util.Log; import android.view.View; @@ -48,6 +49,7 @@ import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.utils.StringUtil; import com.android.settingslib.widget.LayoutPreference; +import com.android.settingslib.widget.RadioButtonPreference; import java.util.ArrayList; import java.util.List; @@ -60,7 +62,7 @@ import java.util.List; */ public class AdvancedPowerUsageDetail extends DashboardFragment implements ButtonActionDialogFragment.AppButtonsDialogListener, - BatteryTipPreferenceController.BatteryTipListener { + BatteryTipPreferenceController.BatteryTipListener, RadioButtonPreference.OnClickListener { public static final String TAG = "AdvancedPowerDetail"; public static final String EXTRA_UID = "extra_uid"; @@ -75,6 +77,10 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements private static final String KEY_PREF_FOREGROUND = "app_usage_foreground"; private static final String KEY_PREF_BACKGROUND = "app_usage_background"; private static final String KEY_PREF_HEADER = "header_view"; + private static final String KEY_PREF_UNRESTRICTED = "unrestricted_pref"; + private static final String KEY_PREF_OPTIMIZED = "optimized_pref"; + private static final String KEY_PREF_RESTRICTED = "restricted_pref"; + private static final String KEY_FOOTER_PREFERENCE = "app_usage_footer_preference"; private static final int REQUEST_UNINSTALL = 0; private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1; @@ -87,13 +93,26 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements ApplicationsState.AppEntry mAppEntry; @VisibleForTesting BatteryUtils mBatteryUtils; + @VisibleForTesting + BatteryOptimizeUtils mBatteryOptimizeUtils; @VisibleForTesting Preference mForegroundPreference; @VisibleForTesting Preference mBackgroundPreference; + @VisibleForTesting + Preference mFooterPreference; + @VisibleForTesting + RadioButtonPreference mRestrictedPreference; + @VisibleForTesting + RadioButtonPreference mOptimizePreference; + @VisibleForTesting + RadioButtonPreference mUnrestrictedPreference; private AppButtonsPreferenceController mAppButtonsPreferenceController; private BackgroundActivityPreferenceController mBackgroundActivityPreferenceController; + private UnrestrictedPreferenceController mUnrestrictedPreferenceController; + private OptimizedPreferenceController mOptimizedPreferenceController; + private RestrictedPreferenceController mRestrictedPreferenceController; private String mPackageName; @@ -174,8 +193,19 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements mPackageName = getArguments().getString(EXTRA_PACKAGE_NAME); mForegroundPreference = findPreference(KEY_PREF_FOREGROUND); mBackgroundPreference = findPreference(KEY_PREF_BACKGROUND); + mFooterPreference = findPreference(KEY_FOOTER_PREFERENCE); mHeaderPreference = (LayoutPreference) findPreference(KEY_PREF_HEADER); + mUnrestrictedPreference = findPreference(KEY_PREF_UNRESTRICTED); + mOptimizePreference = findPreference(KEY_PREF_OPTIMIZED); + mRestrictedPreference = findPreference(KEY_PREF_RESTRICTED); + mUnrestrictedPreference.setOnClickListener(this); + mOptimizePreference.setOnClickListener(this); + mRestrictedPreference.setOnClickListener(this); + + mBatteryOptimizeUtils = new BatteryOptimizeUtils( + getContext(), getArguments().getInt(EXTRA_UID), mPackageName); + if (mPackageName != null) { mAppEntry = mState.getEntry(mPackageName, UserHandle.myUserId()); } @@ -241,6 +271,26 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements backgroundTimeMs, /* withSeconds */ false, /* collapseTimeUnit */ false))); + + final String stateString; + final String footerString; + //TODO(b/178197718) Update strings + if (!mBatteryOptimizeUtils.isValidPackageName()) { + //Present optimized only string when the package name is invalid. + stateString = context.getString(R.string.manager_battery_usage_optimized_title); + footerString = context.getString( + R.string.manager_battery_usage_footer_limited, stateString); + } else if (mBatteryOptimizeUtils.isSystemOrDefaultApp()) { + //Present unrestricted only string when the package is system or default active app. + stateString = context.getString(R.string.manager_battery_usage_unrestricted_title); + footerString = context.getString( + R.string.manager_battery_usage_footer_limited, stateString); + } else { + //Present default string to normal app. + footerString = context.getString(R.string.manager_battery_usage_footer); + + } + mFooterPreference.setTitle(Html.fromHtml(footerString, Html.FROM_HTML_MODE_COMPACT)); } @Override @@ -274,6 +324,15 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements (SettingsActivity) getActivity(), this, getSettingsLifecycle(), packageName, mState, REQUEST_UNINSTALL, REQUEST_REMOVE_DEVICE_ADMIN); controllers.add(mAppButtonsPreferenceController); + mUnrestrictedPreferenceController = + new UnrestrictedPreferenceController(context, uid, packageName); + mOptimizedPreferenceController = + new OptimizedPreferenceController(context, uid, packageName); + mRestrictedPreferenceController = + new RestrictedPreferenceController(context, uid, packageName); + controllers.add(mUnrestrictedPreferenceController); + controllers.add(mOptimizedPreferenceController); + controllers.add(mRestrictedPreferenceController); return controllers; } @@ -298,4 +357,15 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements mBackgroundActivityPreferenceController.updateSummary( findPreference(mBackgroundActivityPreferenceController.getPreferenceKey())); } + + @Override + public void onRadioButtonClicked(RadioButtonPreference selected) { + updatePreferenceState(mUnrestrictedPreference, selected.getKey()); + updatePreferenceState(mOptimizePreference, selected.getKey()); + updatePreferenceState(mRestrictedPreference, selected.getKey()); + } + + private void updatePreferenceState(RadioButtonPreference preference, String selectedKey) { + preference.setChecked(selectedKey.equals(preference.getKey())); + } } diff --git a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java new file mode 100644 index 00000000000..1184a779e3f --- /dev/null +++ b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java @@ -0,0 +1,121 @@ +/* + * 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)); + } +} diff --git a/src/com/android/settings/fuelgauge/OptimizedPreferenceController.java b/src/com/android/settings/fuelgauge/OptimizedPreferenceController.java new file mode 100644 index 00000000000..b2da35632ea --- /dev/null +++ b/src/com/android/settings/fuelgauge/OptimizedPreferenceController.java @@ -0,0 +1,85 @@ +/* + * 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 android.content.Context; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.widget.RadioButtonPreference; + +public class OptimizedPreferenceController extends AbstractPreferenceController + implements PreferenceControllerMixin { + + private static final String TAG = "OPTIMIZED_PREF"; + + @VisibleForTesting String KEY_OPTIMIZED_PREF = "optimized_pref"; + @VisibleForTesting BatteryOptimizeUtils mBatteryOptimizeUtils; + + public OptimizedPreferenceController(Context context, int uid, String packageName) { + super(context); + mBatteryOptimizeUtils = new BatteryOptimizeUtils(context, uid, packageName); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void updateState(Preference preference) { + if (!mBatteryOptimizeUtils.isValidPackageName()) { + Log.d(TAG, "invalid package name, optimized states only"); + preference.setEnabled(true); + ((RadioButtonPreference) preference).setChecked(true); + return; + } + + if (mBatteryOptimizeUtils.isSystemOrDefaultApp()) { + Log.d(TAG, "is system or default app, disable pref"); + ((RadioButtonPreference) preference).setChecked(false); + preference.setEnabled(false); + } else if (mBatteryOptimizeUtils.getAppUsageState() == OPTIMIZED) { + Log.d(TAG, "is optimized states"); + ((RadioButtonPreference) preference).setChecked(true); + } else { + ((RadioButtonPreference) preference).setChecked(false); + } + } + + @Override + public String getPreferenceKey() { + return KEY_OPTIMIZED_PREF; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!KEY_OPTIMIZED_PREF.equals(preference.getKey())) { + return false; + } + + mBatteryOptimizeUtils.setAppUsageState(OPTIMIZED); + Log.d(TAG, "Set optimized"); + return true; + } +} diff --git a/src/com/android/settings/fuelgauge/RestrictedPreferenceController.java b/src/com/android/settings/fuelgauge/RestrictedPreferenceController.java new file mode 100644 index 00000000000..b52af578e13 --- /dev/null +++ b/src/com/android/settings/fuelgauge/RestrictedPreferenceController.java @@ -0,0 +1,88 @@ +/* + * 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.RESTRICTED; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.widget.RadioButtonPreference; + +public class RestrictedPreferenceController extends AbstractPreferenceController + implements PreferenceControllerMixin { + + private static final String TAG = "RESTRICTED_PREF"; + + @VisibleForTesting String KEY_RESTRICTED_PREF = "restricted_pref"; + @VisibleForTesting BatteryOptimizeUtils mBatteryOptimizeUtils; + + public RestrictedPreferenceController(Context context, int uid, String packageName) { + super(context); + mBatteryOptimizeUtils = new BatteryOptimizeUtils(context, uid, packageName); + } + + @Override + public void updateState(Preference preference) { + + if (!mBatteryOptimizeUtils.isValidPackageName()) { + Log.d(TAG, "invalid package name, disable pref"); + preference.setEnabled(false); + return; + } else { + preference.setEnabled(true); + } + + if (mBatteryOptimizeUtils.isSystemOrDefaultApp()) { + Log.d(TAG, "is system or default app, disable pref"); + ((RadioButtonPreference) preference).setChecked(false); + preference.setEnabled(false); + } else if (mBatteryOptimizeUtils.getAppUsageState() == RESTRICTED) { + Log.d(TAG, "is restricted states"); + ((RadioButtonPreference) preference).setChecked(true); + } else { + ((RadioButtonPreference) preference).setChecked(false); + } + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + return KEY_RESTRICTED_PREF; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!KEY_RESTRICTED_PREF.equals(preference.getKey())) { + return false; + } + + mBatteryOptimizeUtils.setAppUsageState(RESTRICTED); + Log.d(TAG, "Set restricted"); + return true; + } +} diff --git a/src/com/android/settings/fuelgauge/UnrestrictedPreferenceController.java b/src/com/android/settings/fuelgauge/UnrestrictedPreferenceController.java new file mode 100644 index 00000000000..36141c52abd --- /dev/null +++ b/src/com/android/settings/fuelgauge/UnrestrictedPreferenceController.java @@ -0,0 +1,86 @@ +/* + * 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.UNRESTRICTED; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.widget.RadioButtonPreference; + +public class UnrestrictedPreferenceController extends AbstractPreferenceController + implements PreferenceControllerMixin { + + private static final String TAG = "UNRESTRICTED_PREF"; + + @VisibleForTesting String KEY_UNRESTRICTED_PREF = "unrestricted_pref"; + @VisibleForTesting BatteryOptimizeUtils mBatteryOptimizeUtils; + + public UnrestrictedPreferenceController(Context context, int uid, String packageName) { + super(context); + mBatteryOptimizeUtils = new BatteryOptimizeUtils(context, uid, packageName); + } + + @Override + public void updateState(Preference preference) { + + if (!mBatteryOptimizeUtils.isValidPackageName()) { + Log.d(TAG, "invalid package name, disable pref"); + preference.setEnabled(false); + return; + } else { + preference.setEnabled(true); + } + + if (mBatteryOptimizeUtils.isSystemOrDefaultApp()) { + Log.d(TAG, "is system or default app, unrestricted states only"); + ((RadioButtonPreference) preference).setChecked(true); + } else if (mBatteryOptimizeUtils.getAppUsageState() == UNRESTRICTED) { + Log.d(TAG, "is unrestricted states"); + ((RadioButtonPreference) preference).setChecked(true); + } else { + ((RadioButtonPreference) preference).setChecked(false); + } + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + return KEY_UNRESTRICTED_PREF; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!KEY_UNRESTRICTED_PREF.equals(preference.getKey())) { + return false; + } + + mBatteryOptimizeUtils.setAppUsageState(UNRESTRICTED); + Log.d(TAG, "Set unrestricted"); + return true; + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java index 7f76c7040ae..820607fffaa 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doAnswer; @@ -58,6 +59,7 @@ import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.instantapps.InstantAppDataProvider; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.widget.LayoutPreference; +import com.android.settingslib.widget.RadioButtonPreference; import org.junit.After; import org.junit.Before; @@ -96,6 +98,9 @@ public class AdvancedPowerUsageDetailTest { private static final long PROCSTATE_TOP_TIME_US = PROCSTATE_TOP_TIME_MS * 1000; private static final long PHONE_FOREGROUND_TIME_MS = 250 * 1000; private static final long PHONE_BACKGROUND_TIME_MS = 0; + private static final String KEY_PREF_UNRESTRICTED = "unrestricted_pref"; + private static final String KEY_PREF_OPTIMIZED = "optimized_pref"; + private static final String KEY_PREF_RESTRICTED = "restricted_pref"; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private FragmentActivity mActivity; @@ -121,9 +126,15 @@ public class AdvancedPowerUsageDetailTest { private BatteryStats.Timer mForegroundActivityTimer; @Mock private BatteryUtils mBatteryUtils; + @Mock + private BatteryOptimizeUtils mBatteryOptimizeUtils; private Context mContext; private Preference mForegroundPreference; private Preference mBackgroundPreference; + private Preference mFooterPreference; + private RadioButtonPreference mRestrictedPreference; + private RadioButtonPreference mOptimizePreference; + private RadioButtonPreference mUnrestrictedPreference; private AdvancedPowerUsageDetail mFragment; private SettingsActivity mTestActivity; @@ -170,6 +181,7 @@ public class AdvancedPowerUsageDetailTest { mFragment.mHeaderPreference = mHeaderPreference; mFragment.mState = mState; mFragment.mBatteryUtils = new BatteryUtils(RuntimeEnvironment.application); + mFragment.mBatteryOptimizeUtils = mBatteryOptimizeUtils; mAppEntry.info = mock(ApplicationInfo.class); mTestActivity = spy(new SettingsActivity()); @@ -194,8 +206,16 @@ public class AdvancedPowerUsageDetailTest { mForegroundPreference = new Preference(mContext); mBackgroundPreference = new Preference(mContext); + mFooterPreference = new Preference(mContext); + mRestrictedPreference = new RadioButtonPreference(mContext); + mOptimizePreference = new RadioButtonPreference(mContext); + mUnrestrictedPreference = new RadioButtonPreference(mContext); mFragment.mForegroundPreference = mForegroundPreference; mFragment.mBackgroundPreference = mBackgroundPreference; + mFragment.mFooterPreference = mFooterPreference; + mFragment.mRestrictedPreference = mRestrictedPreference; + mFragment.mOptimizePreference = mOptimizePreference; + mFragment.mUnrestrictedPreference = mUnrestrictedPreference; } @After @@ -352,4 +372,48 @@ public class AdvancedPowerUsageDetailTest { assertThat(mForegroundPreference.getSummary().toString()).isEqualTo("Used for 0 min"); assertThat(mBackgroundPreference.getSummary().toString()).isEqualTo("Active for 0 min"); } + + @Test + public void testInitPreference_isValidPackageName_hasCorrectString() { + when(mBatteryOptimizeUtils.isValidPackageName()).thenReturn(false); + + mFragment.initPreference(); + + assertThat(mFooterPreference.getTitle().toString()) + .isEqualTo("This app requires Optimized battery usage."); + } + + @Test + public void testInitPreference_isSystemOrDefaultApp_hasCorrectString() { + when(mBatteryOptimizeUtils.isValidPackageName()).thenReturn(true); + when(mBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(true); + + mFragment.initPreference(); + + assertThat(mFooterPreference.getTitle() + .toString()).isEqualTo("This app requires Unrestricted battery usage."); + } + + @Test + public void testInitPreference_hasCorrectString() { + when(mBatteryOptimizeUtils.isValidPackageName()).thenReturn(true); + when(mBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(false); + + mFragment.initPreference(); + + assertThat(mFooterPreference.getTitle().toString()) + .isEqualTo("Changing how an app uses your battery can affect its performance."); + } + + @Test + public void testOnRadioButtonClicked_clickOptimizePref_optimizePreferenceChecked() { + mOptimizePreference.setKey(KEY_PREF_OPTIMIZED); + mRestrictedPreference.setKey(KEY_PREF_RESTRICTED); + mUnrestrictedPreference.setKey(KEY_PREF_UNRESTRICTED); + mFragment.onRadioButtonClicked(mOptimizePreference); + + assertThat(mOptimizePreference.isChecked()).isTrue(); + assertThat(mRestrictedPreference.isChecked()).isFalse(); + assertThat(mUnrestrictedPreference.isChecked()).isFalse(); + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java new file mode 100644 index 00000000000..89d66be187b --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java @@ -0,0 +1,146 @@ +/* + * 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); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/OptimizedPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/OptimizedPreferenceControllerTest.java new file mode 100644 index 00000000000..874618db00b --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/OptimizedPreferenceControllerTest.java @@ -0,0 +1,111 @@ +/* + * 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.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import com.android.settingslib.widget.RadioButtonPreference; + +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 OptimizedPreferenceControllerTest { + private static final int UID = 12345; + private static final String PACKAGE_NAME = "com.android.app"; + + private OptimizedPreferenceController mController; + private RadioButtonPreference mPreference; + + @Mock BatteryOptimizeUtils mockBatteryOptimizeUtils; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mController = new OptimizedPreferenceController( + RuntimeEnvironment.application, UID, PACKAGE_NAME); + mPreference = new RadioButtonPreference(RuntimeEnvironment.application); + mController.mBatteryOptimizeUtils = mockBatteryOptimizeUtils; + } + + @Test + public void testUpdateState_invalidPackage_prefEnabled() { + when(mockBatteryOptimizeUtils.isValidPackageName()).thenReturn(false); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + assertThat(mPreference.isChecked()).isTrue(); + } + + @Test + public void testUpdateState_isSystemOrDefaultApp_prefUnchecked() { + when(mockBatteryOptimizeUtils.isValidPackageName()).thenReturn(true); + when(mockBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(true); + + mController.updateState(mPreference); + + assertThat(mPreference.isChecked()).isFalse(); + assertThat(mPreference.isEnabled()).isFalse(); + } + + @Test + public void testUpdateState_isOptimizedStates_prefChecked() { + when(mockBatteryOptimizeUtils.isValidPackageName()).thenReturn(true); + when(mockBatteryOptimizeUtils.getAppUsageState()).thenReturn( + BatteryOptimizeUtils.AppUsageState.OPTIMIZED); + + mController.updateState(mPreference); + + assertThat(mPreference.isChecked()).isTrue(); + } + + @Test + public void testUpdateState_prefUnchecked() { + when(mockBatteryOptimizeUtils.isValidPackageName()).thenReturn(true); + + mController.updateState(mPreference); + + assertThat(mPreference.isChecked()).isFalse(); + } + + @Test + public void testHandlePreferenceTreeClick_samePrefKey_verifyAction() { + mPreference.setKey(mController.KEY_OPTIMIZED_PREF); + mController.handlePreferenceTreeClick(mPreference); + + verify(mockBatteryOptimizeUtils).setAppUsageState( + BatteryOptimizeUtils.AppUsageState.OPTIMIZED); + } + + @Test + public void testHandlePreferenceTreeClick_incorrectPrefKey_noAction() { + mController.handlePreferenceTreeClick(mPreference); + + verifyZeroInteractions(mockBatteryOptimizeUtils); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/RestrictedPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/RestrictedPreferenceControllerTest.java new file mode 100644 index 00000000000..2e17404f675 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/RestrictedPreferenceControllerTest.java @@ -0,0 +1,119 @@ +/* + * 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.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import com.android.settingslib.widget.RadioButtonPreference; + +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 RestrictedPreferenceControllerTest { + private static final int UID = 12345; + private static final String PACKAGE_NAME = "com.android.app"; + + private RestrictedPreferenceController mController; + private RadioButtonPreference mPreference; + + @Mock BatteryOptimizeUtils mockBatteryOptimizeUtils; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mController = new RestrictedPreferenceController( + RuntimeEnvironment.application, UID, PACKAGE_NAME); + mPreference = new RadioButtonPreference(RuntimeEnvironment.application); + mController.mBatteryOptimizeUtils = mockBatteryOptimizeUtils; + } + + @Test + public void testUpdateState_isValidPackage_prefEnabled() { + when(mockBatteryOptimizeUtils.isValidPackageName()).thenReturn(true); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + } + + @Test + public void testUpdateState_invalidPackage_prefDisabled() { + when(mockBatteryOptimizeUtils.isValidPackageName()).thenReturn(false); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isFalse(); + } + + @Test + public void testUpdateState_isSystemOrDefaultApp_prefChecked() { + when(mockBatteryOptimizeUtils.isValidPackageName()).thenReturn(true); + when(mockBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(true); + + mController.updateState(mPreference); + + assertThat(mPreference.isChecked()).isFalse(); + assertThat(mPreference.isEnabled()).isFalse(); + } + + @Test + public void testUpdateState_isRestrictedStates_prefChecked() { + when(mockBatteryOptimizeUtils.isValidPackageName()).thenReturn(true); + when(mockBatteryOptimizeUtils.getAppUsageState()).thenReturn( + BatteryOptimizeUtils.AppUsageState.RESTRICTED); + + mController.updateState(mPreference); + + assertThat(mPreference.isChecked()).isTrue(); + } + + @Test + public void testUpdateState_prefUnchecked() { + when(mockBatteryOptimizeUtils.isValidPackageName()).thenReturn(true); + + mController.updateState(mPreference); + + assertThat(mPreference.isChecked()).isFalse(); + } + + @Test + public void testHandlePreferenceTreeClick_samePrefKey_verifyAction() { + mPreference.setKey(mController.KEY_RESTRICTED_PREF); + mController.handlePreferenceTreeClick(mPreference); + + verify(mockBatteryOptimizeUtils).setAppUsageState( + BatteryOptimizeUtils.AppUsageState.RESTRICTED); + } + + @Test + public void testHandlePreferenceTreeClick_incorrectPrefKey_noAction() { + mController.handlePreferenceTreeClick(mPreference); + + verifyZeroInteractions(mockBatteryOptimizeUtils); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/UnrestrictedPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/UnrestrictedPreferenceControllerTest.java new file mode 100644 index 00000000000..63cf760a2f6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/UnrestrictedPreferenceControllerTest.java @@ -0,0 +1,119 @@ +/* + * 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.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import com.android.settingslib.widget.RadioButtonPreference; + +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 UnrestrictedPreferenceControllerTest { + private static final int UID = 12345; + private static final String PACKAGE_NAME = "com.android.app"; + + private UnrestrictedPreferenceController mController; + private RadioButtonPreference mPreference; + + @Mock BatteryOptimizeUtils mockBatteryOptimizeUtils; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mController = new UnrestrictedPreferenceController( + RuntimeEnvironment.application, UID, PACKAGE_NAME); + mPreference = new RadioButtonPreference(RuntimeEnvironment.application); + mController.mBatteryOptimizeUtils = mockBatteryOptimizeUtils; + } + + @Test + public void testUpdateState_isValidPackage_prefEnabled() { + when(mockBatteryOptimizeUtils.isValidPackageName()).thenReturn(true); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + } + + @Test + public void testUpdateState_invalidPackage_prefDisabled() { + when(mockBatteryOptimizeUtils.isValidPackageName()).thenReturn(false); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isFalse(); + } + + @Test + public void testUpdateState_isSystemOrDefaultApp_prefChecked() { + when(mockBatteryOptimizeUtils.isValidPackageName()).thenReturn(true); + when(mockBatteryOptimizeUtils.isSystemOrDefaultApp()).thenReturn(true); + + mController.updateState(mPreference); + + assertThat(mPreference.isChecked()).isTrue(); + } + + @Test + public void testUpdateState_isUnrestrictedStates_prefChecked() { + when(mockBatteryOptimizeUtils.isValidPackageName()).thenReturn(true); + when(mockBatteryOptimizeUtils.getAppUsageState()).thenReturn( + BatteryOptimizeUtils.AppUsageState.UNRESTRICTED); + + mController.updateState(mPreference); + + assertThat(mPreference.isChecked()).isTrue(); + } + + @Test + public void testUpdateState_prefUnchecked() { + when(mockBatteryOptimizeUtils.isValidPackageName()).thenReturn(true); + + mController.updateState(mPreference); + + assertThat(mPreference.isChecked()).isFalse(); + } + + @Test + public void testHandlePreferenceTreeClick_samePrefKey_verifyAction() { + mPreference.setKey(mController.KEY_UNRESTRICTED_PREF); + mController.handlePreferenceTreeClick(mPreference); + + verify(mockBatteryOptimizeUtils).setAppUsageState( + BatteryOptimizeUtils.AppUsageState.UNRESTRICTED); + } + + @Test + public void testHandlePreferenceTreeClick_incorrectPrefKey_noAction() { + mController.handlePreferenceTreeClick(mPreference); + + verifyZeroInteractions(mockBatteryOptimizeUtils); + } +}