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

Commit f4261d72 authored by Kevin Han's avatar Kevin Han
Browse files

Introduce global package hibernation

Introduce the concept of global package hibernation / package-level
hibernation. This opens the way for optimizations at the package or
APK level based off usage across all users.

Bug: 175829330
Test: atest AppHibernationServiceTest
Test: adb shell cmd app_hibernation get-state --global
com.google.android.deskclock

Change-Id: Ic989fc09a4c60f3bbf28b34fe38fa07e77eedacb
parent 37f1ed35
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -1414,8 +1414,10 @@ package android.app.usage {
package android.apphibernation {
  public final class AppHibernationManager {
    method public boolean isHibernating(@NonNull String);
    method public void setHibernating(@NonNull String, boolean);
    method public boolean isHibernatingForUser(@NonNull String);
    method public boolean isHibernatingGlobally(@NonNull String);
    method public void setHibernatingForUser(@NonNull String, boolean);
    method public void setHibernatingGlobally(@NonNull String, boolean);
  }
}
+36 −6
Original line number Diff line number Diff line
@@ -49,31 +49,61 @@ public final class AppHibernationManager {
    }

    /**
     * Returns true if the package is hibernating, false otherwise.
     * Returns true if the package is hibernating for this context's user, false otherwise.
     *
     * @hide
     */
    @SystemApi
    public boolean isHibernating(@NonNull String packageName) {
    public boolean isHibernatingForUser(@NonNull String packageName) {
        try {
            return mIAppHibernationService.isHibernating(packageName, mContext.getUserId());
            return mIAppHibernationService.isHibernatingForUser(packageName, mContext.getUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Set whether the package is hibernating.
     * Set whether the package is hibernating for this context's user.
     *
     * @hide
     */
    @SystemApi
    public void setHibernating(@NonNull String packageName, boolean isHibernating) {
    public void setHibernatingForUser(@NonNull String packageName, boolean isHibernating) {
        try {
            mIAppHibernationService.setHibernating(packageName, mContext.getUserId(),
            mIAppHibernationService.setHibernatingForUser(packageName, mContext.getUserId(),
                    isHibernating);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Returns true if app is hibernating globally / at the package level.
     *
     * @hide
     */
    @SystemApi
    public boolean isHibernatingGlobally(@NonNull String packageName) {
        try {
            return mIAppHibernationService.isHibernatingGlobally(packageName);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Set whether a package should be globally hibernating. This hibernates the package at a
     * package level. User-level hibernation (e.g.. {@link #isHibernatingForUser} is independent
     * from global hibernation.
     *
     * @hide
     */
    @SystemApi
    public void setHibernatingGlobally(@NonNull String packageName, boolean isHibernating) {
        try {
            mIAppHibernationService.setHibernatingGlobally(packageName, isHibernating);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}
+4 −2
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ package android.apphibernation;
 * @hide
 */
interface IAppHibernationService {
    boolean isHibernating(String packageName, int userId);
    void setHibernating(String packageName, int userId, boolean isHibernating);
    boolean isHibernatingForUser(String packageName, int userId);
    void setHibernatingForUser(String packageName, int userId, boolean isHibernating);
    boolean isHibernatingGlobally(String packageName);
    void setHibernatingGlobally(String packageName, boolean isHibernating);
}
 No newline at end of file
+128 −25
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.content.Intent.ACTION_USER_ADDED;
import static android.content.Intent.ACTION_USER_REMOVED;
import static android.content.Intent.EXTRA_REMOVED_FOR_ALL_USERS;
import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PackageManager.MATCH_ALL;
import static android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION;
@@ -46,6 +47,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
@@ -55,6 +57,7 @@ import com.android.server.SystemService;
import java.io.FileDescriptor;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * System service that manages app hibernation state, a state apps can enter that means they are
@@ -74,6 +77,8 @@ public final class AppHibernationService extends SystemService {
    private final UserManager mUserManager;
    @GuardedBy("mLock")
    private final SparseArray<Map<String, UserPackageState>> mUserStates = new SparseArray<>();
    @GuardedBy("mLock")
    private final Set<String> mGloballyHibernatedPackages = new ArraySet<>();

    /**
     * Initializes the system service.
@@ -138,7 +143,7 @@ public final class AppHibernationService extends SystemService {
     * @param userId the user to check
     * @return true if package is hibernating for the user
     */
    public boolean isHibernating(String packageName, int userId) {
    boolean isHibernatingForUser(String packageName, int userId) {
        userId = handleIncomingUser(userId, "isHibernating");
        synchronized (mLock) {
            final Map<String, UserPackageState> packageStates = mUserStates.get(userId);
@@ -151,7 +156,19 @@ public final class AppHibernationService extends SystemService {
                        String.format("Package %s is not installed for user %s",
                                packageName, userId));
            }
            return pkgState != null ? pkgState.hibernated : null;
            return pkgState.hibernated;
        }
    }

    /**
     * Whether a package is hibernated globally. This only occurs when a package is hibernating for
     * all users and allows us to make optimizations at the package or APK level.
     *
     * @param packageName package to check
     */
    boolean isHibernatingGlobally(String packageName) {
        synchronized (mLock) {
            return mGloballyHibernatedPackages.contains(packageName);
        }
    }

@@ -162,7 +179,7 @@ public final class AppHibernationService extends SystemService {
     * @param userId user
     * @param isHibernating new hibernation state
     */
    public void setHibernating(String packageName, int userId, boolean isHibernating) {
    void setHibernatingForUser(String packageName, int userId, boolean isHibernating) {
        userId = handleIncomingUser(userId, "setHibernating");
        synchronized (mLock) {
            if (!mUserStates.contains(userId)) {
@@ -180,29 +197,96 @@ public final class AppHibernationService extends SystemService {
                return;
            }

            if (isHibernating) {
                hibernatePackageForUserL(packageName, userId, pkgState);
            } else {
                unhibernatePackageForUserL(packageName, userId, pkgState);
            }
        }
    }

            final long caller = Binder.clearCallingIdentity();
            try {
    /**
     * Set whether the package should be hibernated globally at a package level, allowing the
     * the system to make optimizations at the package or APK level.
     *
     * @param packageName package to hibernate globally
     * @param isHibernating new hibernation state
     */
    void setHibernatingGlobally(String packageName, boolean isHibernating) {
        if (isHibernating != mGloballyHibernatedPackages.contains(packageName)) {
            synchronized (mLock) {
                if (isHibernating) {
                    hibernatePackageGloballyL(packageName);
                } else {
                    unhibernatePackageGloballyL(packageName);
                }
            }
        }
    }

    /**
     * Put an app into hibernation for a given user, allowing user-level optimizations to occur.
     * The caller should hold {@link #mLock}
     *
     * @param pkgState package hibernation state
     */
    private void hibernatePackageForUserL(@NonNull String packageName, int userId,
            @NonNull UserPackageState pkgState) {
        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackage");
        final long caller = Binder.clearCallingIdentity();
        try {
            mIActivityManager.forceStopPackage(packageName, userId);
            mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId,
                    null /* observer */);
                } else {
                    Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackage");
                    mIPackageManager.setPackageStoppedState(packageName, false, userId);
                }
                pkgState.hibernated = isHibernating;
            pkgState.hibernated = true;
        } catch (RemoteException e) {
            throw new IllegalStateException(
                    "Failed to hibernate due to manager not being available", e);
        } finally {
            Binder.restoreCallingIdentity(caller);
            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
        }
    }

    /**
     * Remove a package from hibernation for a given user. The caller should hold {@link #mLock}.
     *
     * @param pkgState package hibernation state
     */
    private void unhibernatePackageForUserL(@NonNull String packageName, int userId,
            UserPackageState pkgState) {
        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackage");
        final long caller = Binder.clearCallingIdentity();
        try {
            mIPackageManager.setPackageStoppedState(packageName, false, userId);
            pkgState.hibernated = false;
        } catch (RemoteException e) {
            throw new IllegalStateException(
                    "Failed to unhibernate due to manager not being available", e);
        } finally {
            Binder.restoreCallingIdentity(caller);
            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
        }
    }

            // TODO: Support package level hibernation when package is hibernating for all users
    /**
     * Put a package into global hibernation, optimizing its storage at a package / APK level.
     * The caller should hold {@link #mLock}.
     */
    private void hibernatePackageGloballyL(@NonNull String packageName) {
        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackageGlobally");
        // TODO(175830194): Delete vdex/odex when DexManager API is built out
        mGloballyHibernatedPackages.add(packageName);
        Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
    }

    /**
     * Unhibernate a package from global hibernation. The caller should hold {@link #mLock}.
     */
    private void unhibernatePackageGloballyL(@NonNull String packageName) {
        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackageGlobally");
        mGloballyHibernatedPackages.remove(packageName);
        Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
    }

    /**
@@ -220,8 +304,8 @@ public final class AppHibernationService extends SystemService {
            throw new IllegalStateException("Package manager not available.", e);
        }

        for (PackageInfo pkg : packageList) {
            packages.put(pkg.packageName, new UserPackageState());
        for (int i = 0, size = packageList.size(); i < size; i++) {
            packages.put(packageList.get(i).packageName, new UserPackageState());
        }
        mUserStates.put(userId, packages);
    }
@@ -250,6 +334,12 @@ public final class AppHibernationService extends SystemService {
        }
    }

    private void onPackageRemovedForAllUsers(@NonNull String packageName) {
        synchronized (mLock) {
            mGloballyHibernatedPackages.remove(packageName);
        }
    }

    /**
     * Private helper method to get the real user id and enforce permission checks.
     *
@@ -277,13 +367,23 @@ public final class AppHibernationService extends SystemService {
        }

        @Override
        public boolean isHibernating(String packageName, int userId) {
            return mService.isHibernating(packageName, userId);
        public boolean isHibernatingForUser(String packageName, int userId) {
            return mService.isHibernatingForUser(packageName, userId);
        }

        @Override
        public void setHibernatingForUser(String packageName, int userId, boolean isHibernating) {
            mService.setHibernatingForUser(packageName, userId, isHibernating);
        }

        @Override
        public void setHibernating(String packageName, int userId, boolean isHibernating) {
            mService.setHibernating(packageName, userId, isHibernating);
        public void setHibernatingGlobally(String packageName, boolean isHibernating) {
            mService.setHibernatingGlobally(packageName, isHibernating);
        }

        @Override
        public boolean isHibernatingGlobally(String packageName) {
            return mService.isHibernatingGlobally(packageName);
        }

        @Override
@@ -322,6 +422,9 @@ public final class AppHibernationService extends SystemService {
                    onPackageAdded(packageName, userId);
                } else if (ACTION_PACKAGE_REMOVED.equals(action)) {
                    onPackageRemoved(packageName, userId);
                    if (intent.getBooleanExtra(EXTRA_REMOVED_FOR_ALL_USERS, false)) {
                        onPackageRemovedForAllUsers(packageName);
                    }
                }
            }
        }
+44 −17
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.server.apphibernation;

import android.os.ShellCommand;
import android.os.UserHandle;
import android.text.TextUtils;

import java.io.PrintWriter;

@@ -27,6 +26,7 @@ import java.io.PrintWriter;
 */
final class AppHibernationShellCommand extends ShellCommand {
    private static final String USER_OPT = "--user";
    private static final String GLOBAL_OPT = "--global";
    private static final int SUCCESS = 0;
    private static final int ERROR = -1;
    private final AppHibernationService mService;
@@ -51,7 +51,21 @@ final class AppHibernationShellCommand extends ShellCommand {
    }

    private int runSetState() {
        int userId = parseUserOption();
        String opt;
        boolean setsGlobal = false;
        int userId = UserHandle.USER_CURRENT;
        while ((opt = getNextOption()) != null) {
            switch (opt) {
                case USER_OPT:
                    userId = UserHandle.parseUserArg(getNextArgRequired());
                    break;
                case GLOBAL_OPT:
                    setsGlobal = true;
                    break;
                default:
                    getErrPrintWriter().println("Error: Unknown option: " + opt);
            }
        }

        String pkg = getNextArgRequired();
        if (pkg == null) {
@@ -66,32 +80,43 @@ final class AppHibernationShellCommand extends ShellCommand {
        }
        boolean newState = Boolean.parseBoolean(newStateRaw);

        mService.setHibernating(pkg, userId, newState);
        if (setsGlobal) {
            mService.setHibernatingGlobally(pkg, newState);
        } else {
            mService.setHibernatingForUser(pkg, userId, newState);
        }
        return SUCCESS;
    }

    private int runGetState() {
        int userId = parseUserOption();
        String opt;
        boolean requestsGlobal = false;
        int userId = UserHandle.USER_CURRENT;
        while ((opt = getNextOption()) != null) {
            switch (opt) {
                case USER_OPT:
                    userId = UserHandle.parseUserArg(getNextArgRequired());
                    break;
                case GLOBAL_OPT:
                    requestsGlobal = true;
                    break;
                default:
                    getErrPrintWriter().println("Error: Unknown option: " + opt);
            }
        }

        String pkg = getNextArgRequired();
        if (pkg == null) {
            getErrPrintWriter().println("Error: No package specified");
            return ERROR;
        }
        boolean isHibernating = mService.isHibernating(pkg, userId);
        boolean isHibernating = requestsGlobal
                ? mService.isHibernatingGlobally(pkg) : mService.isHibernatingForUser(pkg, userId);
        final PrintWriter pw = getOutPrintWriter();
        pw.println(isHibernating);
        return SUCCESS;
    }

    private int parseUserOption() {
        String option = getNextOption();
        if (TextUtils.equals(option, USER_OPT)) {
            return UserHandle.parseUserArg(getNextArgRequired());
        }
        return UserHandle.USER_CURRENT;
    }

    @Override
    public void onHelp() {
        final PrintWriter pw = getOutPrintWriter();
@@ -99,11 +124,13 @@ final class AppHibernationShellCommand extends ShellCommand {
        pw.println("  help");
        pw.println("    Print this help text.");
        pw.println("");
        pw.println("  set-state [--user USER_ID] PACKAGE true|false");
        pw.println("    Sets the hibernation state of the package to value specified");
        pw.println("  set-state [--user USER_ID] [--global] PACKAGE true|false");
        pw.println("    Sets the hibernation state of the package to value specified. Optionally");
        pw.println("    may specify a user id or set global hibernation state.");
        pw.println("");
        pw.println("  get-state [--user USER_ID] PACKAGE");
        pw.println("    Gets the hibernation state of the package");
        pw.println("  get-state [--user USER_ID] [--global] PACKAGE");
        pw.println("    Gets the hibernation state of the package. Optionally may specify a user");
        pw.println("    id or request global hibernation state.");
        pw.println("");
    }
}
Loading