Updated BackgroundInstallControlService to use historical sessions to
retrieve install start time.
Test: atest BinaryTransparencyHostTest BackgroundInstallControlServiceHostTest BackgroundInstallControlServiceTest BackgroundInstallControlCallbackHelperTest
Bug: 296058502
Change-Id: I663e80f9ccb98e825468980195d6267386f86184
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 524bad5..b6daed1 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -30,6 +30,7 @@
import android.content.pm.IBackgroundInstallControlService;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
@@ -46,6 +47,7 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArrayMap;
import android.util.SparseSetArray;
@@ -63,8 +65,10 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import java.util.ListIterator;
+import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
@@ -103,6 +107,24 @@
private final SparseArrayMap<String, TreeSet<ForegroundTimeFrame>>
mInstallerForegroundTimeFrames = new SparseArrayMap<>();
+ @VisibleForTesting
+ protected final PackageManagerInternal.PackageListObserver mPackageObserver =
+ 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();
+ }
+ };
+
public BackgroundInstallControlService(@NonNull Context context) {
this(new InjectorImpl(context));
}
@@ -258,6 +280,7 @@
String installerPackageName;
String initiatingPackageName;
+
try {
final InstallSourceInfo installInfo = mPackageManager.getInstallSourceInfo(packageName);
installerPackageName = installInfo.getInstallingPackageName();
@@ -280,7 +303,8 @@
// convert up-time to current time.
final long installTimestamp =
- System.currentTimeMillis() - (SystemClock.uptimeMillis() - appInfo.createTimestamp);
+ System.currentTimeMillis() - (SystemClock.uptimeMillis()
+ - retrieveInstallStartTimestamp(packageName, userId, appInfo));
if (installedByAdb(initiatingPackageName)
|| wasForegroundInstallation(installerPackageName, userId, installTimestamp)) {
@@ -293,6 +317,35 @@
writeBackgroundInstalledPackagesToDisk();
}
+ private long retrieveInstallStartTimestamp(String packageName,
+ int userId, ApplicationInfo appInfo) {
+ long installStartTimestamp = appInfo.createTimestamp;
+
+ try {
+ Optional<PackageInstaller.SessionInfo> latestInstallSession =
+ getLatestInstallSession(packageName, userId);
+ if (latestInstallSession.isEmpty()) {
+ Slog.w(TAG, "Package's historical install session not found, falling back "
+ + "to appInfo.createTimestamp: " + packageName);
+ } else {
+ installStartTimestamp = latestInstallSession.get().getCreatedMillis();
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Retrieval of install time from historical session failed, falling "
+ + "back to appInfo.createTimestamp");
+ Slog.w(TAG, Log.getStackTraceString(e));
+ }
+ return installStartTimestamp;
+ }
+
+ private Optional<PackageInstaller.SessionInfo> getLatestInstallSession(
+ String packageName, int userId) {
+ List<PackageInstaller.SessionInfo> historicalSessions =
+ mPackageManagerInternal.getHistoricalSessions(userId).getList();
+ return historicalSessions.stream().filter(s -> packageName.equals(s.getAppPackageName()))
+ .max(Comparator.comparingLong(PackageInstaller.SessionInfo::getCreatedMillis));
+ }
+
// ADB sets installerPackageName to null, this creates a loophole to bypass BIC which will be
// addressed with b/265203007
private boolean installedByAdb(String initiatingPackageName) {
@@ -496,22 +549,7 @@
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();
- }
- });
+ mPackageManagerInternal.getPackageList(mPackageObserver);
}
// The foreground time frame (ForegroundTimeFrame) represents the period
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index 1ae6e63..0d826df 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -43,8 +43,10 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
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;
@@ -115,9 +117,6 @@
private BackgroundInstallControlCallbackHelper mCallbackHelper;
@Captor
- private ArgumentCaptor<PackageManagerInternal.PackageListObserver> mPackageListObserverCaptor;
-
- @Captor
private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor;
@Before
@@ -137,8 +136,8 @@
mUsageEventListener = mUsageEventListenerCaptor.getValue();
mBackgroundInstallControlService.onStart(true);
- verify(mPackageManagerInternal).getPackageList(mPackageListObserverCaptor.capture());
- mPackageListObserver = mPackageListObserverCaptor.getValue();
+
+ mPackageListObserver = mBackgroundInstallControlService.mPackageObserver;
}
@After
@@ -554,6 +553,7 @@
assertEquals(0, foregroundTimeFrames.size());
}
+ //package installed, but no UI interaction found
@Test
public void testHandleUsageEvent_packageAddedNoUsageEvent()
throws NoSuchFieldException, PackageManager.NameNotFoundException {
@@ -571,12 +571,10 @@
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
@@ -590,6 +588,10 @@
assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
}
+ private long convertToTestAdjustTimestamp(long timestamp) {
+ return timestamp - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+ }
+
@Test
public void testHandleUsageEvent_packageAddedInsideTimeFrame()
throws NoSuchFieldException, PackageManager.NameNotFoundException {
@@ -607,12 +609,10 @@
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
@@ -639,6 +639,122 @@
}
@Test
+ public void testHandleUsageEvent_fallsBackToAppInfoTimeWhenHistoricalSessionsNotFound()
+ throws NoSuchFieldException, PackageManager.NameNotFoundException {
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ InstallSourceInfo installSourceInfo =
+ new InstallSourceInfo(
+ /* initiatingPackageName= */ INSTALLER_NAME_1,
+ /* initiatingPackageSigningInfo= */ null,
+ /* originatingPackageName= */ null,
+ /* installingPackageName= */ INSTALLER_NAME_1);
+ assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
+ when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
+ ApplicationInfo appInfo = mock(ApplicationInfo.class);
+
+ when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+ .thenReturn(appInfo);
+
+ FieldSetter.setField(
+ appInfo,
+ ApplicationInfo.class.getDeclaredField("createTimestamp"),
+ // create timestamp is after generated foreground events (hence not considered
+ // foreground install)
+ convertToTestAdjustTimestamp(USAGE_EVENT_TIMESTAMP_2 + 1));
+
+ int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+ assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+ createPackageManagerHistoricalSessions(List.of(), USER_ID_1);
+
+ // The 2 relevants usage events are before the timeframe, the app is not considered
+ // foreground installed.
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), 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();
+
+ var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+ assertNotNull(packages);
+ assertEquals(1, packages.size());
+ assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+ }
+
+ @Test
+ public void testHandleUsageEvent_usesHistoricalSessionCreateTimeWhenHistoricalSessionsFound()
+ throws NoSuchFieldException, PackageManager.NameNotFoundException {
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ InstallSourceInfo installSourceInfo =
+ new InstallSourceInfo(
+ /* initiatingPackageName= */ INSTALLER_NAME_1,
+ /* initiatingPackageSigningInfo= */ null,
+ /* originatingPackageName= */ null,
+ /* installingPackageName= */ INSTALLER_NAME_1);
+ assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
+ when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
+ ApplicationInfo appInfo = mock(ApplicationInfo.class);
+
+ when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+ .thenReturn(appInfo);
+
+ FieldSetter.setField(
+ appInfo,
+ ApplicationInfo.class.getDeclaredField("createTimestamp"),
+ //create timestamp is out of window of (after) the interact events
+ convertToTestAdjustTimestamp(USAGE_EVENT_TIMESTAMP_2 + 1));
+
+ int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+ assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+ PackageInstaller.SessionInfo installSession1 = mock(PackageInstaller.SessionInfo.class);
+ PackageInstaller.SessionInfo installSession2 = mock(PackageInstaller.SessionInfo.class);
+ doReturn(convertToTestAdjustTimestamp(0L)).when(installSession1).getCreatedMillis();
+ doReturn(convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1)).when(installSession2)
+ .getCreatedMillis();
+ doReturn(PACKAGE_NAME_1).when(installSession1).getAppPackageName();
+ doReturn(PACKAGE_NAME_1).when(installSession2).getAppPackageName();
+ createPackageManagerHistoricalSessions(List.of(installSession1, installSession2),
+ USER_ID_1);
+
+ // The following 2 generated usage events occur after historical session create times hence,
+ // considered foreground install. The appInfo createTimestamp occurs after events, so the
+ // app would be considered background install if it falls back to it as reference create
+ // timestamp.
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), 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());
+ }
+
+ private void createPackageManagerHistoricalSessions(
+ List<PackageInstaller.SessionInfo> sessions, int userId) {
+ ParceledListSlice<PackageInstaller.SessionInfo> mockParcelList =
+ mock(ParceledListSlice.class);
+ when(mockParcelList.getList()).thenReturn(sessions);
+ when(mPackageManagerInternal.getHistoricalSessions(userId)).thenReturn(mockParcelList);
+ }
+
+ @Test
public void testHandleUsageEvent_packageAddedOutsideTimeFrame1()
throws NoSuchFieldException, PackageManager.NameNotFoundException {
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
@@ -655,12 +771,10 @@
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
@@ -708,12 +822,10 @@
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
@@ -765,12 +877,10 @@
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
@@ -818,12 +928,10 @@
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));