Merge "[pm] Implement the BIC service"
This commit is contained in:
32
core/proto/android/server/background_install_control.proto
Normal file
32
core/proto/android/server/background_install_control.proto
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.
|
||||
*/
|
||||
|
||||
syntax = "proto2";
|
||||
package com.android.server.pm;
|
||||
|
||||
option java_multiple_files = true;
|
||||
|
||||
// Proto for the background installed packages.
|
||||
// It's used for serializing the background installed package info to disk.
|
||||
message BackgroundInstalledPackagesProto {
|
||||
repeated BackgroundInstalledPackageProto bg_installed_pkg = 1;
|
||||
}
|
||||
|
||||
// Proto for the background installed package entry
|
||||
message BackgroundInstalledPackageProto {
|
||||
optional string package_name = 1;
|
||||
optional int32 user_id = 2;
|
||||
}
|
||||
@@ -17,19 +17,46 @@
|
||||
package com.android.server.pm;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.usage.UsageEvents;
|
||||
import android.app.usage.UsageStatsManagerInternal;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IBackgroundInstallControlService;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.InstallSourceInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.os.IBinder;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArraySet;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArrayMap;
|
||||
import android.util.SparseSetArray;
|
||||
import android.util.proto.ProtoInputStream;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.ServiceThread;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.pm.permission.PermissionManagerServiceInternal;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
@@ -37,14 +64,30 @@ import com.android.server.SystemService;
|
||||
public class BackgroundInstallControlService extends SystemService {
|
||||
private static final String TAG = "BackgroundInstallControlService";
|
||||
|
||||
private static final String DISK_FILE_NAME = "states";
|
||||
private static final String DISK_DIR_NAME = "bic";
|
||||
|
||||
private static final int MAX_FOREGROUND_TIME_FRAMES_SIZE = 10;
|
||||
|
||||
private static final int MSG_USAGE_EVENT_RECEIVED = 0;
|
||||
private static final int MSG_PACKAGE_ADDED = 1;
|
||||
private static final int MSG_PACKAGE_REMOVED = 2;
|
||||
|
||||
private final Context mContext;
|
||||
private final BinderService mBinderService;
|
||||
private final IPackageManager mIPackageManager;
|
||||
private final PackageManagerInternal mPackageManagerInternal;
|
||||
private final UsageStatsManagerInternal mUsageStatsManagerInternal;
|
||||
private final PermissionManagerServiceInternal mPermissionManager;
|
||||
private final Handler mHandler;
|
||||
private final File mDiskFile;
|
||||
|
||||
// User ID -> package name -> time diff
|
||||
// The time diff between the last foreground activity installer and
|
||||
// the "onPackageAdded" function call.
|
||||
private final SparseArrayMap<String, Long> mBackgroundInstalledPackages =
|
||||
|
||||
private SparseSetArray<String> mBackgroundInstalledPackages = null;
|
||||
|
||||
// User ID -> package name -> set of foreground time frame
|
||||
private final SparseArrayMap<String,
|
||||
TreeSet<ForegroundTimeFrame>> mInstallerForegroundTimeFrames =
|
||||
new SparseArrayMap<>();
|
||||
|
||||
public BackgroundInstallControlService(@NonNull Context context) {
|
||||
@@ -56,49 +99,385 @@ public class BackgroundInstallControlService extends SystemService {
|
||||
super(injector.getContext());
|
||||
mContext = injector.getContext();
|
||||
mIPackageManager = injector.getIPackageManager();
|
||||
mPackageManagerInternal = injector.getPackageManagerInternal();
|
||||
mPermissionManager = injector.getPermissionManager();
|
||||
mHandler = new EventHandler(injector.getLooper(), this);
|
||||
mDiskFile = injector.getDiskFile();
|
||||
mUsageStatsManagerInternal = injector.getUsageStatsManagerInternal();
|
||||
mUsageStatsManagerInternal.registerListener(
|
||||
(userId, event) ->
|
||||
mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED,
|
||||
userId,
|
||||
0,
|
||||
event).sendToTarget()
|
||||
);
|
||||
mBinderService = new BinderService(this);
|
||||
}
|
||||
|
||||
private static final class BinderService extends IBackgroundInstallControlService.Stub {
|
||||
final BackgroundInstallControlService mService;
|
||||
|
||||
BinderService(BackgroundInstallControlService service) {
|
||||
BinderService(BackgroundInstallControlService service) {
|
||||
mService = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
|
||||
@PackageManager.PackageInfoFlagsBits long flags, int userId) {
|
||||
ParceledListSlice<PackageInfo> packages;
|
||||
try {
|
||||
packages = mService.mIPackageManager.getInstalledPackages(flags, userId);
|
||||
} catch (RemoteException e) {
|
||||
throw new IllegalStateException("Package manager not available", e);
|
||||
}
|
||||
|
||||
// TODO(b/244216300): to enable the test the usage by BinaryTransparencyService,
|
||||
// we currently comment out the actual implementation.
|
||||
// The fake implementation is just to filter out the first app of the list.
|
||||
// for (int i = 0, size = packages.getList().size(); i < size; i++) {
|
||||
// String packageName = packages.getList().get(i).packageName;
|
||||
// if (!mBackgroundInstalledPackages.contains(userId, packageName) {
|
||||
// packages.getList().remove(i);
|
||||
// }
|
||||
// }
|
||||
if (packages.getList().size() > 0) {
|
||||
packages.getList().remove(0);
|
||||
}
|
||||
return packages;
|
||||
return mService.getBackgroundInstalledPackages(flags, userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the system service should publish a binder service using
|
||||
* {@link #publishBinderService(String, IBinder).}
|
||||
*/
|
||||
@VisibleForTesting
|
||||
ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
|
||||
@PackageManager.PackageInfoFlagsBits long flags, int userId) {
|
||||
ParceledListSlice<PackageInfo> packages;
|
||||
try {
|
||||
packages = mIPackageManager.getInstalledPackages(flags, userId);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
initBackgroundInstalledPackages();
|
||||
|
||||
ListIterator<PackageInfo> iter = packages.getList().listIterator();
|
||||
while (iter.hasNext()) {
|
||||
String packageName = iter.next().packageName;
|
||||
if (!mBackgroundInstalledPackages.contains(userId, packageName)) {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
||||
private static class EventHandler extends Handler {
|
||||
private final BackgroundInstallControlService mService;
|
||||
|
||||
EventHandler(Looper looper, BackgroundInstallControlService service) {
|
||||
super(looper);
|
||||
mService = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_USAGE_EVENT_RECEIVED: {
|
||||
mService.handleUsageEvent((UsageEvents.Event) msg.obj, msg.arg1 /* userId */);
|
||||
break;
|
||||
}
|
||||
case MSG_PACKAGE_ADDED: {
|
||||
mService.handlePackageAdd((String) msg.obj, msg.arg1 /* userId */);
|
||||
break;
|
||||
}
|
||||
case MSG_PACKAGE_REMOVED: {
|
||||
mService.handlePackageRemove((String) msg.obj, msg.arg1 /* userId */);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Slog.w(TAG, "Unknown message: " + msg.what);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handlePackageAdd(String packageName, int userId) {
|
||||
InstallSourceInfo installSourceInfo = null;
|
||||
try {
|
||||
installSourceInfo = mIPackageManager.getInstallSourceInfo(packageName);
|
||||
} catch (RemoteException e) {
|
||||
// Failed to talk to PackageManagerService Should never happen!
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
String installerPackageName =
|
||||
installSourceInfo == null ? null : installSourceInfo.getInstallingPackageName();
|
||||
if (installerPackageName == null) {
|
||||
Slog.w(TAG, "fails to get installerPackageName for " + packageName);
|
||||
return;
|
||||
}
|
||||
|
||||
ApplicationInfo appInfo = null;
|
||||
try {
|
||||
appInfo = mIPackageManager.getApplicationInfo(packageName,
|
||||
0, userId);
|
||||
} catch (RemoteException e) {
|
||||
// Failed to talk to PackageManagerService Should never happen!
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
if (appInfo == null) {
|
||||
Slog.w(TAG, "fails to get appInfo for " + packageName);
|
||||
return;
|
||||
}
|
||||
|
||||
// convert up-time to current time.
|
||||
final long installTimestamp = System.currentTimeMillis()
|
||||
- (SystemClock.uptimeMillis() - appInfo.createTimestamp);
|
||||
|
||||
if (wasForegroundInstallation(installerPackageName, userId, installTimestamp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
initBackgroundInstalledPackages();
|
||||
mBackgroundInstalledPackages.add(userId, packageName);
|
||||
writeBackgroundInstalledPackagesToDisk();
|
||||
}
|
||||
|
||||
private boolean wasForegroundInstallation(String installerPackageName,
|
||||
int userId, long installTimestamp) {
|
||||
TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames =
|
||||
mInstallerForegroundTimeFrames.get(userId, installerPackageName);
|
||||
|
||||
// The installer never run in foreground.
|
||||
if (foregroundTimeFrames == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var foregroundTimeFrame : foregroundTimeFrames) {
|
||||
// the foreground time frame starts later than the installation.
|
||||
// so the installation is outside the foreground time frame.
|
||||
if (foregroundTimeFrame.startTimeStampMillis > installTimestamp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// the foreground time frame is not over yet.
|
||||
// the installation is inside the foreground time frame.
|
||||
if (!foregroundTimeFrame.isDone()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// the foreground time frame ends later than the installation.
|
||||
// the installation is inside the foreground time frame.
|
||||
if (installTimestamp <= foregroundTimeFrame.endTimeStampMillis) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// the installation is not inside any of foreground time frames.
|
||||
// so it is not a foreground installation.
|
||||
return false;
|
||||
}
|
||||
|
||||
void handlePackageRemove(String packageName, int userId) {
|
||||
initBackgroundInstalledPackages();
|
||||
mBackgroundInstalledPackages.remove(userId, packageName);
|
||||
writeBackgroundInstalledPackagesToDisk();
|
||||
}
|
||||
|
||||
void handleUsageEvent(UsageEvents.Event event, int userId) {
|
||||
if (event.mEventType != UsageEvents.Event.ACTIVITY_RESUMED
|
||||
&& event.mEventType != UsageEvents.Event.ACTIVITY_PAUSED
|
||||
&& event.mEventType != UsageEvents.Event.ACTIVITY_STOPPED) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isInstaller(event.mPackage, userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mInstallerForegroundTimeFrames.contains(userId, event.mPackage)) {
|
||||
mInstallerForegroundTimeFrames.add(userId, event.mPackage, new TreeSet<>());
|
||||
}
|
||||
|
||||
TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames =
|
||||
mInstallerForegroundTimeFrames.get(userId, event.mPackage);
|
||||
|
||||
if ((foregroundTimeFrames.size() == 0) || foregroundTimeFrames.last().isDone()) {
|
||||
// ignore the other events if there is no open ForegroundTimeFrame.
|
||||
if (event.mEventType != UsageEvents.Event.ACTIVITY_RESUMED) {
|
||||
return;
|
||||
}
|
||||
foregroundTimeFrames.add(new ForegroundTimeFrame(event.mTimeStamp));
|
||||
}
|
||||
|
||||
foregroundTimeFrames.last().addEvent(event);
|
||||
|
||||
if (foregroundTimeFrames.size() > MAX_FOREGROUND_TIME_FRAMES_SIZE) {
|
||||
foregroundTimeFrames.pollFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void writeBackgroundInstalledPackagesToDisk() {
|
||||
AtomicFile atomicFile = new AtomicFile(mDiskFile);
|
||||
FileOutputStream fileOutputStream;
|
||||
try {
|
||||
fileOutputStream = atomicFile.startWrite();
|
||||
} catch (IOException e) {
|
||||
Slog.e(TAG, "Failed to start write to states protobuf.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
|
||||
for (int i = 0; i < mBackgroundInstalledPackages.size(); i++) {
|
||||
int userId = mBackgroundInstalledPackages.keyAt(i);
|
||||
for (String packageName : mBackgroundInstalledPackages.get(userId)) {
|
||||
long token = protoOutputStream.start(
|
||||
BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
|
||||
protoOutputStream.write(
|
||||
BackgroundInstalledPackageProto.PACKAGE_NAME, packageName);
|
||||
protoOutputStream.write(
|
||||
BackgroundInstalledPackageProto.USER_ID, userId + 1);
|
||||
protoOutputStream.end(token);
|
||||
}
|
||||
}
|
||||
protoOutputStream.flush();
|
||||
atomicFile.finishWrite(fileOutputStream);
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Failed to finish write to states protobuf.", e);
|
||||
atomicFile.failWrite(fileOutputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void initBackgroundInstalledPackages() {
|
||||
if (mBackgroundInstalledPackages != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mBackgroundInstalledPackages = new SparseSetArray<>();
|
||||
|
||||
if (!mDiskFile.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AtomicFile atomicFile = new AtomicFile(mDiskFile);
|
||||
try (FileInputStream fileInputStream = atomicFile.openRead()) {
|
||||
ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
|
||||
|
||||
while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||
if (protoInputStream.getFieldNumber()
|
||||
!= (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
|
||||
continue;
|
||||
}
|
||||
long token = protoInputStream.start(
|
||||
BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
|
||||
String packageName = null;
|
||||
int userId = UserHandle.USER_NULL;
|
||||
while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||
switch (protoInputStream.getFieldNumber()) {
|
||||
case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
|
||||
packageName = protoInputStream.readString(
|
||||
BackgroundInstalledPackageProto.PACKAGE_NAME);
|
||||
break;
|
||||
case (int) BackgroundInstalledPackageProto.USER_ID:
|
||||
userId = protoInputStream.readInt(
|
||||
BackgroundInstalledPackageProto.USER_ID) - 1;
|
||||
break;
|
||||
default:
|
||||
Slog.w(TAG, "Undefined field in proto: "
|
||||
+ protoInputStream.getFieldNumber());
|
||||
}
|
||||
}
|
||||
protoInputStream.end(token);
|
||||
if (packageName != null && userId != UserHandle.USER_NULL) {
|
||||
mBackgroundInstalledPackages.add(userId, packageName);
|
||||
} else {
|
||||
Slog.w(TAG, "Fails to get packageName or UserId from proto file");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Slog.w(TAG, "Error reading state from the disk", e);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
SparseSetArray<String> getBackgroundInstalledPackages() {
|
||||
return mBackgroundInstalledPackages;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
SparseArrayMap<String, TreeSet<ForegroundTimeFrame>> getInstallerForegroundTimeFrames() {
|
||||
return mInstallerForegroundTimeFrames;
|
||||
}
|
||||
|
||||
private boolean isInstaller(String pkgName, int userId) {
|
||||
if (mInstallerForegroundTimeFrames.contains(userId, pkgName)) {
|
||||
return true;
|
||||
}
|
||||
return mPermissionManager.checkPermission(pkgName,
|
||||
android.Manifest.permission.INSTALL_PACKAGES,
|
||||
userId) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService);
|
||||
onStart(/* isForTesting= */ false);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void onStart(boolean isForTesting) {
|
||||
if (!isForTesting) {
|
||||
publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService);
|
||||
}
|
||||
|
||||
mPackageManagerInternal.getPackageList(new PackageManagerInternal.PackageListObserver() {
|
||||
@Override
|
||||
public void onPackageAdded(String packageName, int uid) {
|
||||
final int userId = UserHandle.getUserId(uid);
|
||||
mHandler.obtainMessage(MSG_PACKAGE_ADDED,
|
||||
userId, 0, packageName).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageRemoved(String packageName, int uid) {
|
||||
final int userId = UserHandle.getUserId(uid);
|
||||
mHandler.obtainMessage(MSG_PACKAGE_REMOVED,
|
||||
userId, 0, packageName).sendToTarget();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// The foreground time frame (ForegroundTimeFrame) represents the period
|
||||
// when a package's activities continuously occupy the foreground.
|
||||
// Each ForegroundTimeFrame starts with an ACTIVITY_RESUMED event,
|
||||
// and then ends with an ACTIVITY_PAUSED or ACTIVITY_STOPPED event.
|
||||
// The startTimeStampMillis stores the timestamp of the ACTIVITY_RESUMED event.
|
||||
// The endTimeStampMillis stores the timestamp of the ACTIVITY_PAUSED or ACTIVITY_STOPPED event
|
||||
// that wraps up the ForegroundTimeFrame.
|
||||
// The activities are designed to handle the edge case in which a package's one activity
|
||||
// seamlessly replace another activity of the same package. Thus, we count these activities
|
||||
// together as a ForegroundTimeFrame. For this scenario, only when all the activities terminate
|
||||
// shall consider the completion of the ForegroundTimeFrame.
|
||||
static final class ForegroundTimeFrame implements Comparable<ForegroundTimeFrame> {
|
||||
public final long startTimeStampMillis;
|
||||
public long endTimeStampMillis;
|
||||
public final Set<Integer> activities;
|
||||
|
||||
public int compareTo(ForegroundTimeFrame o) {
|
||||
int comp = Long.compare(startTimeStampMillis, o.startTimeStampMillis);
|
||||
if (comp != 0) return comp;
|
||||
|
||||
return Integer.compare(hashCode(), o.hashCode());
|
||||
}
|
||||
|
||||
ForegroundTimeFrame(long startTimeStampMillis) {
|
||||
this.startTimeStampMillis = startTimeStampMillis;
|
||||
endTimeStampMillis = 0;
|
||||
activities = new ArraySet<>();
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return endTimeStampMillis != 0;
|
||||
}
|
||||
|
||||
public void addEvent(UsageEvents.Event event) {
|
||||
switch (event.mEventType) {
|
||||
case UsageEvents.Event.ACTIVITY_RESUMED:
|
||||
activities.add(event.mInstanceId);
|
||||
break;
|
||||
case UsageEvents.Event.ACTIVITY_PAUSED:
|
||||
case UsageEvents.Event.ACTIVITY_STOPPED:
|
||||
if (activities.contains(event.mInstanceId)) {
|
||||
activities.remove(event.mInstanceId);
|
||||
if (activities.size() == 0) {
|
||||
endTimeStampMillis = event.mTimeStamp;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,6 +487,16 @@ public class BackgroundInstallControlService extends SystemService {
|
||||
Context getContext();
|
||||
|
||||
IPackageManager getIPackageManager();
|
||||
|
||||
PackageManagerInternal getPackageManagerInternal();
|
||||
|
||||
UsageStatsManagerInternal getUsageStatsManagerInternal();
|
||||
|
||||
PermissionManagerServiceInternal getPermissionManager();
|
||||
|
||||
Looper getLooper();
|
||||
|
||||
File getDiskFile();
|
||||
}
|
||||
|
||||
private static final class InjectorImpl implements Injector {
|
||||
@@ -126,5 +515,36 @@ public class BackgroundInstallControlService extends SystemService {
|
||||
public IPackageManager getIPackageManager() {
|
||||
return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackageManagerInternal getPackageManagerInternal() {
|
||||
return LocalServices.getService(PackageManagerInternal.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsageStatsManagerInternal getUsageStatsManagerInternal() {
|
||||
return LocalServices.getService(UsageStatsManagerInternal.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermissionManagerServiceInternal getPermissionManager() {
|
||||
return LocalServices.getService(PermissionManagerServiceInternal.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Looper getLooper() {
|
||||
ServiceThread serviceThread = new ServiceThread(TAG,
|
||||
android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
|
||||
serviceThread.start();
|
||||
return serviceThread.getLooper();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDiskFile() {
|
||||
File dir = new File(Environment.getDataSystemDirectory(), DISK_DIR_NAME);
|
||||
File file = new File(dir, DISK_FILE_NAME);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,840 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.pm;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.usage.UsageEvents;
|
||||
import android.app.usage.UsageEvents.Event;
|
||||
import android.app.usage.UsageStatsManagerInternal;
|
||||
import android.app.usage.UsageStatsManagerInternal.UsageEventListener;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.InstallSourceInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.os.FileUtils;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.os.test.TestLooper;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.SparseSetArray;
|
||||
import android.util.proto.ProtoInputStream;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import com.android.server.pm.permission.PermissionManagerServiceInternal;
|
||||
|
||||
import org.junit.After;
|
||||
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 org.mockito.internal.util.reflection.FieldSetter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tests for {@link com.android.server.pm.BackgroundInstallControlService}
|
||||
*/
|
||||
@Presubmit
|
||||
public final class BackgroundInstallControlServiceTest {
|
||||
private static final String INSTALLER_NAME_1 = "installer1";
|
||||
private static final String INSTALLER_NAME_2 = "installer2";
|
||||
private static final String PACKAGE_NAME_1 = "package1";
|
||||
private static final String PACKAGE_NAME_2 = "package2";
|
||||
private static final String PACKAGE_NAME_3 = "package3";
|
||||
private static final int USER_ID_1 = 1;
|
||||
private static final int USER_ID_2 = 2;
|
||||
private static final long USAGE_EVENT_TIMESTAMP_1 = 1000;
|
||||
private static final long USAGE_EVENT_TIMESTAMP_2 = 2000;
|
||||
private static final long USAGE_EVENT_TIMESTAMP_3 = 3000;
|
||||
private static final long PACKAGE_ADD_TIMESTAMP_1 = 1500;
|
||||
|
||||
private BackgroundInstallControlService mBackgroundInstallControlService;
|
||||
private PackageManagerInternal.PackageListObserver mPackageListObserver;
|
||||
private UsageEventListener mUsageEventListener;
|
||||
private TestLooper mTestLooper;
|
||||
private Looper mLooper;
|
||||
private File mFile;
|
||||
|
||||
|
||||
@Mock
|
||||
private Context mContext;
|
||||
@Mock
|
||||
private IPackageManager mIPackageManager;
|
||||
@Mock
|
||||
private PackageManagerInternal mPackageManagerInternal;
|
||||
@Mock
|
||||
private UsageStatsManagerInternal mUsageStatsManagerInternal;
|
||||
@Mock
|
||||
private PermissionManagerServiceInternal mPermissionManager;
|
||||
@Captor
|
||||
private ArgumentCaptor<PackageManagerInternal.PackageListObserver> mPackageListObserverCaptor;
|
||||
@Captor
|
||||
private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mTestLooper = new TestLooper();
|
||||
mLooper = mTestLooper.getLooper();
|
||||
mFile = new File(
|
||||
InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(),
|
||||
"test");
|
||||
mBackgroundInstallControlService = new BackgroundInstallControlService(
|
||||
new MockInjector(mContext));
|
||||
|
||||
verify(mUsageStatsManagerInternal).registerListener(mUsageEventListenerCaptor.capture());
|
||||
mUsageEventListener = mUsageEventListenerCaptor.getValue();
|
||||
|
||||
mBackgroundInstallControlService.onStart(true);
|
||||
verify(mPackageManagerInternal).getPackageList(mPackageListObserverCaptor.capture());
|
||||
mPackageListObserver = mPackageListObserverCaptor.getValue();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
FileUtils.deleteContentsAndDir(mFile);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitBackgroundInstalledPackages_empty() {
|
||||
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
|
||||
mBackgroundInstallControlService.initBackgroundInstalledPackages();
|
||||
assertNotNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
|
||||
assertEquals(0,
|
||||
mBackgroundInstallControlService.getBackgroundInstalledPackages().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitBackgroundInstalledPackages_one() {
|
||||
AtomicFile atomicFile = new AtomicFile(mFile);
|
||||
FileOutputStream fileOutputStream;
|
||||
try {
|
||||
fileOutputStream = atomicFile.startWrite();
|
||||
} catch (IOException e) {
|
||||
fail("Failed to start write to states protobuf." + e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Write test data to the file on the disk.
|
||||
try {
|
||||
ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
|
||||
long token = protoOutputStream.start(
|
||||
BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
|
||||
protoOutputStream.write(
|
||||
BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
|
||||
protoOutputStream.write(
|
||||
BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
|
||||
protoOutputStream.end(token);
|
||||
protoOutputStream.flush();
|
||||
atomicFile.finishWrite(fileOutputStream);
|
||||
} catch (Exception e) {
|
||||
fail("Failed to finish write to states protobuf. " + e);
|
||||
atomicFile.failWrite(fileOutputStream);
|
||||
}
|
||||
|
||||
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
|
||||
mBackgroundInstallControlService.initBackgroundInstalledPackages();
|
||||
var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
|
||||
assertNotNull(packages);
|
||||
assertEquals(1, packages.size());
|
||||
assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitBackgroundInstalledPackages_two() {
|
||||
AtomicFile atomicFile = new AtomicFile(mFile);
|
||||
FileOutputStream fileOutputStream;
|
||||
try {
|
||||
fileOutputStream = atomicFile.startWrite();
|
||||
} catch (IOException e) {
|
||||
fail("Failed to start write to states protobuf." + e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Write test data to the file on the disk.
|
||||
try {
|
||||
ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
|
||||
|
||||
long token = protoOutputStream.start(
|
||||
BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
|
||||
protoOutputStream.write(
|
||||
BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1);
|
||||
protoOutputStream.write(
|
||||
BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1);
|
||||
protoOutputStream.end(token);
|
||||
|
||||
token = protoOutputStream.start(
|
||||
BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
|
||||
protoOutputStream.write(
|
||||
BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_2);
|
||||
protoOutputStream.write(
|
||||
BackgroundInstalledPackageProto.USER_ID, USER_ID_2 + 1);
|
||||
protoOutputStream.end(token);
|
||||
|
||||
protoOutputStream.flush();
|
||||
atomicFile.finishWrite(fileOutputStream);
|
||||
} catch (Exception e) {
|
||||
fail("Failed to finish write to states protobuf. " + e);
|
||||
atomicFile.failWrite(fileOutputStream);
|
||||
}
|
||||
|
||||
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
|
||||
mBackgroundInstallControlService.initBackgroundInstalledPackages();
|
||||
var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
|
||||
assertNotNull(packages);
|
||||
assertEquals(2, packages.size());
|
||||
assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
|
||||
assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteBackgroundInstalledPackagesToDisk_empty() {
|
||||
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
|
||||
mBackgroundInstallControlService.initBackgroundInstalledPackages();
|
||||
var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
|
||||
assertNotNull(packages);
|
||||
mBackgroundInstallControlService.writeBackgroundInstalledPackagesToDisk();
|
||||
|
||||
// Read the file on the disk to verify
|
||||
var packagesInDisk = new SparseSetArray<>();
|
||||
AtomicFile atomicFile = new AtomicFile(mFile);
|
||||
try (FileInputStream fileInputStream = atomicFile.openRead()) {
|
||||
ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
|
||||
|
||||
while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||
if (protoInputStream.getFieldNumber()
|
||||
!= (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
|
||||
continue;
|
||||
}
|
||||
long token = protoInputStream.start(
|
||||
BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
|
||||
String packageName = null;
|
||||
int userId = UserHandle.USER_NULL;
|
||||
while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||
switch (protoInputStream.getFieldNumber()) {
|
||||
case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
|
||||
packageName = protoInputStream.readString(
|
||||
BackgroundInstalledPackageProto.PACKAGE_NAME);
|
||||
break;
|
||||
case (int) BackgroundInstalledPackageProto.USER_ID:
|
||||
userId = protoInputStream.readInt(
|
||||
BackgroundInstalledPackageProto.USER_ID) - 1;
|
||||
break;
|
||||
default:
|
||||
fail("Undefined field in proto: "
|
||||
+ protoInputStream.getFieldNumber());
|
||||
}
|
||||
}
|
||||
protoInputStream.end(token);
|
||||
if (packageName != null && userId != UserHandle.USER_NULL) {
|
||||
packagesInDisk.add(userId, packageName);
|
||||
} else {
|
||||
fail("Fails to get packageName or UserId from proto file");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
fail("Error reading state from the disk. " + e);
|
||||
}
|
||||
|
||||
assertEquals(0, packagesInDisk.size());
|
||||
assertEquals(packages.size(), packagesInDisk.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteBackgroundInstalledPackagesToDisk_one() {
|
||||
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
|
||||
mBackgroundInstallControlService.initBackgroundInstalledPackages();
|
||||
var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
|
||||
assertNotNull(packages);
|
||||
|
||||
packages.add(USER_ID_1, PACKAGE_NAME_1);
|
||||
mBackgroundInstallControlService.writeBackgroundInstalledPackagesToDisk();
|
||||
|
||||
// Read the file on the disk to verify
|
||||
var packagesInDisk = new SparseSetArray<>();
|
||||
AtomicFile atomicFile = new AtomicFile(mFile);
|
||||
try (FileInputStream fileInputStream = atomicFile.openRead()) {
|
||||
ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
|
||||
|
||||
while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||
if (protoInputStream.getFieldNumber()
|
||||
!= (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
|
||||
continue;
|
||||
}
|
||||
long token = protoInputStream.start(
|
||||
BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
|
||||
String packageName = null;
|
||||
int userId = UserHandle.USER_NULL;
|
||||
while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||
switch (protoInputStream.getFieldNumber()) {
|
||||
case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
|
||||
packageName = protoInputStream.readString(
|
||||
BackgroundInstalledPackageProto.PACKAGE_NAME);
|
||||
break;
|
||||
case (int) BackgroundInstalledPackageProto.USER_ID:
|
||||
userId = protoInputStream.readInt(
|
||||
BackgroundInstalledPackageProto.USER_ID) - 1;
|
||||
break;
|
||||
default:
|
||||
fail("Undefined field in proto: "
|
||||
+ protoInputStream.getFieldNumber());
|
||||
}
|
||||
}
|
||||
protoInputStream.end(token);
|
||||
if (packageName != null && userId != UserHandle.USER_NULL) {
|
||||
packagesInDisk.add(userId, packageName);
|
||||
} else {
|
||||
fail("Fails to get packageName or UserId from proto file");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
fail("Error reading state from the disk. " + e);
|
||||
}
|
||||
|
||||
assertEquals(1, packagesInDisk.size());
|
||||
assertEquals(packages.size(), packagesInDisk.size());
|
||||
assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteBackgroundInstalledPackagesToDisk_two() {
|
||||
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
|
||||
mBackgroundInstallControlService.initBackgroundInstalledPackages();
|
||||
var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
|
||||
assertNotNull(packages);
|
||||
|
||||
packages.add(USER_ID_1, PACKAGE_NAME_1);
|
||||
packages.add(USER_ID_2, PACKAGE_NAME_2);
|
||||
mBackgroundInstallControlService.writeBackgroundInstalledPackagesToDisk();
|
||||
|
||||
// Read the file on the disk to verify
|
||||
var packagesInDisk = new SparseSetArray<>();
|
||||
AtomicFile atomicFile = new AtomicFile(mFile);
|
||||
try (FileInputStream fileInputStream = atomicFile.openRead()) {
|
||||
ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
|
||||
|
||||
while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||
if (protoInputStream.getFieldNumber()
|
||||
!= (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
|
||||
continue;
|
||||
}
|
||||
long token = protoInputStream.start(
|
||||
BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
|
||||
String packageName = null;
|
||||
int userId = UserHandle.USER_NULL;
|
||||
while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||
switch (protoInputStream.getFieldNumber()) {
|
||||
case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
|
||||
packageName = protoInputStream.readString(
|
||||
BackgroundInstalledPackageProto.PACKAGE_NAME);
|
||||
break;
|
||||
case (int) BackgroundInstalledPackageProto.USER_ID:
|
||||
userId = protoInputStream.readInt(
|
||||
BackgroundInstalledPackageProto.USER_ID) - 1;
|
||||
break;
|
||||
default:
|
||||
fail("Undefined field in proto: "
|
||||
+ protoInputStream.getFieldNumber());
|
||||
}
|
||||
}
|
||||
protoInputStream.end(token);
|
||||
if (packageName != null && userId != UserHandle.USER_NULL) {
|
||||
packagesInDisk.add(userId, packageName);
|
||||
} else {
|
||||
fail("Fails to get packageName or UserId from proto file");
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
fail("Error reading state from the disk. " + e);
|
||||
}
|
||||
|
||||
assertEquals(2, packagesInDisk.size());
|
||||
assertEquals(packages.size(), packagesInDisk.size());
|
||||
assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
|
||||
assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleUsageEvent_permissionDenied() {
|
||||
assertEquals(0,
|
||||
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
|
||||
doReturn(PackageManager.PERMISSION_DENIED).when(mPermissionManager).checkPermission(
|
||||
anyString(), anyString(), anyInt());
|
||||
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
|
||||
USER_ID_1, INSTALLER_NAME_1, 0);
|
||||
mTestLooper.dispatchAll();
|
||||
assertEquals(0,
|
||||
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleUsageEvent_permissionGranted() {
|
||||
assertEquals(0,
|
||||
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
|
||||
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
|
||||
anyString(), anyString(), anyInt());
|
||||
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
|
||||
USER_ID_1, INSTALLER_NAME_1, 0);
|
||||
mTestLooper.dispatchAll();
|
||||
assertEquals(1,
|
||||
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleUsageEvent_ignoredEvent() {
|
||||
assertEquals(0,
|
||||
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
|
||||
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
|
||||
anyString(), anyString(), anyInt());
|
||||
generateUsageEvent(UsageEvents.Event.USER_INTERACTION,
|
||||
USER_ID_1, INSTALLER_NAME_1, 0);
|
||||
mTestLooper.dispatchAll();
|
||||
assertEquals(0,
|
||||
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleUsageEvent_firstActivityResumedHalfTimeFrame() {
|
||||
assertEquals(0,
|
||||
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
|
||||
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
|
||||
anyString(), anyString(), anyInt());
|
||||
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
|
||||
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
var installerForegroundTimeFrames =
|
||||
mBackgroundInstallControlService.getInstallerForegroundTimeFrames();
|
||||
assertEquals(1, installerForegroundTimeFrames.numMaps());
|
||||
assertEquals(1, installerForegroundTimeFrames.numElementsForKey(USER_ID_1));
|
||||
|
||||
var foregroundTimeFrames = installerForegroundTimeFrames.get(USER_ID_1, INSTALLER_NAME_1);
|
||||
assertEquals(1, foregroundTimeFrames.size());
|
||||
|
||||
var foregroundTimeFrame = foregroundTimeFrames.first();
|
||||
assertEquals(USAGE_EVENT_TIMESTAMP_1, foregroundTimeFrame.startTimeStampMillis);
|
||||
assertFalse(foregroundTimeFrame.isDone());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleUsageEvent_firstActivityResumedOneTimeFrame() {
|
||||
assertEquals(0,
|
||||
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
|
||||
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
|
||||
anyString(), anyString(), anyInt());
|
||||
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
|
||||
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
|
||||
generateUsageEvent(Event.ACTIVITY_STOPPED,
|
||||
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
var installerForegroundTimeFrames =
|
||||
mBackgroundInstallControlService.getInstallerForegroundTimeFrames();
|
||||
assertEquals(1, installerForegroundTimeFrames.numMaps());
|
||||
assertEquals(1, installerForegroundTimeFrames.numElementsForKey(USER_ID_1));
|
||||
|
||||
var foregroundTimeFrames = installerForegroundTimeFrames.get(USER_ID_1, INSTALLER_NAME_1);
|
||||
assertEquals(1, foregroundTimeFrames.size());
|
||||
|
||||
var foregroundTimeFrame = foregroundTimeFrames.first();
|
||||
assertEquals(USAGE_EVENT_TIMESTAMP_1, foregroundTimeFrame.startTimeStampMillis);
|
||||
assertTrue(foregroundTimeFrame.isDone());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleUsageEvent_firstActivityResumedOneAndHalfTimeFrame() {
|
||||
assertEquals(0,
|
||||
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
|
||||
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
|
||||
anyString(), anyString(), anyInt());
|
||||
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
|
||||
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
|
||||
generateUsageEvent(Event.ACTIVITY_STOPPED,
|
||||
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
|
||||
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
|
||||
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
var installerForegroundTimeFrames =
|
||||
mBackgroundInstallControlService.getInstallerForegroundTimeFrames();
|
||||
assertEquals(1, installerForegroundTimeFrames.numMaps());
|
||||
assertEquals(1, installerForegroundTimeFrames.numElementsForKey(USER_ID_1));
|
||||
|
||||
var foregroundTimeFrames = installerForegroundTimeFrames.get(USER_ID_1, INSTALLER_NAME_1);
|
||||
assertEquals(2, foregroundTimeFrames.size());
|
||||
|
||||
var foregroundTimeFrame1 = foregroundTimeFrames.first();
|
||||
assertEquals(USAGE_EVENT_TIMESTAMP_1, foregroundTimeFrame1.startTimeStampMillis);
|
||||
assertTrue(foregroundTimeFrame1.isDone());
|
||||
|
||||
var foregroundTimeFrame2 = foregroundTimeFrames.last();
|
||||
assertEquals(USAGE_EVENT_TIMESTAMP_3, foregroundTimeFrame2.startTimeStampMillis);
|
||||
assertFalse(foregroundTimeFrame2.isDone());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleUsageEvent_firstNoneActivityResumed() {
|
||||
assertEquals(0,
|
||||
mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps());
|
||||
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
|
||||
anyString(), anyString(), anyInt());
|
||||
generateUsageEvent(Event.ACTIVITY_STOPPED,
|
||||
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
var installerForegroundTimeFrames =
|
||||
mBackgroundInstallControlService.getInstallerForegroundTimeFrames();
|
||||
assertEquals(1, installerForegroundTimeFrames.numMaps());
|
||||
assertEquals(1, installerForegroundTimeFrames.numElementsForKey(USER_ID_1));
|
||||
|
||||
var foregroundTimeFrames = installerForegroundTimeFrames.get(USER_ID_1, INSTALLER_NAME_1);
|
||||
assertEquals(0, foregroundTimeFrames.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleUsageEvent_packageAddedNoUsageEvent() throws
|
||||
RemoteException, NoSuchFieldException {
|
||||
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
|
||||
InstallSourceInfo installSourceInfo = new InstallSourceInfo(
|
||||
/* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
|
||||
/* originatingPackageName = */ null,
|
||||
/* installingPackageName = */ INSTALLER_NAME_1);
|
||||
assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
|
||||
when(mIPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
|
||||
ApplicationInfo appInfo = mock(ApplicationInfo.class);
|
||||
|
||||
when(mIPackageManager.getApplicationInfo(
|
||||
eq(PACKAGE_NAME_1),
|
||||
eq(0L),
|
||||
anyInt())
|
||||
).thenReturn(appInfo);
|
||||
|
||||
long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
|
||||
- (System.currentTimeMillis() - SystemClock.uptimeMillis());
|
||||
FieldSetter.setField(appInfo,
|
||||
ApplicationInfo.class.getDeclaredField("createTimestamp"),
|
||||
createTimestamp);
|
||||
|
||||
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
|
||||
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
|
||||
|
||||
mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
|
||||
assertNotNull(packages);
|
||||
assertEquals(1, packages.size());
|
||||
assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleUsageEvent_packageAddedInsideTimeFrame() throws
|
||||
RemoteException, NoSuchFieldException {
|
||||
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
|
||||
InstallSourceInfo installSourceInfo = new InstallSourceInfo(
|
||||
/* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
|
||||
/* originatingPackageName = */ null,
|
||||
/* installingPackageName = */ INSTALLER_NAME_1);
|
||||
assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
|
||||
when(mIPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
|
||||
ApplicationInfo appInfo = mock(ApplicationInfo.class);
|
||||
|
||||
when(mIPackageManager.getApplicationInfo(
|
||||
eq(PACKAGE_NAME_1),
|
||||
eq(0L),
|
||||
anyInt())
|
||||
).thenReturn(appInfo);
|
||||
|
||||
long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
|
||||
- (System.currentTimeMillis() - SystemClock.uptimeMillis());
|
||||
FieldSetter.setField(appInfo,
|
||||
ApplicationInfo.class.getDeclaredField("createTimestamp"),
|
||||
createTimestamp);
|
||||
|
||||
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
|
||||
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
|
||||
|
||||
// The following 2 usage events generation is the only difference from the
|
||||
// testHandleUsageEvent_packageAddedNoUsageEvent test.
|
||||
// The 2 usage events make the package adding inside a time frame.
|
||||
// So it's not a background install. Thus, it's null for the return of
|
||||
// mBackgroundInstallControlService.getBackgroundInstalledPackages()
|
||||
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
|
||||
anyString(), anyString(), anyInt());
|
||||
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
|
||||
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1);
|
||||
generateUsageEvent(Event.ACTIVITY_STOPPED,
|
||||
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
|
||||
|
||||
mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
|
||||
mTestLooper.dispatchAll();
|
||||
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleUsageEvent_packageAddedOutsideTimeFrame1() throws
|
||||
RemoteException, NoSuchFieldException {
|
||||
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
|
||||
InstallSourceInfo installSourceInfo = new InstallSourceInfo(
|
||||
/* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
|
||||
/* originatingPackageName = */ null,
|
||||
/* installingPackageName = */ INSTALLER_NAME_1);
|
||||
assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
|
||||
when(mIPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
|
||||
ApplicationInfo appInfo = mock(ApplicationInfo.class);
|
||||
|
||||
when(mIPackageManager.getApplicationInfo(
|
||||
eq(PACKAGE_NAME_1),
|
||||
eq(0L),
|
||||
anyInt())
|
||||
).thenReturn(appInfo);
|
||||
|
||||
long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
|
||||
- (System.currentTimeMillis() - SystemClock.uptimeMillis());
|
||||
FieldSetter.setField(appInfo,
|
||||
ApplicationInfo.class.getDeclaredField("createTimestamp"),
|
||||
createTimestamp);
|
||||
|
||||
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
|
||||
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
|
||||
|
||||
// The following 2 usage events generation is the only difference from the
|
||||
// testHandleUsageEvent_packageAddedNoUsageEvent test.
|
||||
// The 2 usage events make the package adding outside a time frame.
|
||||
// Compared to testHandleUsageEvent_packageAddedInsideTimeFrame,
|
||||
// it's a background install. Thus, it's not null for the return of
|
||||
// mBackgroundInstallControlService.getBackgroundInstalledPackages()
|
||||
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
|
||||
anyString(), anyString(), anyInt());
|
||||
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
|
||||
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
|
||||
generateUsageEvent(Event.ACTIVITY_STOPPED,
|
||||
USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3);
|
||||
|
||||
mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
|
||||
assertNotNull(packages);
|
||||
assertEquals(1, packages.size());
|
||||
assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
|
||||
}
|
||||
@Test
|
||||
public void testHandleUsageEvent_packageAddedOutsideTimeFrame2() throws
|
||||
RemoteException, NoSuchFieldException {
|
||||
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
|
||||
InstallSourceInfo installSourceInfo = new InstallSourceInfo(
|
||||
/* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
|
||||
/* originatingPackageName = */ null,
|
||||
/* installingPackageName = */ INSTALLER_NAME_1);
|
||||
assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
|
||||
when(mIPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
|
||||
ApplicationInfo appInfo = mock(ApplicationInfo.class);
|
||||
|
||||
when(mIPackageManager.getApplicationInfo(
|
||||
eq(PACKAGE_NAME_1),
|
||||
eq(0L),
|
||||
anyInt())
|
||||
).thenReturn(appInfo);
|
||||
|
||||
long createTimestamp = PACKAGE_ADD_TIMESTAMP_1
|
||||
- (System.currentTimeMillis() - SystemClock.uptimeMillis());
|
||||
FieldSetter.setField(appInfo,
|
||||
ApplicationInfo.class.getDeclaredField("createTimestamp"),
|
||||
createTimestamp);
|
||||
|
||||
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
|
||||
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
|
||||
|
||||
// The following 2 usage events generation is the only difference from the
|
||||
// testHandleUsageEvent_packageAddedNoUsageEvent test.
|
||||
// These 2 usage events are triggered by INSTALLER_NAME_2.
|
||||
// The 2 usage events make the package adding outside a time frame.
|
||||
// Compared to testHandleUsageEvent_packageAddedInsideTimeFrame,
|
||||
// it's a background install. Thus, it's not null for the return of
|
||||
// mBackgroundInstallControlService.getBackgroundInstalledPackages()
|
||||
doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission(
|
||||
anyString(), anyString(), anyInt());
|
||||
generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED,
|
||||
USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_2);
|
||||
generateUsageEvent(Event.ACTIVITY_STOPPED,
|
||||
USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_3);
|
||||
|
||||
mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
|
||||
assertNotNull(packages);
|
||||
assertEquals(1, packages.size());
|
||||
assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPackageRemoved() {
|
||||
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
|
||||
mBackgroundInstallControlService.initBackgroundInstalledPackages();
|
||||
var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
|
||||
assertNotNull(packages);
|
||||
|
||||
packages.add(USER_ID_1, PACKAGE_NAME_1);
|
||||
packages.add(USER_ID_2, PACKAGE_NAME_2);
|
||||
|
||||
assertEquals(2, packages.size());
|
||||
assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
|
||||
assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
|
||||
|
||||
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
|
||||
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
|
||||
|
||||
mPackageListObserver.onPackageRemoved(PACKAGE_NAME_1, uid);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
assertEquals(1, packages.size());
|
||||
assertFalse(packages.contains(USER_ID_1, PACKAGE_NAME_1));
|
||||
assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBackgroundInstalledPackages() throws RemoteException {
|
||||
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
|
||||
mBackgroundInstallControlService.initBackgroundInstalledPackages();
|
||||
var bgPackages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
|
||||
assertNotNull(bgPackages);
|
||||
|
||||
bgPackages.add(USER_ID_1, PACKAGE_NAME_1);
|
||||
bgPackages.add(USER_ID_2, PACKAGE_NAME_2);
|
||||
|
||||
assertEquals(2, bgPackages.size());
|
||||
assertTrue(bgPackages.contains(USER_ID_1, PACKAGE_NAME_1));
|
||||
assertTrue(bgPackages.contains(USER_ID_2, PACKAGE_NAME_2));
|
||||
|
||||
List<PackageInfo> packages = new ArrayList<>();
|
||||
var packageInfo1 = makePackageInfo(PACKAGE_NAME_1);
|
||||
packages.add(packageInfo1);
|
||||
var packageInfo2 = makePackageInfo(PACKAGE_NAME_2);
|
||||
packages.add(packageInfo2);
|
||||
var packageInfo3 = makePackageInfo(PACKAGE_NAME_3);
|
||||
packages.add(packageInfo3);
|
||||
doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages(
|
||||
anyLong(), anyInt());
|
||||
|
||||
var resultPackages =
|
||||
mBackgroundInstallControlService.getBackgroundInstalledPackages(0L, USER_ID_1);
|
||||
assertEquals(1, resultPackages.getList().size());
|
||||
assertTrue(resultPackages.getList().contains(packageInfo1));
|
||||
assertFalse(resultPackages.getList().contains(packageInfo2));
|
||||
assertFalse(resultPackages.getList().contains(packageInfo3));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock a usage event occurring.
|
||||
*
|
||||
* @param usageEventId id of a usage event
|
||||
* @param userId user id of a usage event
|
||||
* @param pkgName package name of a usage event
|
||||
* @param timestamp timestamp of a usage event
|
||||
*/
|
||||
private void generateUsageEvent(int usageEventId,
|
||||
int userId,
|
||||
String pkgName,
|
||||
long timestamp) {
|
||||
Event event = new Event(usageEventId, timestamp);
|
||||
event.mPackage = pkgName;
|
||||
mUsageEventListener.onUsageEvent(userId, event);
|
||||
}
|
||||
|
||||
private PackageInfo makePackageInfo(String packageName) {
|
||||
PackageInfo pkg = new PackageInfo();
|
||||
pkg.packageName = packageName;
|
||||
pkg.applicationInfo = new ApplicationInfo();
|
||||
return pkg;
|
||||
}
|
||||
|
||||
private class MockInjector implements BackgroundInstallControlService.Injector {
|
||||
private final Context mContext;
|
||||
|
||||
MockInjector(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPackageManager getIPackageManager() {
|
||||
return mIPackageManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackageManagerInternal getPackageManagerInternal() {
|
||||
return mPackageManagerInternal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsageStatsManagerInternal getUsageStatsManagerInternal() {
|
||||
return mUsageStatsManagerInternal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermissionManagerServiceInternal getPermissionManager() {
|
||||
return mPermissionManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Looper getLooper() {
|
||||
return mLooper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDiskFile() {
|
||||
return mFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user