blob: 28a919115f56cfb4bdd4cbe4ac3c2ccf44bfd06a [file] [log] [blame]
/*
* Copyright (C) 2021 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.server.apphibernation;
import static android.app.AppOpsManager.OP_NONE;
import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.ACTION_PACKAGE_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_ANY_USER;
import static android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION;
import static com.android.server.apphibernation.AppHibernationConstants.KEY_APP_HIBERNATION_ENABLED;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.IActivityManager;
import android.app.StatsManager;
import android.app.StatsManager.StatsPullAtomCallback;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
import android.apphibernation.IAppHibernationService;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.Environment;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.util.StatsEvent;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
/**
* System service that manages app hibernation state, a state apps can enter that means they are
* not being actively used and can be optimized for storage. The actual policy for determining
* if an app should hibernate is managed by PermissionController code.
*/
public final class AppHibernationService extends SystemService {
private static final String TAG = "AppHibernationService";
private static final int PACKAGE_MATCH_FLAGS =
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
| PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS;
/**
* Lock for accessing any in-memory hibernation state
*/
private final Object mLock = new Object();
private final Context mContext;
private final IPackageManager mIPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
private final IActivityManager mIActivityManager;
private final UserManager mUserManager;
@GuardedBy("mLock")
private final SparseArray<Map<String, UserLevelState>> mUserStates = new SparseArray<>();
private final SparseArray<HibernationStateDiskStore<UserLevelState>> mUserDiskStores =
new SparseArray<>();
@GuardedBy("mLock")
private final Map<String, GlobalLevelState> mGlobalHibernationStates = new ArrayMap<>();
private final HibernationStateDiskStore<GlobalLevelState> mGlobalLevelHibernationDiskStore;
private final Injector mInjector;
private final Executor mBackgroundExecutor;
private final boolean mOatArtifactDeletionEnabled;
@VisibleForTesting
public static boolean sIsServiceEnabled;
/**
* Initializes the system service.
* <p>
* Subclasses must define a single argument constructor that accepts the context
* and passes it to super.
* </p>
*
* @param context The system server context.
*/
public AppHibernationService(@NonNull Context context) {
this(new InjectorImpl(context));
}
@VisibleForTesting
AppHibernationService(@NonNull Injector injector) {
super(injector.getContext());
mContext = injector.getContext();
mIPackageManager = injector.getPackageManager();
mPackageManagerInternal = injector.getPackageManagerInternal();
mIActivityManager = injector.getActivityManager();
mUserManager = injector.getUserManager();
mGlobalLevelHibernationDiskStore = injector.getGlobalLevelDiskStore();
mBackgroundExecutor = injector.getBackgroundExecutor();
mOatArtifactDeletionEnabled = injector.isOatArtifactDeletionEnabled();
mInjector = injector;
final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_PACKAGE_ADDED);
intentFilter.addAction(ACTION_PACKAGE_REMOVED);
intentFilter.addDataScheme("package");
userAllContext.registerReceiver(mBroadcastReceiver, intentFilter);
LocalServices.addService(AppHibernationManagerInternal.class, mLocalService);
mInjector.getUsageStatsManagerInternal().registerListener(mUsageEventListener);
}
@Override
public void onStart() {
publishBinderService(Context.APP_HIBERNATION_SERVICE, mServiceStub);
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_BOOT_COMPLETED) {
mBackgroundExecutor.execute(() -> {
List<GlobalLevelState> states =
mGlobalLevelHibernationDiskStore.readHibernationStates();
synchronized (mLock) {
initializeGlobalHibernationStates(states);
}
});
}
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
sIsServiceEnabled = isDeviceConfigAppHibernationEnabled();
DeviceConfig.addOnPropertiesChangedListener(
NAMESPACE_APP_HIBERNATION,
ActivityThread.currentApplication().getMainExecutor(),
this::onDeviceConfigChanged);
final StatsManager statsManager = getContext().getSystemService(StatsManager.class);
final StatsPullAtomCallbackImpl pullAtomCallback = new StatsPullAtomCallbackImpl();
statsManager.setPullAtomCallback(
FrameworkStatsLog.USER_LEVEL_HIBERNATED_APPS,
/* metadata */ null, // use default PullAtomMetadata values
mBackgroundExecutor,
pullAtomCallback);
statsManager.setPullAtomCallback(
FrameworkStatsLog.GLOBAL_HIBERNATED_APPS,
/* metadata */ null, // use default PullAtomMetadata values
mBackgroundExecutor,
pullAtomCallback);
}
}
/**
* Whether global hibernation should delete ART ahead-of-time compilation artifacts and prevent
* package manager from re-optimizing the APK.
*/
private boolean isOatArtifactDeletionEnabled() {
return mOatArtifactDeletionEnabled;
}
/**
* Whether a package is hibernating for a given user.
*
* @param packageName the package to check
* @param userId the user to check
* @return true if package is hibernating for the user
*/
boolean isHibernatingForUser(String packageName, int userId) {
String methodName = "isHibernatingForUser";
if (!checkHibernationEnabled(methodName)) {
return false;
}
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_APP_HIBERNATION,
"Caller does not have MANAGE_APP_HIBERNATION permission.");
userId = handleIncomingUser(userId, methodName);
if (!checkUserStatesExist(userId, methodName)) {
return false;
}
synchronized (mLock) {
final Map<String, UserLevelState> packageStates = mUserStates.get(userId);
final UserLevelState pkgState = packageStates.get(packageName);
if (pkgState == null) {
Slog.e(TAG, String.format("Package %s is not installed for user %s",
packageName, userId));
return false;
}
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) {
if (!checkHibernationEnabled("isHibernatingGlobally")) {
return false;
}
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_APP_HIBERNATION,
"Caller does not have MANAGE_APP_HIBERNATION permission.");
synchronized (mLock) {
GlobalLevelState state = mGlobalHibernationStates.get(packageName);
if (state == null) {
// This API can be legitimately called before installation finishes as part of
// dex optimization, so we just return false here.
return false;
}
return state.hibernated;
}
}
/**
* Set whether the package is hibernating for the given user.
*
* @param packageName package to modify state
* @param userId user
* @param isHibernating new hibernation state
*/
void setHibernatingForUser(String packageName, int userId, boolean isHibernating) {
String methodName = "setHibernatingForUser";
if (!checkHibernationEnabled(methodName)) {
return;
}
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_APP_HIBERNATION,
"Caller does not have MANAGE_APP_HIBERNATION permission.");
final int realUserId = handleIncomingUser(userId, methodName);
if (!checkUserStatesExist(realUserId, methodName)) {
return;
}
synchronized (mLock) {
final Map<String, UserLevelState> packageStates = mUserStates.get(realUserId);
final UserLevelState pkgState = packageStates.get(packageName);
if (pkgState == null) {
Slog.e(TAG, String.format("Package %s is not installed for user %s",
packageName, realUserId));
return;
}
if (pkgState.hibernated == isHibernating) {
return;
}
pkgState.hibernated = isHibernating;
if (isHibernating) {
mBackgroundExecutor.execute(() -> hibernatePackageForUser(packageName, realUserId));
} else {
mBackgroundExecutor.execute(
() -> unhibernatePackageForUser(packageName, realUserId));
pkgState.lastUnhibernatedMs = System.currentTimeMillis();
}
final UserLevelState stateSnapshot = new UserLevelState(pkgState);
final int userIdSnapshot = realUserId;
mBackgroundExecutor.execute(() -> {
FrameworkStatsLog.write(
FrameworkStatsLog.USER_LEVEL_HIBERNATION_STATE_CHANGED,
stateSnapshot.packageName,
userIdSnapshot,
stateSnapshot.hibernated);
});
List<UserLevelState> states = new ArrayList<>(mUserStates.get(realUserId).values());
mUserDiskStores.get(realUserId).scheduleWriteHibernationStates(states);
}
}
/**
* 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 (!checkHibernationEnabled("setHibernatingGlobally")) {
return;
}
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_APP_HIBERNATION,
"Caller does not have MANAGE_APP_HIBERNATION permission.");
synchronized (mLock) {
GlobalLevelState state = mGlobalHibernationStates.get(packageName);
if (state == null) {
Slog.e(TAG, String.format("Package %s is not installed for any user", packageName));
return;
}
if (state.hibernated != isHibernating) {
state.hibernated = isHibernating;
if (isHibernating) {
mBackgroundExecutor.execute(() -> hibernatePackageGlobally(packageName, state));
} else {
state.savedByte = 0;
state.lastUnhibernatedMs = System.currentTimeMillis();
}
List<GlobalLevelState> states = new ArrayList<>(mGlobalHibernationStates.values());
mGlobalLevelHibernationDiskStore.scheduleWriteHibernationStates(states);
}
}
}
/**
* Get the hibernating packages for the given user. This is equivalent to the list of
* packages for the user that return true for {@link #isHibernatingForUser}.
*/
@NonNull List<String> getHibernatingPackagesForUser(int userId) {
ArrayList<String> hibernatingPackages = new ArrayList<>();
String methodName = "getHibernatingPackagesForUser";
if (!checkHibernationEnabled(methodName)) {
return hibernatingPackages;
}
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_APP_HIBERNATION,
"Caller does not have MANAGE_APP_HIBERNATION permission.");
userId = handleIncomingUser(userId, methodName);
if (!checkUserStatesExist(userId, methodName)) {
return hibernatingPackages;
}
synchronized (mLock) {
Map<String, UserLevelState> userStates = mUserStates.get(userId);
for (UserLevelState state : userStates.values()) {
if (state.hibernated) {
hibernatingPackages.add(state.packageName);
}
}
return hibernatingPackages;
}
}
/**
* Put an app into hibernation for a given user, allowing user-level optimizations to occur. Do
* not hold {@link #mLock} while calling this to avoid deadlock scenarios.
*/
private void hibernatePackageForUser(@NonNull String packageName, int userId) {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackage");
final long caller = Binder.clearCallingIdentity();
try {
mIActivityManager.forceStopPackage(packageName, userId);
mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId,
null /* observer */);
} 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. Do not hold {@link #mLock} while calling
* this.
*/
private void unhibernatePackageForUser(@NonNull String packageName, int userId) {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackage");
final long caller = Binder.clearCallingIdentity();
// Deliver LOCKED_BOOT_COMPLETE AND BOOT_COMPLETE broadcast so app can re-register
// their alarms/jobs/etc.
try {
Intent lockedBcIntent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
.setPackage(packageName);
final String[] requiredPermissions = {Manifest.permission.RECEIVE_BOOT_COMPLETED};
mIActivityManager.broadcastIntentWithFeature(
null /* caller */,
null /* callingFeatureId */,
lockedBcIntent,
null /* resolvedType */,
null /* resultTo */,
Activity.RESULT_OK,
null /* resultData */,
null /* resultExtras */,
requiredPermissions,
null /* excludedPermissions */,
null /* excludedPackages */,
OP_NONE,
null /* bOptions */,
false /* serialized */,
false /* sticky */,
userId);
Intent bcIntent = new Intent(Intent.ACTION_BOOT_COMPLETED).setPackage(packageName);
mIActivityManager.broadcastIntentWithFeature(
null /* caller */,
null /* callingFeatureId */,
bcIntent,
null /* resolvedType */,
null /* resultTo */,
Activity.RESULT_OK,
null /* resultData */,
null /* resultExtras */,
requiredPermissions,
null /* excludedPermissions */,
null /* excludedPackages */,
OP_NONE,
null /* bOptions */,
false /* serialized */,
false /* sticky */,
userId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} 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. Do
* not hold {@link #mLock} while calling this.
*/
private void hibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackageGlobally");
long savedBytes = 0;
if (mOatArtifactDeletionEnabled) {
savedBytes = Math.max(
mPackageManagerInternal.deleteOatArtifactsOfPackage(packageName),
0);
}
synchronized (mLock) {
state.savedByte = savedBytes;
}
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
/**
* Initializes in-memory store of user-level hibernation states for the given user
*
* @param userId user id to add installed packages for
* @param diskStates states pulled from disk, if available
*/
@GuardedBy("mLock")
private void initializeUserHibernationStates(int userId,
@Nullable List<UserLevelState> diskStates) {
List<PackageInfo> packages;
try {
packages = mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId).getList();
} catch (RemoteException e) {
throw new IllegalStateException("Package manager not available", e);
}
Map<String, UserLevelState> userLevelStates = new ArrayMap<>();
for (int i = 0, size = packages.size(); i < size; i++) {
String packageName = packages.get(i).packageName;
UserLevelState state = new UserLevelState();
state.packageName = packageName;
userLevelStates.put(packageName, state);
}
if (diskStates != null) {
Map<String, PackageInfo> installedPackages = new ArrayMap<>();
for (int i = 0, size = packages.size(); i < size; i++) {
installedPackages.put(packages.get(i).packageName, packages.get(i));
}
for (int i = 0, size = diskStates.size(); i < size; i++) {
String packageName = diskStates.get(i).packageName;
PackageInfo pkgInfo = installedPackages.get(packageName);
UserLevelState currentState = diskStates.get(i);
if (pkgInfo == null) {
Slog.w(TAG, String.format(
"No hibernation state associated with package %s user %d. Maybe"
+ "the package was uninstalled? ", packageName, userId));
continue;
}
if (pkgInfo.applicationInfo != null
&& (pkgInfo.applicationInfo.flags &= ApplicationInfo.FLAG_STOPPED) == 0
&& currentState.hibernated) {
// App is not stopped but is hibernated. Disk state is stale, so unhibernate
// the app.
currentState.hibernated = false;
currentState.lastUnhibernatedMs = System.currentTimeMillis();
}
userLevelStates.put(packageName, currentState);
}
}
mUserStates.put(userId, userLevelStates);
}
/**
* Initialize in-memory store of global level hibernation states.
*
* @param diskStates global level hibernation states pulled from disk, if available
*/
@GuardedBy("mLock")
private void initializeGlobalHibernationStates(@Nullable List<GlobalLevelState> diskStates) {
List<PackageInfo> packages;
try {
packages = mIPackageManager.getInstalledPackages(
PACKAGE_MATCH_FLAGS | MATCH_ANY_USER, 0 /* userId */).getList();
} catch (RemoteException e) {
throw new IllegalStateException("Package manager not available", e);
}
for (int i = 0, size = packages.size(); i < size; i++) {
String packageName = packages.get(i).packageName;
GlobalLevelState state = new GlobalLevelState();
state.packageName = packageName;
mGlobalHibernationStates.put(packageName, state);
}
if (diskStates != null) {
Set<String> installedPackages = new ArraySet<>();
for (int i = 0, size = packages.size(); i < size; i++) {
installedPackages.add(packages.get(i).packageName);
}
for (int i = 0, size = diskStates.size(); i < size; i++) {
GlobalLevelState state = diskStates.get(i);
if (!installedPackages.contains(state.packageName)) {
Slog.w(TAG, String.format(
"No hibernation state associated with package %s. Maybe the "
+ "package was uninstalled? ", state.packageName));
continue;
}
mGlobalHibernationStates.put(state.packageName, state);
}
}
}
@Override
public void onUserUnlocking(@NonNull TargetUser user) {
int userId = user.getUserIdentifier();
HibernationStateDiskStore<UserLevelState> diskStore =
mInjector.getUserLevelDiskStore(userId);
mUserDiskStores.put(userId, diskStore);
mBackgroundExecutor.execute(() -> {
List<UserLevelState> storedStates = diskStore.readHibernationStates();
synchronized (mLock) {
// Ensure user hasn't stopped in the time to execute.
if (mUserManager.isUserUnlockingOrUnlocked(userId)) {
initializeUserHibernationStates(userId, storedStates);
// Globally unhibernate a package if the unlocked user does not have it
// hibernated.
for (UserLevelState userState : mUserStates.get(userId).values()) {
String pkgName = userState.packageName;
GlobalLevelState globalState = mGlobalHibernationStates.get(pkgName);
if (globalState.hibernated && !userState.hibernated) {
setHibernatingGlobally(pkgName, false);
}
}
}
}
});
}
@Override
public void onUserStopping(@NonNull TargetUser user) {
int userId = user.getUserIdentifier();
// TODO: Flush any scheduled writes to disk immediately on user stopping / power off.
synchronized (mLock) {
mUserDiskStores.remove(userId);
mUserStates.remove(userId);
}
}
private void onPackageAdded(@NonNull String packageName, int userId) {
synchronized (mLock) {
if (!mUserStates.contains(userId)) {
return;
}
UserLevelState userState = new UserLevelState();
userState.packageName = packageName;
mUserStates.get(userId).put(packageName, userState);
if (!mGlobalHibernationStates.containsKey(packageName)) {
GlobalLevelState globalState = new GlobalLevelState();
globalState.packageName = packageName;
mGlobalHibernationStates.put(packageName, globalState);
}
}
}
private void onPackageRemoved(@NonNull String packageName, int userId) {
synchronized (mLock) {
if (!mUserStates.contains(userId)) {
return;
}
mUserStates.get(userId).remove(packageName);
}
}
private void onPackageRemovedForAllUsers(@NonNull String packageName) {
synchronized (mLock) {
mGlobalHibernationStates.remove(packageName);
}
}
private void onDeviceConfigChanged(Properties properties) {
for (String key : properties.getKeyset()) {
if (TextUtils.equals(KEY_APP_HIBERNATION_ENABLED, key)) {
sIsServiceEnabled = isDeviceConfigAppHibernationEnabled();
break;
}
}
}
/**
* Private helper method to get the real user id and enforce permission checks.
*
* @param userId user id to handle
* @param name name to use for exceptions
* @return real user id
*/
private int handleIncomingUser(int userId, @NonNull String name) {
int callingUid = Binder.getCallingUid();
try {
return mIActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
false /* allowAll */, true /* requireFull */, name, null);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
private boolean checkUserStatesExist(int userId, String methodName) {
if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
Slog.e(TAG, String.format(
"Attempt to call %s on stopped or nonexistent user %d", methodName, userId));
return false;
}
if (!mUserStates.contains(userId)) {
Slog.w(TAG, String.format(
"Attempt to call %s before states have been read from disk", methodName));
return false;
}
return true;
}
private boolean checkHibernationEnabled(String methodName) {
if (!sIsServiceEnabled) {
Slog.w(TAG, String.format("Attempted to call %s on unsupported device.", methodName));
}
return sIsServiceEnabled;
}
private void dump(PrintWriter pw) {
// Check usage stats permission since hibernation indirectly informs usage.
if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
IndentingPrintWriter idpw = new IndentingPrintWriter(pw, " ");
synchronized (mLock) {
final int userCount = mUserStates.size();
for (int i = 0; i < userCount; i++) {
final int userId = mUserStates.keyAt(i);
idpw.print("User Level Hibernation States, ");
idpw.printPair("user", userId);
idpw.println();
Map<String, UserLevelState> stateMap = mUserStates.get(userId);
idpw.increaseIndent();
for (UserLevelState state : stateMap.values()) {
idpw.print(state);
idpw.println();
}
idpw.decreaseIndent();
}
idpw.println();
idpw.print("Global Level Hibernation States");
idpw.println();
for (GlobalLevelState state : mGlobalHibernationStates.values()) {
idpw.print(state);
idpw.println();
}
}
}
private final AppHibernationManagerInternal mLocalService = new LocalService(this);
private static final class LocalService extends AppHibernationManagerInternal {
private final AppHibernationService mService;
LocalService(AppHibernationService service) {
mService = service;
}
@Override
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 boolean isHibernatingGlobally(String packageName) {
return mService.isHibernatingGlobally(packageName);
}
@Override
public boolean isOatArtifactDeletionEnabled() {
return mService.isOatArtifactDeletionEnabled();
}
}
private final AppHibernationServiceStub mServiceStub = new AppHibernationServiceStub(this);
static final class AppHibernationServiceStub extends IAppHibernationService.Stub {
final AppHibernationService mService;
AppHibernationServiceStub(AppHibernationService service) {
mService = service;
}
@Override
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 boolean isHibernatingGlobally(String packageName) {
return mService.isHibernatingGlobally(packageName);
}
@Override
public List<String> getHibernatingPackagesForUser(int userId) {
return mService.getHibernatingPackagesForUser(userId);
}
@Override
public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
@Nullable FileDescriptor err, @NonNull String[] args,
@Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver) {
new AppHibernationShellCommand(mService).exec(this, in, out, err, args, callback,
resultReceiver);
}
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {
mService.dump(fout);
}
}
// Broadcast receiver for package add/removal events
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
if (userId == UserHandle.USER_NULL) {
return;
}
final String action = intent.getAction();
if (ACTION_PACKAGE_ADDED.equals(action) || ACTION_PACKAGE_REMOVED.equals(action)) {
final String packageName = intent.getData().getSchemeSpecificPart();
if (intent.getBooleanExtra(EXTRA_REPLACING, false)) {
// Package removal/add is part of an update, so no need to modify package state.
return;
}
if (ACTION_PACKAGE_ADDED.equals(action)) {
onPackageAdded(packageName, userId);
} else if (ACTION_PACKAGE_REMOVED.equals(action)) {
onPackageRemoved(packageName, userId);
if (intent.getBooleanExtra(EXTRA_REMOVED_FOR_ALL_USERS, false)) {
onPackageRemovedForAllUsers(packageName);
}
}
}
}
};
private final UsageEventListener mUsageEventListener = (userId, event) -> {
if (!isAppHibernationEnabled()) {
return;
}
final int eventType = event.mEventType;
if (eventType == UsageEvents.Event.USER_INTERACTION
|| eventType == UsageEvents.Event.ACTIVITY_RESUMED
|| eventType == UsageEvents.Event.APP_COMPONENT_USED) {
final String pkgName = event.mPackage;
setHibernatingForUser(pkgName, userId, false);
setHibernatingGlobally(pkgName, false);
}
};
/**
* Whether app hibernation is enabled on this device.
*
* @return true if enabled, false otherwise
*/
public static boolean isAppHibernationEnabled() {
return sIsServiceEnabled;
}
private static boolean isDeviceConfigAppHibernationEnabled() {
return DeviceConfig.getBoolean(
NAMESPACE_APP_HIBERNATION,
KEY_APP_HIBERNATION_ENABLED,
true /* defaultValue */);
}
/**
* Dependency injector for {@link #AppHibernationService)}.
*/
interface Injector {
Context getContext();
IPackageManager getPackageManager();
PackageManagerInternal getPackageManagerInternal();
IActivityManager getActivityManager();
UserManager getUserManager();
Executor getBackgroundExecutor();
UsageStatsManagerInternal getUsageStatsManagerInternal();
HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore();
HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId);
boolean isOatArtifactDeletionEnabled();
}
private static final class InjectorImpl implements Injector {
private static final String HIBERNATION_DIR_NAME = "hibernation";
private final Context mContext;
private final ScheduledExecutorService mScheduledExecutorService;
private final UserLevelHibernationProto mUserLevelHibernationProto;
InjectorImpl(Context context) {
mContext = context;
mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
mUserLevelHibernationProto = new UserLevelHibernationProto();
}
@Override
public Context getContext() {
return mContext;
}
@Override
public IPackageManager getPackageManager() {
return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
}
@Override
public PackageManagerInternal getPackageManagerInternal() {
return LocalServices.getService(PackageManagerInternal.class);
}
@Override
public IActivityManager getActivityManager() {
return ActivityManager.getService();
}
@Override
public UserManager getUserManager() {
return mContext.getSystemService(UserManager.class);
}
@Override
public Executor getBackgroundExecutor() {
return mScheduledExecutorService;
}
@Override
public UsageStatsManagerInternal getUsageStatsManagerInternal() {
return LocalServices.getService(UsageStatsManagerInternal.class);
}
@Override
public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() {
File dir = new File(Environment.getDataSystemDirectory(), HIBERNATION_DIR_NAME);
return new HibernationStateDiskStore<>(
dir, new GlobalLevelHibernationProto(), mScheduledExecutorService);
}
@Override
public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) {
File dir = new File(Environment.getDataSystemCeDirectory(userId), HIBERNATION_DIR_NAME);
return new HibernationStateDiskStore<>(
dir, mUserLevelHibernationProto, mScheduledExecutorService);
}
@Override
public boolean isOatArtifactDeletionEnabled() {
return mContext.getResources().getBoolean(
com.android.internal.R.bool.config_hibernationDeletesOatArtifactsEnabled);
}
}
private final class StatsPullAtomCallbackImpl implements StatsPullAtomCallback {
private static final int MEGABYTE_IN_BYTES = 1000000;
@Override
public int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) {
if (!isAppHibernationEnabled()
&& (atomTag == FrameworkStatsLog.USER_LEVEL_HIBERNATED_APPS
|| atomTag == FrameworkStatsLog.GLOBAL_HIBERNATED_APPS)) {
return StatsManager.PULL_SUCCESS;
}
switch (atomTag) {
case FrameworkStatsLog.USER_LEVEL_HIBERNATED_APPS:
List<UserInfo> userInfos = mUserManager.getAliveUsers();
final int numUsers = userInfos.size();
for (int i = 0; i < numUsers; ++i) {
final int userId = userInfos.get(i).id;
if (mUserManager.isUserUnlockingOrUnlocked(userId)) {
data.add(
FrameworkStatsLog.buildStatsEvent(
atomTag,
getHibernatingPackagesForUser(userId).size(),
userId)
);
}
}
break;
case FrameworkStatsLog.GLOBAL_HIBERNATED_APPS:
int hibernatedAppCount = 0;
long storage_saved_byte = 0;
synchronized (mLock) {
for (GlobalLevelState state : mGlobalHibernationStates.values()) {
if (state.hibernated) {
hibernatedAppCount++;
storage_saved_byte += state.savedByte;
}
}
}
data.add(
FrameworkStatsLog.buildStatsEvent(
atomTag,
hibernatedAppCount,
storage_saved_byte / MEGABYTE_IN_BYTES)
);
break;
default:
return StatsManager.PULL_SKIP;
}
return StatsManager.PULL_SUCCESS;
}
}
}