Add Apps > Battery optimization page implementation.
Moving the old restricted page to the new optimization page will happen
in a follow-up CL.
Test: Unit, manual
Bug: 238026672
Change-Id: I5fee9ebe03284a013da6bfca9ada8b166c6af91c
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index c3ab8e2..f7ba017 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -326,6 +326,8 @@
public static class ChangeWifiStateActivity extends SettingsActivity { /* empty */ }
public static class AppDrawOverlaySettingsActivity extends SettingsActivity { /* empty */ }
public static class AppWriteSettingsActivity extends SettingsActivity { /* empty */ }
+ /** Activity to manage app battery optimization details. */
+ public static class AppBatteryOptimizationActivity extends SettingsActivity { /* empty */ }
public static class ManageExternalSourcesActivity extends SettingsActivity {/* empty */ }
public static class ManageAppExternalSourcesActivity extends SettingsActivity { /* empty */ }
diff --git a/src/com/android/settings/applications/AppStateBatteryOptimizationBridge.java b/src/com/android/settings/applications/AppStateBatteryOptimizationBridge.java
new file mode 100644
index 0000000..6301c85
--- /dev/null
+++ b/src/com/android/settings/applications/AppStateBatteryOptimizationBridge.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 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.applications;
+
+import android.annotation.IntDef;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.applications.ApplicationsState.AppFilter;
+import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class for bridging the Battery optimization information to ApplicationState.
+ */
+public class AppStateBatteryOptimizationBridge extends AppStateBaseBridge {
+ private static final String TAG = AppStateBatteryOptimizationBridge.class.getSimpleName();
+ static final boolean DEBUG = Build.IS_DEBUGGABLE;
+
+ private final Context mContext;
+ private final AppOpsManager mAppOpsManager;
+ private final PowerAllowlistBackend mPowerAllowlistBackend;
+
+ private static final int MODE_UNKNOWN = 0;
+ private static final int MODE_UNRESTRICTED = 1;
+ private static final int MODE_OPTIMIZED = 2;
+ private static final int MODE_RESTRICTED = 3;
+
+ @IntDef(
+ prefix = {"MODE_"},
+ value = {
+ MODE_UNKNOWN,
+ MODE_RESTRICTED,
+ MODE_UNRESTRICTED,
+ MODE_OPTIMIZED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface OptimizationMode {
+ }
+
+ public AppStateBatteryOptimizationBridge(
+ Context context, ApplicationsState appState, Callback callback) {
+ super(appState, callback);
+ mContext = context;
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+ mPowerAllowlistBackend = PowerAllowlistBackend.getInstance(mContext);
+ }
+
+ @Override
+ protected void updateExtraInfo(AppEntry app, String pkg, int uid) {
+ app.extraInfo = getAppBatteryOptimizationState(pkg, uid);
+ }
+
+ @Override
+ protected void loadAllExtraInfo() {
+ if (DEBUG) {
+ Log.d(TAG, "Start loadAllExtraInfo()");
+ }
+ mAppSession.getAllApps().stream().forEach(appEntry ->
+ updateExtraInfo(appEntry, appEntry.info.packageName, appEntry.info.uid));
+ if (DEBUG) {
+ Log.d(TAG, "End loadAllExtraInfo()");
+ }
+ }
+
+ protected Object getAppBatteryOptimizationState(String pkg, int uid) {
+ // Restricted = AppOpsManager.MODE_IGNORED + !allowListed
+ // Unrestricted = AppOpsManager.MODE_ALLOWED + allowListed
+ // Optimized = AppOpsManager.MODE_ALLOWED + !allowListed
+
+ boolean allowListed = mPowerAllowlistBackend.isAllowlisted(pkg);
+ int aomMode =
+ mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, pkg);
+ @OptimizationMode int mode = MODE_UNKNOWN;
+ String modeName = "";
+ if (aomMode == AppOpsManager.MODE_IGNORED && !allowListed) {
+ mode = MODE_RESTRICTED;
+ if (DEBUG) {
+ modeName = "RESTRICTED";
+ }
+ } else if (aomMode == AppOpsManager.MODE_ALLOWED) {
+ mode = allowListed ? MODE_UNRESTRICTED : MODE_OPTIMIZED;
+ if (DEBUG) {
+ modeName = allowListed ? "UNRESTRICTED" : "OPTIMIZED";
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Pkg: " + pkg + ", mode: " + modeName);
+ }
+ return new BatteryOptimizationDetails(mode);
+ }
+
+ @OptimizationMode
+ private static int getBatteryOptimizationDetailsMode(AppEntry entry) {
+ if (entry == null || entry.extraInfo == null) {
+ return MODE_UNKNOWN;
+ }
+
+ return entry.extraInfo instanceof BatteryOptimizationDetails
+ ? ((BatteryOptimizationDetails) entry.extraInfo).mMode
+ : MODE_UNKNOWN;
+ }
+
+ /**
+ * Used by {@link com.android.settings.applications.manageapplications.AppFilterRegistry} to
+ * determine which apps are unrestricted.
+ */
+ public static final AppFilter FILTER_BATTERY_UNRESTRICTED_APPS =
+ new AppFilter() {
+ @Override
+ public void init() {}
+
+ @Override
+ public boolean filterApp(AppEntry info) {
+ return getBatteryOptimizationDetailsMode(info) == MODE_UNRESTRICTED;
+ }
+ };
+
+ /**
+ * Used by {@link com.android.settings.applications.manageapplications.AppFilterRegistry} to
+ * determine which apps are optimized.
+ */
+ public static final AppFilter FILTER_BATTERY_OPTIMIZED_APPS =
+ new AppFilter() {
+ @Override
+ public void init() {}
+
+ @Override
+ public boolean filterApp(AppEntry info) {
+ return getBatteryOptimizationDetailsMode(info) == MODE_OPTIMIZED;
+ }
+ };
+
+ /**
+ * Used by {@link com.android.settings.applications.manageapplications.AppFilterRegistry} to
+ * determine which apps are restricted.
+ */
+ public static final AppFilter FILTER_BATTERY_RESTRICTED_APPS =
+ new AppFilter() {
+ @Override
+ public void init() {}
+
+ @Override
+ public boolean filterApp(AppEntry info) {
+ return getBatteryOptimizationDetailsMode(info) == MODE_RESTRICTED;
+ }
+ };
+
+ /**
+ * Extra details for battery optimization app data.
+ */
+ static final class BatteryOptimizationDetails {
+ @OptimizationMode
+ int mMode;
+
+ BatteryOptimizationDetails(@OptimizationMode int mode) {
+ mMode = mode;
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java b/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java
index 3c00b73..35de16e 100644
--- a/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java
+++ b/src/com/android/settings/applications/manageapplications/AppFilterRegistry.java
@@ -20,6 +20,7 @@
import com.android.settings.R;
import com.android.settings.applications.AppStateAlarmsAndRemindersBridge;
+import com.android.settings.applications.AppStateBatteryOptimizationBridge;
import com.android.settings.applications.AppStateInstallAppsBridge;
import com.android.settings.applications.AppStateLocaleBridge;
import com.android.settings.applications.AppStateManageExternalStorageBridge;
@@ -37,28 +38,31 @@
*/
public class AppFilterRegistry {
- @IntDef(value = {
- FILTER_APPS_POWER_ALLOWLIST,
- FILTER_APPS_POWER_ALLOWLIST_ALL,
- FILTER_APPS_ALL,
- FILTER_APPS_ENABLED,
- FILTER_APPS_INSTANT,
- FILTER_APPS_DISABLED,
- FILTER_APPS_RECENT,
- FILTER_APPS_FREQUENT,
- FILTER_APPS_PERSONAL,
- FILTER_APPS_WORK,
- FILTER_APPS_USAGE_ACCESS,
- FILTER_APPS_WITH_OVERLAY,
- FILTER_APPS_WRITE_SETTINGS,
- FILTER_APPS_INSTALL_SOURCES,
- FILTER_APPS_BLOCKED,
- FILTER_ALARMS_AND_REMINDERS,
- FILTER_APPS_MEDIA_MANAGEMENT,
- FILTER_APPS_LOCALE,
- })
- @interface FilterType {
- }
+ @IntDef(
+ value = {
+ FILTER_APPS_POWER_ALLOWLIST,
+ FILTER_APPS_POWER_ALLOWLIST_ALL,
+ FILTER_APPS_ALL,
+ FILTER_APPS_ENABLED,
+ FILTER_APPS_INSTANT,
+ FILTER_APPS_DISABLED,
+ FILTER_APPS_RECENT,
+ FILTER_APPS_FREQUENT,
+ FILTER_APPS_PERSONAL,
+ FILTER_APPS_WORK,
+ FILTER_APPS_USAGE_ACCESS,
+ FILTER_APPS_WITH_OVERLAY,
+ FILTER_APPS_WRITE_SETTINGS,
+ FILTER_APPS_INSTALL_SOURCES,
+ FILTER_APPS_BLOCKED,
+ FILTER_ALARMS_AND_REMINDERS,
+ FILTER_APPS_MEDIA_MANAGEMENT,
+ FILTER_APPS_LOCALE,
+ FILTER_APPS_BATTERY_UNRESTRICTED,
+ FILTER_APPS_BATTERY_OPTIMIZED,
+ FILTER_APPS_BATTERY_RESTRICTED,
+ })
+ @interface FilterType {}
// Filter options used for displayed list of applications
// Filters will appear sorted based on their value defined here.
@@ -82,14 +86,18 @@
public static final int FILTER_ALARMS_AND_REMINDERS = 18;
public static final int FILTER_APPS_MEDIA_MANAGEMENT = 19;
public static final int FILTER_APPS_LOCALE = 20;
- // Next id: 21. If you add an entry here, length of mFilters should be updated
+ public static final int FILTER_APPS_BATTERY_UNRESTRICTED = 21;
+ public static final int FILTER_APPS_BATTERY_OPTIMIZED = 22;
+ public static final int FILTER_APPS_BATTERY_RESTRICTED = 23;
+ // Next id: 24. If you add an entry here, please change NUM_FILTER_ENTRIES.
+ private static final int NUM_FILTER_ENTRIES = 24;
private static AppFilterRegistry sRegistry;
private final AppFilterItem[] mFilters;
private AppFilterRegistry() {
- mFilters = new AppFilterItem[21];
+ mFilters = new AppFilterItem[NUM_FILTER_ENTRIES];
// High power allowlist, on
mFilters[FILTER_APPS_POWER_ALLOWLIST] = new AppFilterItem(
@@ -212,6 +220,28 @@
AppStateLocaleBridge.FILTER_APPS_LOCALE,
FILTER_APPS_LOCALE,
R.string.app_locale_picker_title);
+
+ // Battery optimization app states:
+ // Unrestricted
+ mFilters[FILTER_APPS_BATTERY_UNRESTRICTED] =
+ new AppFilterItem(
+ AppStateBatteryOptimizationBridge.FILTER_BATTERY_UNRESTRICTED_APPS,
+ FILTER_APPS_BATTERY_UNRESTRICTED,
+ R.string.filter_battery_unrestricted_title);
+
+ // Optimized
+ mFilters[FILTER_APPS_BATTERY_OPTIMIZED] =
+ new AppFilterItem(
+ AppStateBatteryOptimizationBridge.FILTER_BATTERY_OPTIMIZED_APPS,
+ FILTER_APPS_BATTERY_OPTIMIZED,
+ R.string.filter_battery_optimized_title);
+
+ // Unrestricted
+ mFilters[FILTER_APPS_BATTERY_RESTRICTED] =
+ new AppFilterItem(
+ AppStateBatteryOptimizationBridge.FILTER_BATTERY_RESTRICTED_APPS,
+ FILTER_APPS_BATTERY_RESTRICTED,
+ R.string.filter_battery_restricted_title);
}
@@ -248,6 +278,8 @@
return FILTER_APPS_MEDIA_MANAGEMENT;
case ManageApplications.LIST_TYPE_APPS_LOCALE:
return FILTER_APPS_LOCALE;
+ case ManageApplications.LIST_TYPE_BATTERY_OPTIMIZATION:
+ return FILTER_APPS_BATTERY_OPTIMIZED;
default:
return FILTER_APPS_ALL;
}
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java
index 0b7d522..d020f33 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplications.java
+++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java
@@ -21,6 +21,9 @@
import static com.android.settings.ChangeIds.CHANGE_RESTRICT_SAW_INTENT;
import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_ALL;
+import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_BATTERY_OPTIMIZED;
+import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_BATTERY_RESTRICTED;
+import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_BATTERY_UNRESTRICTED;
import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_BLOCKED;
import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_DISABLED;
import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_ENABLED;
@@ -96,6 +99,7 @@
import com.android.settings.applications.AppStateAlarmsAndRemindersBridge;
import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
import com.android.settings.applications.AppStateBaseBridge;
+import com.android.settings.applications.AppStateBatteryOptimizationBridge;
import com.android.settings.applications.AppStateInstallAppsBridge;
import com.android.settings.applications.AppStateLocaleBridge;
import com.android.settings.applications.AppStateManageExternalStorageBridge;
@@ -120,6 +124,7 @@
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
+import com.android.settings.fuelgauge.AdvancedPowerUsageDetail;
import com.android.settings.fuelgauge.HighPowerDetail;
import com.android.settings.localepicker.AppLocalePickerActivity;
import com.android.settings.notification.ConfigureNotificationSettings;
@@ -229,6 +234,7 @@
public static final int LIST_TYPE_ALARMS_AND_REMINDERS = 12;
public static final int LIST_TYPE_MEDIA_MANAGEMENT_APPS = 13;
public static final int LIST_TYPE_APPS_LOCALE = 14;
+ public static final int LIST_TYPE_BATTERY_OPTIMIZATION = 15;
// List types that should show instant apps.
public static final Set<Integer> LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList(
@@ -327,6 +333,8 @@
}
} else if (className.equals(AppLocaleDetails.class.getName())) {
mListType = LIST_TYPE_APPS_LOCALE;
+ } else if (className.equals(Settings.AppBatteryOptimizationActivity.class.getName())) {
+ mListType = LIST_TYPE_BATTERY_OPTIMIZATION;
} else {
mListType = LIST_TYPE_MAIN;
}
@@ -460,6 +468,12 @@
if (mListType == LIST_TYPE_HIGH_POWER) {
mFilterAdapter.enableFilter(FILTER_APPS_POWER_ALLOWLIST_ALL);
}
+ if (mListType == LIST_TYPE_BATTERY_OPTIMIZATION) {
+ mFilterAdapter.enableFilter(FILTER_APPS_ALL);
+ mFilterAdapter.enableFilter(FILTER_APPS_BATTERY_UNRESTRICTED);
+ mFilterAdapter.enableFilter(FILTER_APPS_BATTERY_OPTIMIZED);
+ mFilterAdapter.enableFilter(FILTER_APPS_BATTERY_RESTRICTED);
+ }
setCompositeFilter();
}
@@ -511,6 +525,8 @@
return SettingsEnums.MEDIA_MANAGEMENT_APPS;
case LIST_TYPE_APPS_LOCALE:
return SettingsEnums.APPS_LOCALE_LIST;
+ case LIST_TYPE_BATTERY_OPTIMIZATION:
+ return SettingsEnums.BATTERY_OPTIMIZED_APPS_LIST;
default:
return SettingsEnums.PAGE_UNKNOWN;
}
@@ -641,6 +657,10 @@
intent.putExtra(AppInfoBase.ARG_PACKAGE_UID, mCurrentUid);
startActivity(intent);
break;
+ case LIST_TYPE_BATTERY_OPTIMIZATION:
+ AdvancedPowerUsageDetail.startBatteryDetailPage(
+ getActivity(), this, mCurrentPkgName);
+ break;
// TODO: Figure out if there is a way where we can spin up the profile's settings
// process ahead of time, to avoid a long load of data when user clicks on a managed
// app. Maybe when they load the list of apps that contains managed profile apps.
@@ -923,6 +943,8 @@
screenTitle = R.string.app_notifications_title;
} else if (className.equals(AppLocaleDetails.class.getName())) {
screenTitle = R.string.app_locales_picker_menu_title;
+ } else if (className.equals(Settings.AppBatteryOptimizationActivity.class.getName())) {
+ screenTitle = R.string.app_battery_optimization_title;
} else {
if (screenTitle == -1) {
screenTitle = R.string.all_apps;
@@ -1115,6 +1137,8 @@
mExtraInfoBridge = new AppStateMediaManagementAppsBridge(mContext, mState, this);
} else if (mManageApplications.mListType == LIST_TYPE_APPS_LOCALE) {
mExtraInfoBridge = new AppStateLocaleBridge(mContext, mState, this);
+ } else if (mManageApplications.mListType == LIST_TYPE_BATTERY_OPTIMIZATION) {
+ mExtraInfoBridge = new AppStateBatteryOptimizationBridge(mContext, mState, this);
} else {
mExtraInfoBridge = null;
}
@@ -1146,18 +1170,21 @@
public void setFilter(AppFilterItem appFilter) {
mAppFilter = appFilter;
+ final int filterType = appFilter.getFilterType();
// Notification filters require resorting the list
if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) {
- if (FILTER_APPS_FREQUENT == appFilter.getFilterType()) {
+ if (FILTER_APPS_FREQUENT == filterType) {
rebuild(R.id.sort_order_frequent_notification, false);
- } else if (FILTER_APPS_RECENT == appFilter.getFilterType()) {
+ } else if (FILTER_APPS_RECENT == filterType) {
rebuild(R.id.sort_order_recent_notification, false);
- } else if (FILTER_APPS_BLOCKED == appFilter.getFilterType()) {
+ } else if (FILTER_APPS_BLOCKED == filterType) {
rebuild(R.id.sort_order_alpha, true);
} else {
rebuild(R.id.sort_order_alpha, true);
}
+ } else if (mManageApplications.mListType == LIST_TYPE_BATTERY_OPTIMIZATION) {
+ logBatteryOptimization(filterType);
} else {
rebuild();
}
@@ -1294,6 +1321,26 @@
});
}
+ private void logBatteryOptimization(int filterType) {
+ switch(filterType) {
+ case FILTER_APPS_BATTERY_UNRESTRICTED:
+ logAction(SettingsEnums.ACTION_BATTERY_OPTIMIZED_APPS_FILTER_UNRESTRICTED);
+ break;
+ case FILTER_APPS_BATTERY_OPTIMIZED:
+ logAction(SettingsEnums.ACTION_BATTERY_OPTIMIZED_APPS_FILTER_OPTIMIZED);
+ break;
+ case FILTER_APPS_BATTERY_RESTRICTED:
+ logAction(SettingsEnums.ACTION_BATTERY_OPTIMIZED_APPS_FILTER_RESTRICTED);
+ break;
+ default:
+ logAction(SettingsEnums.ACTION_BATTERY_OPTIMIZED_APPS_FILTER_ALL_APPS);
+ }
+ }
+
+ private void logAction(int action) {
+ mManageApplications.mMetricsFeatureProvider.action(mContext, action);
+ }
+
@VisibleForTesting
void filterSearch(String query) {
if (mSearchFilter == null) {
diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
index cf58c3c..c813a57 100644
--- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
+++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
@@ -41,15 +41,16 @@
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.overlay.FeatureFactory;
import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry;
import com.android.settings.fuelgauge.batteryusage.BatteryEntry;
import com.android.settings.fuelgauge.batteryusage.BatteryHistEntry;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.utils.StringUtil;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.widget.LayoutPreference;
@@ -209,8 +210,11 @@
return UserHandle.getUserId(batteryEntry.getUid());
}
- public static void startBatteryDetailPage(Activity caller,
- InstrumentedPreferenceFragment fragment, String packageName) {
+ /**
+ * Start packageName's battery detail page.
+ */
+ public static void startBatteryDetailPage(
+ Activity caller, Instrumentable instrumentable, String packageName) {
final Bundle args = new Bundle(3);
final PackageManager packageManager = caller.getPackageManager();
args.putString(EXTRA_PACKAGE_NAME, packageName);
@@ -225,7 +229,7 @@
.setDestination(AdvancedPowerUsageDetail.class.getName())
.setTitleRes(R.string.battery_details_title)
.setArguments(args)
- .setSourceMetricsCategory(fragment.getMetricsCategory())
+ .setSourceMetricsCategory(instrumentable.getMetricsCategory())
.launch();
}