diff options
| author | 2021-01-30 00:37:19 +0000 | |
|---|---|---|
| committer | 2021-01-30 00:37:19 +0000 | |
| commit | 6857f6ed91b99b2f6c38cec9afb4f7f1d962acd3 (patch) | |
| tree | b212313f3a7afcdaf97471d85e7de53abdf6cb7d | |
| parent | 1528efe4b11906a2cd1dd3ba07edeade6899a719 (diff) | |
| parent | f4261d72154a3c5c6db49ba87e26707cef1e35c5 (diff) | |
Merge "Introduce global package hibernation"
6 files changed, 266 insertions, 73 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index a7c5b7480334..602621185872 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -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); } } diff --git a/core/java/android/apphibernation/AppHibernationManager.java b/core/java/android/apphibernation/AppHibernationManager.java index 8f1934c7b77a..7281d50a33a5 100644 --- a/core/java/android/apphibernation/AppHibernationManager.java +++ b/core/java/android/apphibernation/AppHibernationManager.java @@ -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(); + } + } } diff --git a/core/java/android/apphibernation/IAppHibernationService.aidl b/core/java/android/apphibernation/IAppHibernationService.aidl index db57ecb73051..6a068ee2b147 100644 --- a/core/java/android/apphibernation/IAppHibernationService.aidl +++ b/core/java/android/apphibernation/IAppHibernationService.aidl @@ -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 diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index 508bb01e50a8..fded85cd9126 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -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,32 +197,99 @@ 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) { - Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackage"); - mIActivityManager.forceStopPackage(packageName, userId); - mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId, - null /* observer */); + hibernatePackageGloballyL(packageName); } else { - Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackage"); - mIPackageManager.setPackageStoppedState(packageName, false, userId); + unhibernatePackageGloballyL(packageName); } - pkgState.hibernated = isHibernating; - } catch (RemoteException e) { - throw new IllegalStateException( - "Failed to hibernate due to manager not being available", e); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); - Binder.restoreCallingIdentity(caller); } + } + } - // TODO: Support package level hibernation when package is hibernating for all users + /** + * 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 */); + 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); + } + } + + /** + * 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); + } + + /** * Populates {@link #mUserStates} with the users installed packages. The caller should hold * {@link #mLock}. * @@ -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 setHibernatingGlobally(String packageName, boolean isHibernating) { + mService.setHibernatingGlobally(packageName, isHibernating); } @Override - public void setHibernating(String packageName, int userId, boolean isHibernating) { - mService.setHibernating(packageName, userId, isHibernating); + 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); + } } } } diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationShellCommand.java b/services/core/java/com/android/server/apphibernation/AppHibernationShellCommand.java index 869885e28958..7d6eea25541a 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationShellCommand.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationShellCommand.java @@ -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(""); } } diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java index d0370b6c25e9..45bca6829553 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -64,6 +64,8 @@ public final class AppHibernationServiceTest { private static final int USER_ID_1 = 1; private static final int USER_ID_2 = 2; + private final List<UserInfo> mUserInfos = new ArrayList<>(); + private AppHibernationService mAppHibernationService; private BroadcastReceiver mBroadcastReceiver; @Mock @@ -88,47 +90,42 @@ public final class AppHibernationServiceTest { verify(mContext, times(2)).registerReceiver(mReceiverCaptor.capture(), any()); mBroadcastReceiver = mReceiverCaptor.getValue(); - List<UserInfo> userList = new ArrayList<>(); - userList.add(new UserInfo(USER_ID_1, "user 1", 0 /* flags */)); - doReturn(userList).when(mUserManager).getUsers(); - - List<PackageInfo> userPackages = new ArrayList<>(); - userPackages.add(makePackageInfo(PACKAGE_NAME_1)); - - doReturn(new ParceledListSlice<>(userPackages)).when(mIPackageManager) - .getInstalledPackages(anyInt(), eq(USER_ID_1)); + doReturn(mUserInfos).when(mUserManager).getUsers(); doAnswer(returnsArgAt(2)).when(mIActivityManager).handleIncomingUser(anyInt(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any(), any()); + addUser(USER_ID_1); mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); } @Test - public void testSetHibernating_packageIsHibernating() { + public void testSetHibernatingForUser_packageIsHibernating() throws RemoteException { // WHEN we hibernate a package for a user - mAppHibernationService.setHibernating(PACKAGE_NAME_1, USER_ID_1, true); + mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); // THEN the package is marked hibernating for the user - assertTrue(mAppHibernationService.isHibernating(PACKAGE_NAME_1, USER_ID_1)); + assertTrue(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_1, USER_ID_1)); } @Test - public void testSetHibernating_newPackageAdded_packageIsHibernating() { + public void testSetHibernatingForUser_newPackageAdded_packageIsHibernating() + throws RemoteException { // WHEN a new package is added and it is hibernated Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED, Uri.fromParts(PACKAGE_SCHEME, PACKAGE_NAME_2, null /* fragment */)); intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_1); mBroadcastReceiver.onReceive(mContext, intent); - mAppHibernationService.setHibernating(PACKAGE_NAME_2, USER_ID_1, true); + mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_2, USER_ID_1, true); // THEN the new package is hibernated - assertTrue(mAppHibernationService.isHibernating(PACKAGE_NAME_2, USER_ID_1)); + assertTrue(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_2, USER_ID_1)); } @Test - public void testSetHibernating_newUserAdded_packageIsHibernating() throws RemoteException { + public void testSetHibernatingForUser_newUserAdded_packageIsHibernating() + throws RemoteException { // WHEN a new user is added and a package from the user is hibernated List<PackageInfo> userPackages = new ArrayList<>(); userPackages.add(makePackageInfo(PACKAGE_NAME_1)); @@ -138,16 +135,17 @@ public final class AppHibernationServiceTest { intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_2); mBroadcastReceiver.onReceive(mContext, intent); - mAppHibernationService.setHibernating(PACKAGE_NAME_1, USER_ID_2, true); + mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true); // THEN the new user's package is hibernated - assertTrue(mAppHibernationService.isHibernating(PACKAGE_NAME_1, USER_ID_2)); + assertTrue(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_1, USER_ID_2)); } @Test - public void testIsHibernating_packageReplaced_stillReturnsHibernating() { + public void testIsHibernatingForUser_packageReplaced_stillReturnsHibernating() + throws RemoteException { // GIVEN a package is currently hibernated - mAppHibernationService.setHibernating(PACKAGE_NAME_1, USER_ID_1, true); + mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); // WHEN the package is removed but marked as replacing Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED, @@ -157,7 +155,38 @@ public final class AppHibernationServiceTest { mBroadcastReceiver.onReceive(mContext, intent); // THEN the package is still hibernating - assertTrue(mAppHibernationService.isHibernating(PACKAGE_NAME_1, USER_ID_1)); + assertTrue(mAppHibernationService.isHibernatingForUser(PACKAGE_NAME_1, USER_ID_1)); + } + + @Test + public void testSetHibernatingGlobally_packageIsHibernatingGlobally() throws RemoteException { + // WHEN we hibernate a package + mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true); + + // THEN the package is marked hibernating for the user + assertTrue(mAppHibernationService.isHibernatingGlobally(PACKAGE_NAME_1)); + } + + /** + * Add a mock user with one package. Must be called before + * {@link AppHibernationService#onBootPhase(int)} to work properly. + */ + private void addUser(int userId) throws RemoteException { + addUser(userId, new String[]{PACKAGE_NAME_1}); + } + + /** + * Add a mock user with the packages specified. Must be called before + * {@link AppHibernationService#onBootPhase(int)} to work properly + */ + private void addUser(int userId, String[] packageNames) throws RemoteException { + mUserInfos.add(new UserInfo(userId, "user_" + userId, 0 /* flags */)); + List<PackageInfo> userPackages = new ArrayList<>(); + for (String pkgName : packageNames) { + userPackages.add(makePackageInfo(pkgName)); + } + doReturn(new ParceledListSlice<>(userPackages)).when(mIPackageManager) + .getInstalledPackages(anyInt(), eq(userId)); } private static PackageInfo makePackageInfo(String packageName) { |