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

Commit 6147e0fa authored by Tiffany Nguyen's avatar Tiffany Nguyen Committed by YK Hung
Browse files

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
(cherry picked from commit 5ecb1a1d)
Merged-In: I5fee9ebe03284a013da6bfca9ada8b166c6af91c
parent 048bc692
Loading
Loading
Loading
Loading
+15 −0
Original line number Diff line number Diff line
@@ -11794,6 +11794,21 @@
    <!-- [CHAR_LIMIT=NONE] Label for when app is ignoring battery optimizations -->
    <string name="not_battery_optimizing">Not using battery optimization</string>
    <!-- Preference title for battery optimization list page[CHAR_LIMIT=50]-->
    <string name="app_battery_optimization_title">Manage battery usage</string>
    <!-- Preference summary for battery optimization list page[CHAR_LIMIT=50]-->
    <string name="app_battery_optimization_summary">Set battery usage for apps</string>
    <!-- Filter title for battery unrestricted[CHAR_LIMIT=50]-->
    <string name="filter_battery_unrestricted_title">Unrestricted</string>
    <!-- Filter title for battery optimized[CHAR_LIMIT=50]-->
    <string name="filter_battery_optimized_title">Optimized</string>
    <!-- Filter title for battery restricted[CHAR_LIMIT=50]-->
    <string name="filter_battery_restricted_title">Restricted</string>
    <!-- Text for the setting on whether you can type text into notifications without unlocking the device. -->
    <string name="lockscreen_remote_input">If device is locked, prevent typing replies or other text in notifications</string>
+2 −0
Original line number Diff line number Diff line
@@ -326,6 +326,8 @@ public class Settings extends SettingsActivity {
    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 */ }
+180 −0
Original line number Diff line number Diff line
/*
 * 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;
        }
    }
}
+56 −24
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import androidx.annotation.IntDef;

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,7 +38,8 @@ import com.android.settingslib.applications.ApplicationsState;
 */
public class AppFilterRegistry {

    @IntDef(value = {
    @IntDef(
            value = {
                FILTER_APPS_POWER_ALLOWLIST,
                FILTER_APPS_POWER_ALLOWLIST_ALL,
                FILTER_APPS_ALL,
@@ -56,9 +58,11 @@ public class AppFilterRegistry {
                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 {
    }
    @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 class AppFilterRegistry {
    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 @@ public class AppFilterRegistry {
                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 @@ public class AppFilterRegistry {
                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;
        }
+50 −3
Original line number Diff line number Diff line
@@ -20,6 +20,9 @@ import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;

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;
@@ -94,6 +97,7 @@ import com.android.settings.applications.AppInfoBase;
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;
@@ -118,6 +122,7 @@ import com.android.settings.applications.appinfo.WriteSettingsDetails;
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 class ManageApplications extends InstrumentedFragment
    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 @@ public class ManageApplications extends InstrumentedFragment
            }
        } 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 @@ public class ManageApplications extends InstrumentedFragment
        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 @@ public class ManageApplications extends InstrumentedFragment
                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 @@ public class ManageApplications extends InstrumentedFragment
                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.
@@ -934,6 +954,8 @@ public class ManageApplications extends InstrumentedFragment
            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;
@@ -1126,6 +1148,8 @@ public class ManageApplications extends InstrumentedFragment
                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;
            }
@@ -1157,18 +1181,21 @@ public class ManageApplications extends InstrumentedFragment

        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();
            }
@@ -1305,6 +1332,26 @@ public class ManageApplications extends InstrumentedFragment
            });
        }

        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) {
Loading