Merge changes I8fffd952,Ief6690bb am: b4203793bf
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1550435 MUST ONLY BE SUBMITTED BY AUTOMERGER Change-Id: I7a3d4db0692b9266992386916bbae57b360e703f
This commit is contained in:
@@ -16,23 +16,64 @@
|
|||||||
|
|
||||||
package com.android.server.apphibernation;
|
package com.android.server.apphibernation;
|
||||||
|
|
||||||
|
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_REPLACING;
|
||||||
|
import static android.content.pm.PackageManager.MATCH_ALL;
|
||||||
import static android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION;
|
import static android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.Nullable;
|
||||||
|
import android.app.ActivityManager;
|
||||||
|
import android.app.IActivityManager;
|
||||||
import android.apphibernation.IAppHibernationService;
|
import android.apphibernation.IAppHibernationService;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.pm.IPackageManager;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.UserInfo;
|
||||||
|
import android.os.Binder;
|
||||||
|
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;
|
||||||
|
import android.util.ArrayMap;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.server.SystemService;
|
import com.android.server.SystemService;
|
||||||
|
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* System service that manages app hibernation state, a state apps can enter that means they are
|
* 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
|
* 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.
|
* if an app should hibernate is managed by PermissionController code.
|
||||||
*/
|
*/
|
||||||
public final class AppHibernationService extends SystemService {
|
public final class AppHibernationService extends SystemService {
|
||||||
|
private static final String TAG = "AppHibernationService";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock for accessing any in-memory hibernation state
|
||||||
|
*/
|
||||||
|
private final Object mLock = new Object();
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
|
private final IPackageManager mIPackageManager;
|
||||||
|
private final IActivityManager mIActivityManager;
|
||||||
|
private final UserManager mUserManager;
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private final SparseArray<Map<String, UserPackageState>> mUserStates = new SparseArray<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the system service.
|
* Initializes the system service.
|
||||||
@@ -44,8 +85,32 @@ public final class AppHibernationService extends SystemService {
|
|||||||
* @param context The system server context.
|
* @param context The system server context.
|
||||||
*/
|
*/
|
||||||
public AppHibernationService(@NonNull Context context) {
|
public AppHibernationService(@NonNull Context context) {
|
||||||
|
this(context, IPackageManager.Stub.asInterface(ServiceManager.getService("package")),
|
||||||
|
ActivityManager.getService(),
|
||||||
|
context.getSystemService(UserManager.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
AppHibernationService(@NonNull Context context, IPackageManager packageManager,
|
||||||
|
IActivityManager activityManager, UserManager userManager) {
|
||||||
super(context);
|
super(context);
|
||||||
mContext = context;
|
mContext = context;
|
||||||
|
mIPackageManager = packageManager;
|
||||||
|
mIActivityManager = activityManager;
|
||||||
|
mUserManager = userManager;
|
||||||
|
|
||||||
|
final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
|
||||||
|
|
||||||
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
|
intentFilter.addAction(ACTION_USER_ADDED);
|
||||||
|
intentFilter.addAction(ACTION_USER_REMOVED);
|
||||||
|
userAllContext.registerReceiver(mBroadcastReceiver, intentFilter);
|
||||||
|
|
||||||
|
intentFilter = new IntentFilter();
|
||||||
|
intentFilter.addAction(ACTION_PACKAGE_ADDED);
|
||||||
|
intentFilter.addAction(ACTION_PACKAGE_REMOVED);
|
||||||
|
intentFilter.addDataScheme("package");
|
||||||
|
userAllContext.registerReceiver(mBroadcastReceiver, intentFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -53,6 +118,19 @@ public final class AppHibernationService extends SystemService {
|
|||||||
publishBinderService(Context.APP_HIBERNATION_SERVICE, mServiceStub);
|
publishBinderService(Context.APP_HIBERNATION_SERVICE, mServiceStub);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBootPhase(int phase) {
|
||||||
|
if (phase == PHASE_BOOT_COMPLETED) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
final List<UserInfo> users = mUserManager.getUsers();
|
||||||
|
// TODO: Pull from persistent disk storage. For now, just make from scratch.
|
||||||
|
for (UserInfo user : users) {
|
||||||
|
addUserPackageStatesL(user.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether a package is hibernating for a given user.
|
* Whether a package is hibernating for a given user.
|
||||||
*
|
*
|
||||||
@@ -61,8 +139,20 @@ public final class AppHibernationService extends SystemService {
|
|||||||
* @return true if package is hibernating for the user
|
* @return true if package is hibernating for the user
|
||||||
*/
|
*/
|
||||||
public boolean isHibernating(String packageName, int userId) {
|
public boolean isHibernating(String packageName, int userId) {
|
||||||
// Stub
|
userId = handleIncomingUser(userId, "isHibernating");
|
||||||
throw new UnsupportedOperationException("Hibernation state management not implemented yet");
|
synchronized (mLock) {
|
||||||
|
final Map<String, UserPackageState> packageStates = mUserStates.get(userId);
|
||||||
|
if (packageStates == null) {
|
||||||
|
throw new IllegalArgumentException("No user associated with user id " + userId);
|
||||||
|
}
|
||||||
|
final UserPackageState pkgState = packageStates.get(packageName);
|
||||||
|
if (pkgState == null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Package %s is not installed for user %s",
|
||||||
|
packageName, userId));
|
||||||
|
}
|
||||||
|
return pkgState != null ? pkgState.hibernated : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,8 +163,108 @@ public final class AppHibernationService extends SystemService {
|
|||||||
* @param isHibernating new hibernation state
|
* @param isHibernating new hibernation state
|
||||||
*/
|
*/
|
||||||
public void setHibernating(String packageName, int userId, boolean isHibernating) {
|
public void setHibernating(String packageName, int userId, boolean isHibernating) {
|
||||||
// Stub
|
userId = handleIncomingUser(userId, "setHibernating");
|
||||||
throw new UnsupportedOperationException("Hibernation state management not implemented yet");
|
synchronized (mLock) {
|
||||||
|
if (!mUserStates.contains(userId)) {
|
||||||
|
throw new IllegalArgumentException("No user associated with user id " + userId);
|
||||||
|
}
|
||||||
|
Map<String, UserPackageState> packageStates = mUserStates.get(userId);
|
||||||
|
UserPackageState pkgState = packageStates.get(packageName);
|
||||||
|
if (pkgState == null) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Package %s is not installed for user %s",
|
||||||
|
packageName, userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pkgState.hibernated == isHibernating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final long caller = Binder.clearCallingIdentity();
|
||||||
|
try {
|
||||||
|
if (isHibernating) {
|
||||||
|
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackage");
|
||||||
|
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;
|
||||||
|
} 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates {@link #mUserStates} with the users installed packages. The caller should hold
|
||||||
|
* {@link #mLock}.
|
||||||
|
*
|
||||||
|
* @param userId user id to add installed packages for
|
||||||
|
*/
|
||||||
|
private void addUserPackageStatesL(int userId) {
|
||||||
|
Map<String, UserPackageState> packages = new ArrayMap<>();
|
||||||
|
List<PackageInfo> packageList;
|
||||||
|
try {
|
||||||
|
packageList = mIPackageManager.getInstalledPackages(MATCH_ALL, userId).getList();
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
throw new IllegalStateException("Package manager not available.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PackageInfo pkg : packageList) {
|
||||||
|
packages.put(pkg.packageName, new UserPackageState());
|
||||||
|
}
|
||||||
|
mUserStates.put(userId, packages);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onUserAdded(int userId) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
addUserPackageStatesL(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onUserRemoved(int userId) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
mUserStates.remove(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onPackageAdded(@NonNull String packageName, int userId) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
mUserStates.get(userId).put(packageName, new UserPackageState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onPackageRemoved(@NonNull String packageName, int userId) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
mUserStates.get(userId).remove(packageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 final AppHibernationServiceStub mServiceStub = new AppHibernationServiceStub(this);
|
private final AppHibernationServiceStub mServiceStub = new AppHibernationServiceStub(this);
|
||||||
@@ -95,8 +285,48 @@ public final class AppHibernationService extends SystemService {
|
|||||||
public void setHibernating(String packageName, int userId, boolean isHibernating) {
|
public void setHibernating(String packageName, int userId, boolean isHibernating) {
|
||||||
mService.setHibernating(packageName, userId, isHibernating);
|
mService.setHibernating(packageName, userId, isHibernating);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Broadcast receiver for user and 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_USER_ADDED.equals(action)) {
|
||||||
|
onUserAdded(userId);
|
||||||
|
}
|
||||||
|
if (ACTION_USER_REMOVED.equals(action)) {
|
||||||
|
onUserRemoved(userId);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether app hibernation is enabled on this device.
|
* Whether app hibernation is enabled on this device.
|
||||||
*
|
*
|
||||||
@@ -108,4 +338,12 @@ public final class AppHibernationService extends SystemService {
|
|||||||
AppHibernationConstants.KEY_APP_HIBERNATION_ENABLED,
|
AppHibernationConstants.KEY_APP_HIBERNATION_ENABLED,
|
||||||
false /* defaultValue */);
|
false /* defaultValue */);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data class that contains hibernation state info of a package for a user.
|
||||||
|
*/
|
||||||
|
private static final class UserPackageState {
|
||||||
|
public boolean hibernated;
|
||||||
|
// TODO: Track whether hibernation is exempted by the user
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.os.ShellCommand;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shell command implementation for {@link AppHibernationService}.
|
||||||
|
*/
|
||||||
|
final class AppHibernationShellCommand extends ShellCommand {
|
||||||
|
private static final String USER_OPT = "--user";
|
||||||
|
private static final int SUCCESS = 0;
|
||||||
|
private static final int ERROR = -1;
|
||||||
|
private final AppHibernationService mService;
|
||||||
|
|
||||||
|
AppHibernationShellCommand(AppHibernationService service) {
|
||||||
|
mService = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onCommand(String cmd) {
|
||||||
|
if (cmd == null) {
|
||||||
|
return handleDefaultCommands(cmd);
|
||||||
|
}
|
||||||
|
switch (cmd) {
|
||||||
|
case "set-state":
|
||||||
|
return runSetState();
|
||||||
|
case "get-state":
|
||||||
|
return runGetState();
|
||||||
|
default:
|
||||||
|
return handleDefaultCommands(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int runSetState() {
|
||||||
|
int userId = parseUserOption();
|
||||||
|
|
||||||
|
String pkg = getNextArgRequired();
|
||||||
|
if (pkg == null) {
|
||||||
|
getErrPrintWriter().println("Error: no package specified");
|
||||||
|
return ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
String newStateRaw = getNextArgRequired();
|
||||||
|
if (newStateRaw == null) {
|
||||||
|
getErrPrintWriter().println("Error: No state to set specified");
|
||||||
|
return ERROR;
|
||||||
|
}
|
||||||
|
boolean newState = Boolean.parseBoolean(newStateRaw);
|
||||||
|
|
||||||
|
mService.setHibernating(pkg, userId, newState);
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int runGetState() {
|
||||||
|
int userId = parseUserOption();
|
||||||
|
|
||||||
|
String pkg = getNextArgRequired();
|
||||||
|
if (pkg == null) {
|
||||||
|
getErrPrintWriter().println("Error: No package specified");
|
||||||
|
return ERROR;
|
||||||
|
}
|
||||||
|
boolean isHibernating = mService.isHibernating(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();
|
||||||
|
pw.println("App hibernation (app_hibernation) commands: ");
|
||||||
|
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("");
|
||||||
|
pw.println(" get-state [--user USER_ID] PACKAGE");
|
||||||
|
pw.println(" Gets the hibernation state of the package");
|
||||||
|
pw.println("");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.AdditionalAnswers.returnsArgAt;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.internal.verification.VerificationModeFactory.times;
|
||||||
|
|
||||||
|
import android.app.IActivityManager;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.IPackageManager;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.ParceledListSlice;
|
||||||
|
import android.content.pm.UserInfo;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
import android.os.UserManager;
|
||||||
|
|
||||||
|
import androidx.test.filters.SmallTest;
|
||||||
|
|
||||||
|
import com.android.server.SystemService;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link com.android.server.apphibernation.AppHibernationService}
|
||||||
|
*/
|
||||||
|
@SmallTest
|
||||||
|
public final class AppHibernationServiceTest {
|
||||||
|
private static final String PACKAGE_SCHEME = "package";
|
||||||
|
private static final String PACKAGE_NAME_1 = "package1";
|
||||||
|
private static final String PACKAGE_NAME_2 = "package2";
|
||||||
|
private static final int USER_ID_1 = 1;
|
||||||
|
private static final int USER_ID_2 = 2;
|
||||||
|
|
||||||
|
private AppHibernationService mAppHibernationService;
|
||||||
|
private BroadcastReceiver mBroadcastReceiver;
|
||||||
|
@Mock
|
||||||
|
private Context mContext;
|
||||||
|
@Mock
|
||||||
|
private IPackageManager mIPackageManager;
|
||||||
|
@Mock
|
||||||
|
private IActivityManager mIActivityManager;
|
||||||
|
@Mock
|
||||||
|
private UserManager mUserManager;
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<BroadcastReceiver> mReceiverCaptor;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws RemoteException {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt());
|
||||||
|
|
||||||
|
mAppHibernationService = new AppHibernationService(mContext, mIPackageManager,
|
||||||
|
mIActivityManager, mUserManager);
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
doAnswer(returnsArgAt(2)).when(mIActivityManager).handleIncomingUser(anyInt(), anyInt(),
|
||||||
|
anyInt(), anyBoolean(), anyBoolean(), any(), any());
|
||||||
|
|
||||||
|
mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetHibernating_packageIsHibernating() {
|
||||||
|
// WHEN we hibernate a package for a user
|
||||||
|
mAppHibernationService.setHibernating(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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetHibernating_newPackageAdded_packageIsHibernating() {
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// THEN the new package is hibernated
|
||||||
|
assertTrue(mAppHibernationService.isHibernating(PACKAGE_NAME_2, USER_ID_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetHibernating_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));
|
||||||
|
doReturn(new ParceledListSlice<>(userPackages)).when(mIPackageManager)
|
||||||
|
.getInstalledPackages(anyInt(), eq(USER_ID_2));
|
||||||
|
Intent intent = new Intent(Intent.ACTION_USER_ADDED);
|
||||||
|
intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_2);
|
||||||
|
mBroadcastReceiver.onReceive(mContext, intent);
|
||||||
|
|
||||||
|
mAppHibernationService.setHibernating(PACKAGE_NAME_1, USER_ID_2, true);
|
||||||
|
|
||||||
|
// THEN the new user's package is hibernated
|
||||||
|
assertTrue(mAppHibernationService.isHibernating(PACKAGE_NAME_1, USER_ID_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsHibernating_packageReplaced_stillReturnsHibernating() {
|
||||||
|
// GIVEN a package is currently hibernated
|
||||||
|
mAppHibernationService.setHibernating(PACKAGE_NAME_1, USER_ID_1, true);
|
||||||
|
|
||||||
|
// WHEN the package is removed but marked as replacing
|
||||||
|
Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED,
|
||||||
|
Uri.fromParts(PACKAGE_SCHEME, PACKAGE_NAME_2, null /* fragment */));
|
||||||
|
intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_1);
|
||||||
|
intent.putExtra(Intent.EXTRA_REPLACING, true);
|
||||||
|
mBroadcastReceiver.onReceive(mContext, intent);
|
||||||
|
|
||||||
|
// THEN the package is still hibernating
|
||||||
|
assertTrue(mAppHibernationService.isHibernating(PACKAGE_NAME_1, USER_ID_1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PackageInfo makePackageInfo(String packageName) {
|
||||||
|
PackageInfo pkg = new PackageInfo();
|
||||||
|
pkg.packageName = packageName;
|
||||||
|
return pkg;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user