Added uninstall event metric handling and unit tests.

Change-Id: Ibaead8e08cea509610f644779a3e09de6fc44634
Flag: android.app.background_install_control_callback_api
Bug: 374120984
Test: atest
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index e184704..a6b8613 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -966,6 +966,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "android.app.flags-aconfig-java-host",
+    aconfig_declarations: "android.app.flags-aconfig",
+    host_supported: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Broadcast Radio
 aconfig_declarations {
     name: "android.hardware.radio.flags-aconfig",
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index aabbd3b..d57f72c 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -101,6 +101,9 @@
 import java.util.concurrent.Executors;
 import java.util.stream.Collectors;
 
+import com.android.server.pm.BackgroundInstallControlService;
+import com.android.server.pm.BackgroundInstallControlCallbackHelper;
+
 /**
  * @hide
  */
@@ -138,6 +141,10 @@
     static final int MBA_STATUS_NEW_INSTALL = 3;
     // used for indicating newly installed MBAs that are updated (but unused currently)
     static final int MBA_STATUS_UPDATED_NEW_INSTALL = 4;
+    // used for indicating preloaded MBAs that are downgraded
+    static final int MBA_STATUS_DOWNGRADED_PRELOADED = 5;
+    // used for indicating MBAs that are uninstalled
+    static final int MBA_STATUS_UNINSTALLED = 6;
 
     @VisibleForTesting
     static final String KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION =
@@ -202,7 +209,9 @@
          * @param mbaStatus Assign this value of MBA status to the returned elements.
          * @return a @{@code List<IBinaryTransparencyService.AppInfo>}
          */
-        private @NonNull List<IBinaryTransparencyService.AppInfo> collectAppInfo(
+        @VisibleForTesting
+        @NonNull
+        List<IBinaryTransparencyService.AppInfo> collectAppInfo(
                 PackageState packageState, int mbaStatus) {
             // compute content digest
             if (DEBUG) {
@@ -336,27 +345,28 @@
                         + " packages after considering APEXs.");
             }
 
-            // proceed with all preloaded apps
-            List<IBinaryTransparencyService.AppInfo> allUpdatedPreloadInfo =
-                    collectAllUpdatedPreloadInfo(packagesMeasured);
-            for (IBinaryTransparencyService.AppInfo appInfo : allUpdatedPreloadInfo) {
-                packagesMeasured.putBoolean(appInfo.packageName, true);
-                writeAppInfoToLog(appInfo);
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "Measured " + packagesMeasured.size()
-                        + " packages after considering preloads");
-            }
-
-            if (!android.app.Flags.backgroundInstallControlCallbackApi()
-                    && CompatChanges.isChangeEnabled(LOG_MBA_INFO)) {
-                // lastly measure all newly installed MBAs
-                List<IBinaryTransparencyService.AppInfo> allMbaInfo =
-                        collectAllSilentInstalledMbaInfo(packagesMeasured);
-                for (IBinaryTransparencyService.AppInfo appInfo : allMbaInfo) {
+            if (!android.app.Flags.backgroundInstallControlCallbackApi()) {
+                // proceed with all preloaded apps
+                List<IBinaryTransparencyService.AppInfo> allUpdatedPreloadInfo =
+                        collectAllUpdatedPreloadInfo(packagesMeasured);
+                for (IBinaryTransparencyService.AppInfo appInfo : allUpdatedPreloadInfo) {
                     packagesMeasured.putBoolean(appInfo.packageName, true);
                     writeAppInfoToLog(appInfo);
                 }
+                if (DEBUG) {
+                    Slog.d(TAG, "Measured " + packagesMeasured.size()
+                            + " packages after considering preloads");
+                }
+
+                if (CompatChanges.isChangeEnabled(LOG_MBA_INFO)) {
+                    // lastly measure all newly installed MBAs
+                    List<IBinaryTransparencyService.AppInfo> allMbaInfo =
+                            collectAllSilentInstalledMbaInfo(packagesMeasured);
+                    for (IBinaryTransparencyService.AppInfo appInfo : allMbaInfo) {
+                        packagesMeasured.putBoolean(appInfo.packageName, true);
+                        writeAppInfoToLog(appInfo);
+                    }
+                }
             }
             long timeSpentMeasuring = System.currentTimeMillis() - currentTimeMs;
             digestAllPackagesLatency.logSample(timeSpentMeasuring);
@@ -466,7 +476,8 @@
                     apexInfo.signerDigests);
         }
 
-        private void writeAppInfoToLog(IBinaryTransparencyService.AppInfo appInfo) {
+        @VisibleForTesting
+        void writeAppInfoToLog(IBinaryTransparencyService.AppInfo appInfo) {
             // Must order by the proto's field number.
             FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
                     appInfo.packageName,
@@ -1165,40 +1176,85 @@
      * TODO: Add a host test for testing registration and callback of BicCallbackHandler
      *  b/380002484
      */
+    @VisibleForTesting
     static class BicCallbackHandler extends IRemoteCallback.Stub {
-        private static final String BIC_CALLBACK_HANDLER_TAG =
-                "BTS.BicCallbackHandler";
-        private final BinaryTransparencyServiceImpl mServiceImpl;
-        static final String FLAGGED_PACKAGE_NAME_KEY = "packageName";
+        private static final String BIC_CALLBACK_HANDLER_TAG = TAG + ".BicCallbackHandler";
 
-        BicCallbackHandler(BinaryTransparencyServiceImpl impl) {
-            mServiceImpl = impl;
+        private static final int INSTALL_EVENT_TYPE_UNSET = -1;
+
+        private final IBicAppInfoHelper mBicAppInfoHelper;
+
+        @VisibleForTesting
+        BicCallbackHandler(IBicAppInfoHelper bicAppInfoHelper) {
+            mBicAppInfoHelper = bicAppInfoHelper;
         }
 
         @Override
         public void sendResult(Bundle data) {
-            String packageName = data.getString(FLAGGED_PACKAGE_NAME_KEY);
-            if (packageName == null) return;
-            if (DEBUG) {
-                Slog.d(BIC_CALLBACK_HANDLER_TAG, "background install event detected for "
-                        + packageName);
+            String packageName = data.getString(
+                    BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY);
+            int installType = data.getInt(
+                    BackgroundInstallControlCallbackHelper.INSTALL_EVENT_TYPE_KEY,
+                    INSTALL_EVENT_TYPE_UNSET);
+            if (packageName == null || installType == INSTALL_EVENT_TYPE_UNSET) {
+                Slog.w(BIC_CALLBACK_HANDLER_TAG, "Package name or install type is "
+                        + "unavailable, ignoring event");
+                return;
             }
-
-            PackageState packageState = LocalServices.getService(PackageManagerInternal.class)
+            Slog.d(BIC_CALLBACK_HANDLER_TAG, "Detected new bic event for: " + packageName);
+            if (installType == BackgroundInstallControlService.INSTALL_EVENT_TYPE_INSTALL) {
+                PackageState packageState = LocalServices.getService(PackageManagerInternal.class)
                     .getPackageStateInternal(packageName);
-            if (packageState == null) {
-                Slog.w(TAG, "Package state is unavailable, ignoring the package "
-                        + packageName);
-                return;
+                if (packageState == null) {
+                    Slog.w(TAG, "Package state is unavailable, ignoring the package "
+                            + packageName);
+                    return;
+                }
+                int mbaStatus = MBA_STATUS_NEW_INSTALL;
+                if (packageState.isUpdatedSystemApp()) {
+                    mbaStatus = MBA_STATUS_UPDATED_PRELOAD;
+                }
+                List<IBinaryTransparencyService.AppInfo> mbaInfo = mBicAppInfoHelper.collectAppInfo(
+                        packageState, mbaStatus);
+                for (IBinaryTransparencyService.AppInfo appInfo : mbaInfo) {
+                    mBicAppInfoHelper.writeAppInfoToLog(appInfo);
+                }
+            } else if (installType
+                        == BackgroundInstallControlService.INSTALL_EVENT_TYPE_UNINSTALL) {
+                IBinaryTransparencyService.AppInfo appInfo
+                    = new IBinaryTransparencyService.AppInfo();
+                // since app is already uninstalled we won't be able to retrieve additional
+                // info on it.
+                appInfo.packageName = packageName;
+                appInfo.mbaStatus = MBA_STATUS_UNINSTALLED;
+                mBicAppInfoHelper.writeAppInfoToLog(appInfo);
+            } else {
+                Slog.w(BIC_CALLBACK_HANDLER_TAG, "Unsupported BIC event: " + installType);
             }
-            if (packageState.isUpdatedSystemApp()) {
-                return;
-            }
-            List<IBinaryTransparencyService.AppInfo> mbaInfo = mServiceImpl.collectAppInfo(
-                    packageState, MBA_STATUS_NEW_INSTALL);
-            for (IBinaryTransparencyService.AppInfo appInfo : mbaInfo) {
-                mServiceImpl.writeAppInfoToLog(appInfo);
-            }
+        }
+
+        /**
+         * A wrapper of interface for{@link FrameworkStatsLog and ApkDigests}
+         * for easier testing
+         */
+        @VisibleForTesting
+        public interface IBicAppInfoHelper {
+
+            /**
+             * A wrapper of {@link FrameworkStatsLog}
+             *
+             * @param appInfo The app info of the changed MBA to be logged
+             */
+            public void writeAppInfoToLog(IBinaryTransparencyService.AppInfo appInfo);
+
+            /**
+             * A wrapper of {@link BinaryTransparencyServiceImpl}
+             *
+             * @param packageState The packageState provided retrieved from PackageManagerInternal
+             * @param mbaStatus The MBA status of the package
+             */
+            public List<IBinaryTransparencyService.AppInfo> collectAppInfo(
+                PackageState packageState, int mbaStatus);
         }
     };
 
@@ -1586,7 +1642,21 @@
                                 Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
         try {
             iBics.registerBackgroundInstallCallback(
-                    new BicCallbackHandler(mServiceImpl));
+                    new BicCallbackHandler(
+                        new BicCallbackHandler.IBicAppInfoHelper() {
+                            @Override
+                            public void writeAppInfoToLog(
+                                    IBinaryTransparencyService.AppInfo appInfo) {
+                                mServiceImpl.writeAppInfoToLog(appInfo);
+                            }
+
+                            @Override
+                            public List<IBinaryTransparencyService.AppInfo> collectAppInfo(
+                                PackageState packageState, int mbaStatus) {
+                                return mServiceImpl.collectAppInfo(packageState, mbaStatus);
+                            }
+                        }
+                    ));
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to register BackgroundInstallControl callback.");
         }
@@ -1633,8 +1703,12 @@
             }
 
             String packageName = data.getSchemeSpecificPart();
-            // now we've got to check what package is this
-            if (isPackagePreloaded(packageName) || isPackageAnApex(packageName)) {
+
+            boolean shouldMeasureMba =
+                !android.app.Flags.backgroundInstallControlCallbackApi()
+                && isPackagePreloaded(packageName);
+
+            if (shouldMeasureMba || isPackageAnApex(packageName)) {
                 Slog.d(TAG, packageName + " was updated. Scheduling measurement...");
                 UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
                         BinaryTransparencyService.this);
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
index 27c4e9d..bc0fc2b 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java
@@ -33,9 +33,10 @@
 
 public class BackgroundInstallControlCallbackHelper {
 
-    @VisibleForTesting static final String FLAGGED_PACKAGE_NAME_KEY = "packageName";
-    @VisibleForTesting static final String FLAGGED_USER_ID_KEY = "userId";
-    @VisibleForTesting static final String INSTALL_EVENT_TYPE_KEY = "installEventType";
+    public static final String FLAGGED_PACKAGE_NAME_KEY = "packageName";
+    public static final String FLAGGED_USER_ID_KEY = "userId";
+    public static final String INSTALL_EVENT_TYPE_KEY = "installEventType";
+
     private static final String TAG = "BackgroundInstallControlCallbackHelper";
 
     private final Handler mHandler;
diff --git a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
index ae78dfe..cc5be7e 100644
--- a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
@@ -20,6 +20,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -40,6 +41,7 @@
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemProperties;
@@ -50,6 +52,12 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.os.IBinaryTransparencyService;
+import com.android.server.pm.BackgroundInstallControlService;
+import com.android.server.pm.BackgroundInstallControlCallbackHelper;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageStateInternal;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -68,6 +76,9 @@
 public class BinaryTransparencyServiceTest {
     private static final String TAG = "BinaryTransparencyServiceTest";
 
+    private static final String TEST_PKG_NAME = "testPackageName";
+    private static final long TEST_VERSION_CODE = 1L;
+
     private Context mContext;
     private BinaryTransparencyService mBinaryTransparencyService;
     private BinaryTransparencyService.BinaryTransparencyServiceImpl mTestInterface;
@@ -83,6 +94,8 @@
     private PackageManager mPackageManager;
     @Mock
     private PackageManagerInternal mPackageManagerInternal;
+    @Mock
+    private BinaryTransparencyService.BicCallbackHandler.IBicAppInfoHelper mBicAppInfoHelper;
 
     @Captor
     private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback>
@@ -91,6 +104,9 @@
     private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback>
             mFaceAuthenticatorsRegisteredCaptor;
 
+    @Captor
+    private ArgumentCaptor<IBinaryTransparencyService.AppInfo> appInfoCaptor;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -262,4 +278,69 @@
                 eq("") /* softwareVersion */
         );
     }
+
+    @Test
+    public void BicCallbackHandler_uploads_mba_metrics() {
+        Bundle data = setupBicCallbackHandlerTest(false,
+            BinaryTransparencyService.MBA_STATUS_NEW_INSTALL);
+
+        BinaryTransparencyService.BicCallbackHandler handler =
+            new BinaryTransparencyService.BicCallbackHandler(mBicAppInfoHelper);
+        handler.sendResult(data);
+
+        verify(mBicAppInfoHelper, times(1)).writeAppInfoToLog(appInfoCaptor.capture());
+        Assert.assertEquals(TEST_PKG_NAME, appInfoCaptor.getValue().packageName);
+        Assert.assertEquals(TEST_VERSION_CODE, appInfoCaptor.getValue().longVersion);
+    }
+
+    @Test
+    public void BicCallbackHandler_uploads_mba_metrics_for_preloads() {
+        Bundle data = setupBicCallbackHandlerTest(true,
+            BinaryTransparencyService.MBA_STATUS_UPDATED_PRELOAD);
+
+        BinaryTransparencyService.BicCallbackHandler handler =
+            new BinaryTransparencyService.BicCallbackHandler(mBicAppInfoHelper);
+        handler.sendResult(data);
+
+        verify(mBicAppInfoHelper, times(1)).writeAppInfoToLog(appInfoCaptor.capture());
+        Assert.assertEquals(TEST_PKG_NAME, appInfoCaptor.getValue().packageName);
+        Assert.assertEquals(TEST_VERSION_CODE, appInfoCaptor.getValue().longVersion);
+    }
+
+    @Test
+    public void BicCallbackHandler_uploads_mba_metrics_for_uninstalls() {
+        Bundle data = new Bundle();
+        data.putString(BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY,
+            TEST_PKG_NAME);
+        data.putInt(BackgroundInstallControlCallbackHelper.INSTALL_EVENT_TYPE_KEY,
+            BackgroundInstallControlService.INSTALL_EVENT_TYPE_UNINSTALL);
+
+        BinaryTransparencyService.BicCallbackHandler handler =
+                new BinaryTransparencyService.BicCallbackHandler(mBicAppInfoHelper);
+        handler.sendResult(data);
+
+        verify(mBicAppInfoHelper, times(1)).writeAppInfoToLog(appInfoCaptor.capture());
+        Assert.assertEquals(TEST_PKG_NAME ,appInfoCaptor.getValue().packageName);
+        Assert.assertEquals(BinaryTransparencyService.MBA_STATUS_UNINSTALLED,
+            appInfoCaptor.getValue().mbaStatus);
+    }
+
+    private Bundle setupBicCallbackHandlerTest(boolean isUpdatedSystemApp,
+            int expectedBtsMbaStatus) {
+        Bundle data = new Bundle();
+        data.putString(BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY,
+            TEST_PKG_NAME);
+        data.putInt(BackgroundInstallControlCallbackHelper.INSTALL_EVENT_TYPE_KEY,
+            BackgroundInstallControlService.INSTALL_EVENT_TYPE_INSTALL);
+        PackageStateInternal mockPackageState = mock(PackageStateInternal.class);
+        when(mPackageManagerInternal.getPackageStateInternal(TEST_PKG_NAME))
+            .thenReturn(mockPackageState);
+        when(mockPackageState.isUpdatedSystemApp()).thenReturn(isUpdatedSystemApp);
+        IBinaryTransparencyService.AppInfo appInfo = new IBinaryTransparencyService.AppInfo();
+        appInfo.packageName = TEST_PKG_NAME;
+        appInfo.longVersion = TEST_VERSION_CODE;
+        when(mBicAppInfoHelper.collectAppInfo(mockPackageState, expectedBtsMbaStatus))
+            .thenReturn(List.of(appInfo));
+        return data;
+    }
 }
diff --git a/tests/BinaryTransparencyHostTest/Android.bp b/tests/BinaryTransparencyHostTest/Android.bp
index e14e5fe..1c8386a 100644
--- a/tests/BinaryTransparencyHostTest/Android.bp
+++ b/tests/BinaryTransparencyHostTest/Android.bp
@@ -31,6 +31,8 @@
     ],
     static_libs: [
         "truth",
+        "flag-junit-host",
+        "android.app.flags-aconfig-java-host",
     ],
     device_common_data: [
         ":BinaryTransparencyTestApp",
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
index 6e5f08a..6d8dbcb 100644
--- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
@@ -24,6 +24,9 @@
 
 import android.platform.test.annotations.LargeTest;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.host.HostFlagsValueProvider;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -34,6 +37,7 @@
 import com.android.tradefed.util.CommandStatus;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -49,6 +53,10 @@
     /** Waiting time for the job to be scheduled */
     private static final int JOB_CREATION_MAX_SECONDS = 30;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            HostFlagsValueProvider.createCheckFlagsRule(this::getDevice);
+
     @Before
     public void setUp() throws Exception {
         cancelPendingJob();
@@ -123,6 +131,7 @@
         }
     }
 
+    @RequiresFlagsDisabled(android.app.Flags.FLAG_BACKGROUND_INSTALL_CONTROL_CALLBACK_API)
     @Test
     public void testPreloadUpdateTriggersJobScheduling() throws Exception {
         try {