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

Commit 16a99aec authored by Jacky Wang's avatar Jacky Wang Committed by Android (Google) Code Review
Browse files

Merge "[DataStore] Migrate BatteryBackupHelper to BackupRestoreStorage" into main

parents 1a2d60a8 006bf17c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -90,6 +90,7 @@ android_library {
        "MediaDrmSettingsFlagsLib",
        "Settings-change-ids",
        "SettingsLib",
        "SettingsLibDataStore",
        "SettingsLibActivityEmbedding",
        "aconfig_settings_flags_lib",
        "accessibility_settings_flags_lib",
+10 −0
Original line number Diff line number Diff line
@@ -28,11 +28,13 @@ import androidx.annotation.NonNull;
import com.android.settings.activityembedding.ActivityEmbeddingRulesController;
import com.android.settings.activityembedding.ActivityEmbeddingUtils;
import com.android.settings.core.instrumentation.ElapsedTimeUtils;
import com.android.settings.fuelgauge.BatterySettingsStorage;
import com.android.settings.homepage.SettingsHomepageActivity;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.overlay.FeatureFactoryImpl;
import com.android.settings.spa.SettingsSpaEnvironment;
import com.android.settingslib.applications.AppIconCacheManager;
import com.android.settingslib.datastore.BackupRestoreStorageManager;
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory;

import com.google.android.setupcompat.util.WizardManagerHelper;
@@ -54,6 +56,8 @@ public class SettingsApplication extends Application {
    public void onCreate() {
        super.onCreate();

        BackupRestoreStorageManager.getInstance(this).add(new BatterySettingsStorage(this));

        // Add null checking to avoid test case failed.
        if (getApplicationContext() != null) {
            ElapsedTimeUtils.assignSuwFinishedTimeStamp(getApplicationContext());
@@ -73,6 +77,12 @@ public class SettingsApplication extends Application {
        }
    }

    @Override
    public void onTerminate() {
        BackupRestoreStorageManager.getInstance(this).removeAll();
        super.onTerminate();
    }

    @NonNull
    protected FeatureFactory getFeatureFactory() {
        return new FeatureFactoryImpl();
+3 −2
Original line number Diff line number Diff line
@@ -22,10 +22,10 @@ import android.app.backup.BackupAgentHelper;
import android.app.backup.SharedPreferencesBackupHelper;

import com.android.settings.flags.Flags;
import com.android.settings.fuelgauge.BatteryBackupHelper;
import com.android.settings.onboarding.OnboardingFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.shortcut.CreateShortcutPreferenceController;
import com.android.settingslib.datastore.BackupRestoreStorageManager;

/** Backup agent for Settings APK */
public class SettingsBackupHelper extends BackupAgentHelper {
@@ -35,7 +35,7 @@ public class SettingsBackupHelper extends BackupAgentHelper {
    @Override
    public void onCreate() {
        super.onCreate();
        addHelper(BatteryBackupHelper.TAG, new BatteryBackupHelper(this));
        BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this);
        addHelper(PREF_LOCALE_NOTIFICATION,
                new SharedPreferencesBackupHelper(this, LOCALE_NOTIFICATION));
        if (Flags.enableSoundBackup()) {
@@ -51,6 +51,7 @@ public class SettingsBackupHelper extends BackupAgentHelper {
    @Override
    public void onRestoreFinished() {
        super.onRestoreFinished();
        BackupRestoreStorageManager.getInstance(this).onRestoreFinished();
        CreateShortcutPreferenceController.updateRestoredShortcuts(this);
    }
}
+2 −5
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isUserCon

import android.app.Activity;
import android.app.ActivityManager;
import android.app.backup.BackupManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
@@ -53,6 +52,7 @@ 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.datastore.ChangeReason;
import com.android.settingslib.widget.LayoutPreference;

import java.util.ArrayList;
@@ -105,7 +105,6 @@ public class AdvancedPowerUsageDetail extends DashboardFragment
    @VisibleForTesting @BatteryOptimizeUtils.OptimizationMode
    int mOptimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN;

    @VisibleForTesting BackupManager mBackupManager;
    @VisibleForTesting StringBuilder mLogStringBuilder;

    // A wrapper class to carry LaunchBatteryDetailPage required arguments.
@@ -293,9 +292,7 @@ public class AdvancedPowerUsageDetail extends DashboardFragment
    @VisibleForTesting
    void notifyBackupManager() {
        if (mOptimizationMode != mBatteryOptimizeUtils.getAppOptimizationMode()) {
            final BackupManager backupManager =
                    mBackupManager != null ? mBackupManager : new BackupManager(getContext());
            backupManager.dataChanged();
            BatterySettingsStorage.get(getContext()).notifyChange(ChangeReason.UPDATE);
        }
    }

+370 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 java.nio.charset.StandardCharsets.UTF_8;

import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.os.IDeviceIdleController;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.datastore.BackupContext;
import com.android.settingslib.datastore.BackupRestoreEntity;
import com.android.settingslib.datastore.BackupRestoreStorageManager;
import com.android.settingslib.datastore.EntityBackupResult;
import com.android.settingslib.datastore.ObservableBackupRestoreStorage;
import com.android.settingslib.datastore.RestoreContext;
import com.android.settingslib.fuelgauge.PowerAllowlistBackend;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/** An implementation to backup and restore battery configurations. */
public final class BatterySettingsStorage extends ObservableBackupRestoreStorage {
    public static final String TAG = "BatteryBackupHelper";

    // Definition for the device build information.
    public static final String KEY_BUILD_BRAND = "device_build_brand";
    public static final String KEY_BUILD_PRODUCT = "device_build_product";
    public static final String KEY_BUILD_MANUFACTURER = "device_build_manufacture";
    public static final String KEY_BUILD_FINGERPRINT = "device_build_fingerprint";
    // Customized fields for device extra information.
    public static final String KEY_BUILD_METADATA_1 = "device_build_metadata_1";
    public static final String KEY_BUILD_METADATA_2 = "device_build_metadata_2";

    private static final String DEVICE_IDLE_SERVICE = "deviceidle";
    private static final String BATTERY_OPTIMIZE_BACKUP_FILE_NAME =
            "battery_optimize_backup_historical_logs";
    private static final int DEVICE_BUILD_INFO_SIZE = 6;

    static final String DELIMITER = ",";
    static final String DELIMITER_MODE = ":";
    static final String KEY_OPTIMIZATION_LIST = "optimization_mode_list";

    @Nullable private byte[] mOptimizationModeBytes;

    private final Application mApplication;
    // Device information map from restore.
    private final ArrayMap<String, String> mDeviceBuildInfoMap =
            new ArrayMap<>(DEVICE_BUILD_INFO_SIZE);

    /**
     * Returns the {@link BatterySettingsStorage} registered to {@link BackupRestoreStorageManager}.
     */
    public static @NonNull BatterySettingsStorage get(@NonNull Context context) {
        return (BatterySettingsStorage)
                BackupRestoreStorageManager.getInstance(context).getOrThrow(TAG);
    }

    public BatterySettingsStorage(@NonNull Context context) {
        mApplication = (Application) context.getApplicationContext();
    }

    @NonNull
    @Override
    public String getName() {
        return TAG;
    }

    @Override
    public boolean enableBackup(@NonNull BackupContext backupContext) {
        return isOwner();
    }

    @Override
    public boolean enableRestore() {
        return isOwner();
    }

    static boolean isOwner() {
        return UserHandle.myUserId() == UserHandle.USER_SYSTEM;
    }

    @NonNull
    @Override
    public List<BackupRestoreEntity> createBackupRestoreEntities() {
        List<String> allowlistedApps = getFullPowerList();
        if (allowlistedApps == null) {
            return Collections.emptyList();
        }
        PowerUsageFeatureProvider provider =
                FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider();
        return Arrays.asList(
                new StringEntity(KEY_BUILD_BRAND, Build.BRAND),
                new StringEntity(KEY_BUILD_PRODUCT, Build.PRODUCT),
                new StringEntity(KEY_BUILD_MANUFACTURER, Build.MANUFACTURER),
                new StringEntity(KEY_BUILD_FINGERPRINT, Build.FINGERPRINT),
                new StringEntity(KEY_BUILD_METADATA_1, provider.getBuildMetadata1(mApplication)),
                new StringEntity(KEY_BUILD_METADATA_2, provider.getBuildMetadata2(mApplication)),
                new OptimizationModeEntity(allowlistedApps));
    }

    private @Nullable List<String> getFullPowerList() {
        final long timestamp = System.currentTimeMillis();
        String[] allowlistedApps;
        try {
            IDeviceIdleController deviceIdleController =
                    IDeviceIdleController.Stub.asInterface(
                            ServiceManager.getService(DEVICE_IDLE_SERVICE));
            allowlistedApps = deviceIdleController.getFullPowerWhitelist();
        } catch (RemoteException e) {
            Log.e(TAG, "backupFullPowerList() failed", e);
            return null;
        }
        // Ignores unexpected empty result case.
        if (allowlistedApps == null || allowlistedApps.length == 0) {
            Log.w(TAG, "no data found in the getFullPowerList()");
            return Collections.emptyList();
        }
        Log.d(
                TAG,
                String.format(
                        "getFullPowerList() size=%d in %d/ms",
                        allowlistedApps.length, (System.currentTimeMillis() - timestamp)));
        return Arrays.asList(allowlistedApps);
    }

    @Override
    public void writeNewStateDescription(@NonNull ParcelFileDescriptor newState) {
        BatterySettingsMigrateChecker.verifySaverConfiguration(mApplication);
        performRestoreIfNeeded();
    }

    private void performRestoreIfNeeded() {
        byte[] bytes = mOptimizationModeBytes;
        mOptimizationModeBytes = null; // clear data
        if (bytes == null || bytes.length == 0) {
            return;
        }
        final PowerUsageFeatureProvider provider =
                FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider();
        if (!provider.isValidToRestoreOptimizationMode(mDeviceBuildInfoMap)) {
            return;
        }
        // Start to restore the app optimization mode data.
        final int restoreCount = restoreOptimizationMode(bytes);
        if (restoreCount > 0) {
            BatterySettingsMigrateChecker.verifyBatteryOptimizeModes(mApplication);
        }
    }

    int restoreOptimizationMode(byte[] dataBytes) {
        final long timestamp = System.currentTimeMillis();
        final String dataContent = new String(dataBytes, UTF_8);
        if (dataContent.isEmpty()) {
            Log.w(TAG, "no data found in the restoreOptimizationMode()");
            return 0;
        }
        final String[] appConfigurations = dataContent.split(BatteryBackupHelper.DELIMITER);
        if (appConfigurations.length == 0) {
            Log.w(TAG, "no data found from the split() processing");
            return 0;
        }
        int restoreCount = 0;
        for (String appConfiguration : appConfigurations) {
            final String[] results = appConfiguration.split(BatteryBackupHelper.DELIMITER_MODE);
            // Example format: com.android.systemui:2 we should have length=2
            if (results.length != 2) {
                Log.w(TAG, "invalid raw data found:" + appConfiguration);
                continue;
            }
            final String packageName = results[0];
            final int uid = BatteryUtils.getInstance(mApplication).getPackageUid(packageName);
            // Ignores system/default apps.
            if (isSystemOrDefaultApp(packageName, uid)) {
                Log.w(TAG, "ignore from isSystemOrDefaultApp():" + packageName);
                continue;
            }
            @BatteryOptimizeUtils.OptimizationMode int optimizationMode;
            try {
                optimizationMode = Integer.parseInt(results[1]);
            } catch (NumberFormatException e) {
                Log.e(TAG, "failed to parse the optimization mode: " + appConfiguration, e);
                continue;
            }
            restoreOptimizationMode(packageName, optimizationMode);
            restoreCount++;
        }
        Log.d(
                TAG,
                String.format(
                        "restoreOptimizationMode() count=%d in %d/ms",
                        restoreCount, (System.currentTimeMillis() - timestamp)));
        return restoreCount;
    }

    private void restoreOptimizationMode(
            String packageName, @BatteryOptimizeUtils.OptimizationMode int mode) {
        final BatteryOptimizeUtils batteryOptimizeUtils =
                newBatteryOptimizeUtils(mApplication, packageName);
        if (batteryOptimizeUtils == null) {
            return;
        }
        batteryOptimizeUtils.setAppUsageState(
                mode, BatteryOptimizeHistoricalLogEntry.Action.RESTORE);
        Log.d(TAG, String.format("restore:%s mode=%d", packageName, mode));
    }

    @Nullable
    static BatteryOptimizeUtils newBatteryOptimizeUtils(Context context, String packageName) {
        final int uid = BatteryUtils.getInstance(context).getPackageUid(packageName);
        return uid == BatteryUtils.UID_NULL
                ? null
                : new BatteryOptimizeUtils(context, uid, packageName);
    }

    private boolean isSystemOrDefaultApp(String packageName, int uid) {
        return BatteryOptimizeUtils.isSystemOrDefaultApp(
                mApplication, PowerAllowlistBackend.getInstance(mApplication), packageName, uid);
    }

    private class StringEntity implements BackupRestoreEntity {
        private final String mKey;
        private final String mValue;

        StringEntity(String key, String value) {
            this.mKey = key;
            this.mValue = value;
        }

        @NonNull
        @Override
        public String getKey() {
            return mKey;
        }

        @Override
        public @NonNull EntityBackupResult backup(
                @NonNull BackupContext backupContext, @NonNull OutputStream outputStream)
                throws IOException {
            Log.d(TAG, String.format("backup:%s:%s", mKey, mValue));
            outputStream.write(mValue.getBytes(UTF_8));
            return EntityBackupResult.UPDATE;
        }

        @Override
        public void restore(
                @NonNull RestoreContext restoreContext, @NonNull InputStream inputStream)
                throws IOException {
            String dataContent = new String(inputStream.readAllBytes(), UTF_8);
            mDeviceBuildInfoMap.put(mKey, dataContent);
            Log.d(TAG, String.format("restore:%s:%s", mKey, dataContent));
        }
    }

    private class OptimizationModeEntity implements BackupRestoreEntity {
        private final List<String> mAllowlistedApps;

        private OptimizationModeEntity(List<String> allowlistedApps) {
            this.mAllowlistedApps = allowlistedApps;
        }

        @NonNull
        @Override
        public String getKey() {
            return KEY_OPTIMIZATION_LIST;
        }

        @Override
        public @NonNull EntityBackupResult backup(
                @NonNull BackupContext backupContext, @NonNull OutputStream outputStream)
                throws IOException {
            final long timestamp = System.currentTimeMillis();
            final ArraySet<ApplicationInfo> applications = getInstalledApplications();
            if (applications == null || applications.isEmpty()) {
                Log.w(TAG, "no data found in the getInstalledApplications()");
                return EntityBackupResult.DELETE;
            }
            int backupCount = 0;
            final StringBuilder builder = new StringBuilder();
            final AppOpsManager appOps = mApplication.getSystemService(AppOpsManager.class);
            final SharedPreferences sharedPreferences = getSharedPreferences(mApplication);
            // Converts application into the AppUsageState.
            for (ApplicationInfo info : applications) {
                final int mode = BatteryOptimizeUtils.getMode(appOps, info.uid, info.packageName);
                @BatteryOptimizeUtils.OptimizationMode
                final int optimizationMode =
                        BatteryOptimizeUtils.getAppOptimizationMode(
                                mode, mAllowlistedApps.contains(info.packageName));
                // Ignores default optimized/unknown state or system/default apps.
                if (optimizationMode == BatteryOptimizeUtils.MODE_OPTIMIZED
                        || optimizationMode == BatteryOptimizeUtils.MODE_UNKNOWN
                        || isSystemOrDefaultApp(info.packageName, info.uid)) {
                    continue;
                }
                final String packageOptimizeMode =
                        info.packageName + DELIMITER_MODE + optimizationMode;
                builder.append(packageOptimizeMode).append(DELIMITER);
                Log.d(TAG, "backupOptimizationMode: " + packageOptimizeMode);
                BatteryOptimizeLogUtils.writeLog(
                        sharedPreferences,
                        Action.BACKUP,
                        info.packageName,
                        /* actionDescription */ "mode: " + optimizationMode);
                backupCount++;
            }

            outputStream.write(builder.toString().getBytes(UTF_8));
            Log.d(
                    TAG,
                    String.format(
                            "backup getInstalledApplications():%d count=%d in %d/ms",
                            applications.size(),
                            backupCount,
                            (System.currentTimeMillis() - timestamp)));
            return EntityBackupResult.UPDATE;
        }

        private @Nullable ArraySet<ApplicationInfo> getInstalledApplications() {
            return BatteryOptimizeUtils.getInstalledApplications(
                    mApplication, AppGlobals.getPackageManager());
        }

        static @NonNull SharedPreferences getSharedPreferences(Context context) {
            return context.getSharedPreferences(
                    BATTERY_OPTIMIZE_BACKUP_FILE_NAME, Context.MODE_PRIVATE);
        }

        @Override
        public void restore(
                @NonNull RestoreContext restoreContext, @NonNull InputStream inputStream)
                throws IOException {
            mOptimizationModeBytes = inputStream.readAllBytes();
        }
    }
}
Loading