Merge "[pm] Implement the BIC service"

This commit is contained in:
Wenhao Wang
2022-12-14 18:18:56 +00:00
committed by Android (Google) Code Review
3 changed files with 1323 additions and 31 deletions

View 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;
}

View File

@@ -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;
}
}
}

View 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;
}
}
}