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));