All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.apphibernation.AppHibernationService Maven / Gradle / Ivy

/*
 * 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.StorageStats;
import android.app.usage.StorageStatsManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
import android.apphibernation.HibernationStats;
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.IOException;
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;
    private final StorageStatsManager mStorageStatsManager;

    @GuardedBy("mLock")
    private final SparseArray> mUserStates = new SparseArray<>();
    private final SparseArray> mUserDiskStores =
            new SparseArray<>();
    @GuardedBy("mLock")
    private final Map mGlobalHibernationStates = new ArrayMap<>();
    private final HibernationStateDiskStore mGlobalLevelHibernationDiskStore;
    private final Injector mInjector;
    private final Executor mBackgroundExecutor;
    private final boolean mOatArtifactDeletionEnabled;

    @VisibleForTesting
    public static boolean sIsServiceEnabled;

    /**
     * Initializes the system service.
     * 

* Subclasses must define a single argument constructor that accepts the context * and passes it to super. *

* * @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(); mStorageStatsManager = injector.getStorageStatsManager(); 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 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 (!sIsServiceEnabled) { return false; } getContext().enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_APP_HIBERNATION, "Caller did not have permission while calling " + methodName); userId = handleIncomingUser(userId, methodName); synchronized (mLock) { // Don't log as this method can be called before user states exist as part of the // force-stop check. if (!checkUserStatesExist(userId, methodName, /* shouldLog= */ false)) { return false; } final Map packageStates = mUserStates.get(userId); final UserLevelState pkgState = packageStates.get(packageName); if (pkgState == null || !mPackageManagerInternal.canQueryPackage( Binder.getCallingUid(), packageName)) { 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 (!sIsServiceEnabled) { 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 || !mPackageManagerInternal.canQueryPackage( Binder.getCallingUid(), packageName)) { // 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 (!sIsServiceEnabled) { return; } getContext().enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_APP_HIBERNATION, "Caller does not have MANAGE_APP_HIBERNATION permission."); final int realUserId = handleIncomingUser(userId, methodName); synchronized (mLock) { if (!checkUserStatesExist(realUserId, methodName, /* shouldLog= */ true)) { return; } final Map packageStates = mUserStates.get(realUserId); final UserLevelState pkgState = packageStates.get(packageName); if (pkgState == null || !mPackageManagerInternal.canQueryPackage( Binder.getCallingUid(), packageName)) { Slog.e(TAG, TextUtils.formatSimple("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, pkgState)); } 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 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 (!sIsServiceEnabled) { 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 || !mPackageManagerInternal.canQueryPackage( Binder.getCallingUid(), packageName)) { Slog.e(TAG, TextUtils.formatSimple( "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 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 getHibernatingPackagesForUser(int userId) { ArrayList hibernatingPackages = new ArrayList<>(); String methodName = "getHibernatingPackagesForUser"; if (!sIsServiceEnabled) { return hibernatingPackages; } getContext().enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_APP_HIBERNATION, "Caller does not have MANAGE_APP_HIBERNATION permission."); userId = handleIncomingUser(userId, methodName); synchronized (mLock) { if (!checkUserStatesExist(userId, methodName, /* shouldLog= */ true)) { return hibernatingPackages; } Map userStates = mUserStates.get(userId); for (UserLevelState state : userStates.values()) { String packageName = state.packageName; if (!mPackageManagerInternal.canQueryPackage( Binder.getCallingUid(), packageName)) { // Package is not visible to caller continue; } if (state.hibernated) { hibernatingPackages.add(state.packageName); } } return hibernatingPackages; } } /** * Return the stats from app hibernation for each package provided. * * @param packageNames the set of packages to return stats for. Returns all if null * @return map from package to stats for that package */ public Map getHibernationStatsForUser( @Nullable Set packageNames, int userId) { Map statsMap = new ArrayMap<>(); String methodName = "getHibernationStatsForUser"; if (!sIsServiceEnabled) { return statsMap; } getContext().enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_APP_HIBERNATION, "Caller does not have MANAGE_APP_HIBERNATION permission."); userId = handleIncomingUser(userId, methodName); synchronized (mLock) { if (!checkUserStatesExist(userId, methodName, /* shouldLog= */ true)) { return statsMap; } final Map userPackageStates = mUserStates.get(userId); Set pkgs = packageNames != null ? packageNames : userPackageStates.keySet(); for (String pkgName : pkgs) { if (!mPackageManagerInternal.canQueryPackage(Binder.getCallingUid(), pkgName)) { // Package not visible to caller continue; } if (!mGlobalHibernationStates.containsKey(pkgName) || !userPackageStates.containsKey(pkgName)) { Slog.w(TAG, TextUtils.formatSimple( "No hibernation state associated with package %s user %d. Maybe" + "the package was uninstalled? ", pkgName, userId)); continue; } long diskBytesSaved = mGlobalHibernationStates.get(pkgName).savedByte + userPackageStates.get(pkgName).savedByte; HibernationStats stats = new HibernationStats(diskBytesSaved); statsMap.put(pkgName, stats); } } return statsMap; } /** * 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, UserLevelState state) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackage"); final long caller = Binder.clearCallingIdentity(); try { ApplicationInfo info = mIPackageManager.getApplicationInfo( packageName, PACKAGE_MATCH_FLAGS, userId); StorageStats stats = mStorageStatsManager.queryStatsForPackage( info.storageUuid, packageName, new UserHandle(userId)); mIActivityManager.forceStopPackage(packageName, userId); mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId, null /* observer */); synchronized (mLock) { state.savedByte = stats.getCacheBytes(); } } catch (RemoteException e) { throw new IllegalStateException( "Failed to hibernate due to manager not being available", e); } catch (PackageManager.NameNotFoundException e) { Slog.e(TAG, "Package name not found when querying storage stats", e); } catch (IOException e) { Slog.e(TAG, "Storage device not found", 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 diskStates) { List packages; try { packages = mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId).getList(); } catch (RemoteException e) { throw new IllegalStateException("Package manager not available", e); } Map 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 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, TextUtils.formatSimple( "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 diskStates) { List 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 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, TextUtils.formatSimple( "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 diskStore = mInjector.getUserLevelDiskStore(userId); mUserDiskStores.put(userId, diskStore); mBackgroundExecutor.execute(() -> { List 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(); Slog.d(TAG, "App hibernation changed to enabled=" + sIsServiceEnabled); 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(); } } /** * Check that user states exist. * * @param userId user to check * @param methodName method name that is calling. Used for logging purposes. * @param shouldLog whether we should log why the user state doesn't exist * @return true if user states exist */ @GuardedBy("mLock") private boolean checkUserStatesExist(int userId, String methodName, boolean shouldLog) { if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { if (shouldLog) { Slog.w(TAG, TextUtils.formatSimple( "Attempt to call %s on stopped or nonexistent user %d", methodName, userId)); } return false; } if (!mUserStates.contains(userId)) { if (shouldLog) { Slog.w(TAG, TextUtils.formatSimple( "Attempt to call %s before states have been read from disk", methodName)); } return false; } return true; } 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 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 getHibernatingPackagesForUser(int userId) { return mService.getHibernatingPackagesForUser(userId); } @Override public Map getHibernationStatsForUser( @Nullable List packageNames, int userId) { Set pkgsSet = packageNames != null ? new ArraySet<>(packageNames) : null; return mService.getHibernationStatsForUser(pkgsSet, 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(); StorageStatsManager getStorageStatsManager(); Executor getBackgroundExecutor(); UsageStatsManagerInternal getUsageStatsManagerInternal(); HibernationStateDiskStore getGlobalLevelDiskStore(); HibernationStateDiskStore 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 StorageStatsManager getStorageStatsManager() { return mContext.getSystemService(StorageStatsManager.class); } @Override public Executor getBackgroundExecutor() { return mScheduledExecutorService; } @Override public UsageStatsManagerInternal getUsageStatsManagerInternal() { return LocalServices.getService(UsageStatsManagerInternal.class); } @Override public HibernationStateDiskStore getGlobalLevelDiskStore() { File dir = new File(Environment.getDataSystemDirectory(), HIBERNATION_DIR_NAME); return new HibernationStateDiskStore<>( dir, new GlobalLevelHibernationProto(), mScheduledExecutorService); } @Override public HibernationStateDiskStore 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 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 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; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy