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

Commit 6857f6ed authored by Kevin Han's avatar Kevin Han Committed by Gerrit Code Review
Browse files

Merge "Introduce global package hibernation"

parents 1528efe4 f4261d72
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -1415,8 +1415,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