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

Commit 9cbb19d0 authored by Andreas Gampe's avatar Andreas Gampe Committed by Gerrit Code Review
Browse files

Merge changes Ia3ec6198,I211696c8

* changes:
  Decouple downgrade and optimization processes.
  Add new atoms to log Downgraded Apps and Low Storage
parents d1379a4a 4bf2be95
Loading
Loading
Loading
Loading
+44 −0
Original line number Diff line number Diff line
@@ -139,6 +139,9 @@ message Atom {
        BluetoothLinkLayerConnectionEvent bluetooth_link_layer_connection_event = 125;
        BluetoothAclConnectionStateChanged bluetooth_acl_connection_state_changed = 126;
        BluetoothScoConnectionStateChanged bluetooth_sco_connection_state_changed = 127;
        AppDowngraded app_downgraded = 128;
        AppOptimizedAfterDowngraded app_optimized_after_downgraded = 129;
        LowStorageStateChanged low_storage_state_changed = 130;
        NfcErrorOccurred nfc_error_occurred = 134;
        NfcStateChanged nfc_state_changed = 135;
        NfcBeamOccurred nfc_beam_occurred = 136;
@@ -2006,6 +2009,47 @@ message ActivityForegroundStateChanged {
    optional State state = 4;
}

/**
 * Logs when a volume entered low Storage state.
 * Logged from:
 *      frameworks/base/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
 */
message LowStorageStateChanged {
    // Volume that ran out of storage.
    optional string volume_description = 1;

    enum State {
        UNKNOWN = 0;
        OFF = 1;
        ON = 2;
    }
    optional State state = 2;
}

/**
 * Logs when an app is downgraded.
 * Logged from:
 *      frameworks/base/services/core/java/com/android/server/pm/BackgroundDexOptService.java
 */
message AppDowngraded {
    optional string package_name = 1;
    // Size of the package (all data) before being downgraded.
    optional int64 size_in_bytes_before = 2;
    // Size of the package (all data) after being downgraded.
    optional int64 size_in_bytes_after = 3;

    optional bool aggressive = 4;
}

/**
 * Logs when an app is optimized after being downgraded.
 * Logged from:
 *      frameworks/base/services/core/java/com/android/server/pm/BackgroundDexOptService.java
 */
message AppOptimizedAfterDowngraded {
    optional string package_name = 1;
}

/**
 * Logs when an app crashes.
 * Logged from:
+180 −69
Original line number Diff line number Diff line
@@ -27,24 +27,30 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.os.BatteryManager;
import android.os.Environment;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.util.ArraySet;
import android.util.Log;
import android.util.StatsLog;

import com.android.server.pm.dex.DexManager;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.PinnerService;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;

import java.io.File;
import java.nio.file.Paths;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;

/**
 * {@hide}
@@ -52,7 +58,7 @@ import java.util.concurrent.TimeUnit;
public class BackgroundDexOptService extends JobService {
    private static final String TAG = "BackgroundDexOptService";

    private static final boolean DEBUG = false;
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private static final int JOB_IDLE_OPTIMIZE = 800;
    private static final int JOB_POST_BOOT_UPDATE = 801;
@@ -97,7 +103,6 @@ public class BackgroundDexOptService extends JobService {
    private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);

    private final File mDataDir = Environment.getDataDirectory();

    private static final long mDowngradeUnusedAppsThresholdInMillis =
            getDowngradeUnusedAppsThresholdInMillis();

@@ -270,106 +275,145 @@ public class BackgroundDexOptService extends JobService {

        long lowStorageThreshold = getLowStorageThreshold(context);
        // Optimize primary apks.
        int result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ true,
                sFailedPackageNamesPrimary);

        int result = optimizePackages(pm, pkgs, lowStorageThreshold,
            /*isForPrimaryDex=*/ true);
        if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
            return result;
        }

        if (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) {
        if (supportSecondaryDex()) {
            result = reconcileSecondaryDexFiles(pm.getDexManager());
            if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
                return result;
            }

            result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ false,
                    sFailedPackageNamesSecondary);
            result = optimizePackages(pm, pkgs, lowStorageThreshold,
                /*isForPrimaryDex=*/ false);
        }
        return result;
    }

    /**
     * Get the size of the directory. It uses recursion to go over all files.
     * @param f
     * @return
     */
    private long getDirectorySize(File f) {
        long size = 0;
        if (f.isDirectory()) {
            for (File file: f.listFiles()) {
                size += getDirectorySize(file);
            }
        } else {
            size = f.length();
        }
        return size;
    }

    /**
     * Get the size of a package.
     * @param pkg
     */
    private long getPackageSize(PackageManagerService pm, String pkg) {
        PackageInfo info = pm.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM);
        long size = 0;
        if (info != null && info.applicationInfo != null) {
            File path = Paths.get(info.applicationInfo.sourceDir).toFile();
            if (path.isFile()) {
                path = path.getParentFile();
            }
            size += getDirectorySize(path);
            if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
                for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
                    path = Paths.get(splitSourceDir).toFile();
                    if (path.isFile()) {
                        path = path.getParentFile();
                    }
                    size += getDirectorySize(path);
                }
            }
            return size;
        }
        return 0;
    }

    private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
            long lowStorageThreshold, boolean is_for_primary_dex,
            ArraySet<String> failedPackageNames) {
            long lowStorageThreshold, boolean isForPrimaryDex) {
        ArraySet<String> updatedPackages = new ArraySet<>();
        Set<String> unusedPackages = pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
        Log.d(TAG, "Unsused Packages " +  String.join(",", unusedPackages));
        // Only downgrade apps when space is low on device.
        // Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
        // up disk before user hits the actual lowStorageThreshold.
        final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE *
                lowStorageThreshold;
        boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
        Log.d(TAG, "Should Downgrade " + shouldDowngrade);
        boolean dex_opt_performed = false;
        for (String pkg : pkgs) {
            int abort_code = abortIdleOptimizations(lowStorageThreshold);
            if (abort_code == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
                return abort_code;
            }

            synchronized (failedPackageNames) {
                if (failedPackageNames.contains(pkg)) {
                    // Skip previously failing package
            // Downgrade unused packages.
            if (unusedPackages.contains(pkg) && shouldDowngrade) {
                dex_opt_performed = downgradePackage(pm, pkg, isForPrimaryDex);
            } else {
                if (abort_code == OPTIMIZE_ABORT_NO_SPACE_LEFT) {
                    // can't dexopt because of low space.
                    continue;
                }
                dex_opt_performed = optimizePackage(pm, pkg, isForPrimaryDex);
            }
            if (dex_opt_performed) {
                updatedPackages.add(pkg);
            }
        }

            int reason;
            boolean downgrade;
            // Downgrade unused packages.
            if (unusedPackages.contains(pkg) && shouldDowngrade) {
        notifyPinService(updatedPackages);
        return OPTIMIZE_PROCESSED;
    }


    /**
     * Try to downgrade the package to a smaller compilation filter.
     * eg. if the package is in speed-profile the package will be downgraded to verify.
     * @param pm PackageManagerService
     * @param pkg The package to be downgraded.
     * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
     * @return true if the package was downgraded.
     */
    private boolean downgradePackage(PackageManagerService pm, String pkg,
            boolean isForPrimaryDex) {
        Log.d(TAG, "Downgrading " + pkg);
        boolean dex_opt_performed = false;
        int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
        int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
                | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB
                | DexoptOptions.DEXOPT_DOWNGRADE;
        long package_size_before = getPackageSize(pm, pkg);

        if (isForPrimaryDex) {
            // This applies for system apps or if packages location is not a directory, i.e.
            // monolithic install.
                if (is_for_primary_dex && !pm.canHaveOatDir(pkg)) {
            if (!pm.canHaveOatDir(pkg)) {
                // For apps that don't have the oat directory, instead of downgrading,
                // remove their compiler artifacts from dalvik cache.
                pm.deleteOatArtifactsOfPackage(pkg);
                    continue;
            } else {
                    reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
                    downgrade = true;
                dex_opt_performed = performDexOptPrimary(pm, pkg, reason, dexoptFlags);
            }
            } else if (abort_code != OPTIMIZE_ABORT_NO_SPACE_LEFT) {
                reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
                downgrade = false;
        } else {
                // can't dexopt because of low space.
                continue;
            dex_opt_performed = performDexOptSecondary(pm, pkg, reason, dexoptFlags);
        }

            synchronized (failedPackageNames) {
                // Conservatively add package to the list of failing ones in case
                // performDexOpt never returns.
                failedPackageNames.add(pkg);
            }

            // Optimize package if needed. Note that there can be no race between
            // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
            boolean success;
            int dexoptFlags =
                    DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
                    DexoptOptions.DEXOPT_BOOT_COMPLETE |
                    (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0) |
                    DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
            if (is_for_primary_dex) {
                int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, reason,
                        dexoptFlags));
                success = result != PackageDexOptimizer.DEX_OPT_FAILED;
                if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
                    updatedPackages.add(pkg);
                }
            } else {
                success = pm.performDexOpt(new DexoptOptions(pkg,
                        reason, dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX));
            }
            if (success) {
                // Dexopt succeeded, remove package from the list of failing ones.
                synchronized (failedPackageNames) {
                    failedPackageNames.remove(pkg);
        if (dex_opt_performed) {
            StatsLog.write(StatsLog.APP_DOWNGRADED, pkg, package_size_before,
                    getPackageSize(pm, pkg), /*aggressive=*/ false);
        }
        return dex_opt_performed;
    }
        }
        notifyPinService(updatedPackages);
        return OPTIMIZE_PROCESSED;

    private boolean supportSecondaryDex() {
        return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false));
    }

    private int reconcileSecondaryDexFiles(DexManager dm) {
@@ -383,6 +427,73 @@ public class BackgroundDexOptService extends JobService {
        return OPTIMIZE_PROCESSED;
    }

    /**
     *
     * Optimize package if needed. Note that there can be no race between
     * concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
     * @param pm An instance of PackageManagerService
     * @param pkg The package to be downgraded.
     * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
     * @return true if the package was downgraded.
     */
    private boolean optimizePackage(PackageManagerService pm, String pkg,
            boolean isForPrimaryDex) {
        int reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
        int dexoptFlags = DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
                | DexoptOptions.DEXOPT_BOOT_COMPLETE
                | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;

        return isForPrimaryDex
            ? performDexOptPrimary(pm, pkg, reason, dexoptFlags)
            : performDexOptSecondary(pm, pkg, reason, dexoptFlags);
    }

    private boolean performDexOptPrimary(PackageManagerService pm, String pkg, int reason,
            int dexoptFlags) {
        int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false,
                () -> pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, dexoptFlags)));
        return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
    }

    private boolean performDexOptSecondary(PackageManagerService pm, String pkg, int reason,
            int dexoptFlags) {
        DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason,
                dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
        int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true,
                () -> pm.performDexOpt(dexoptOptions)
                    ? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED
        );
        return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
    }

    /**
     * Execute the dexopt wrapper and make sure that if performDexOpt wrapper fails
     * the package is added to the list of failed packages.
     * Return one of following result:
     *  {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
     *  {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
     *  {@link PackageDexOptimizer#DEX_OPT_FAILED}
     */
    private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex,
            Supplier<Integer> performDexOptWrapper) {
        ArraySet<String> sFailedPackageNames =
                isForPrimaryDex ? sFailedPackageNamesPrimary : sFailedPackageNamesSecondary;
        synchronized (sFailedPackageNames) {
            if (sFailedPackageNames.contains(pkg)) {
                // Skip previously failing package
                return PackageDexOptimizer.DEX_OPT_SKIPPED;
            }
            sFailedPackageNames.add(pkg);
        }
        int result = performDexOptWrapper.get();
        if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
            synchronized (sFailedPackageNames) {
                sFailedPackageNames.remove(pkg);
            }
        }
        return result;
    }

    // Evaluate whether or not idle optimizations should continue.
    private int abortIdleOptimizations(long lowStorageThreshold) {
        if (mAbortIdleOptimization.get()) {
+7 −2
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.TrafficStats;
import android.os.Binder;
import android.os.Environment;
import android.os.FileObserver;
@@ -42,13 +41,13 @@ import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.DataUnit;
import android.util.Slog;
import android.util.StatsLog;

import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.EventLogTags;
import com.android.server.IoThread;
import com.android.server.SystemService;
import com.android.server.pm.InstructionSets;
import com.android.server.pm.PackageManagerService;
@@ -499,9 +498,15 @@ public class DeviceStorageMonitorService extends SystemService {
            notification.flags |= Notification.FLAG_NO_CLEAR;
            mNotifManager.notifyAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE,
                    notification, UserHandle.ALL);
            StatsLog.write(StatsLog.LOW_STORAGE_STATE_CHANGED,
                    Objects.toString(vol.getDescription()),
                    StatsLog.LOW_STORAGE_STATE_CHANGED__STATE__ON);
        } else if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel)) {
            mNotifManager.cancelAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE,
                    UserHandle.ALL);
            StatsLog.write(StatsLog.LOW_STORAGE_STATE_CHANGED,
                    Objects.toString(vol.getDescription()),
                    StatsLog.LOW_STORAGE_STATE_CHANGED__STATE__OFF);
        }
    }