Install constraints test - uses libraries

Install constraints should be applied transitively to
apps using libraries

Bug: 235306967
Test: atest CtsStagedInstallHostTestCases
Change-Id: If4e54247d853cb8ba1470a6bb459a74774c24748
diff --git a/hostsidetests/stagedinstall/Android.bp b/hostsidetests/stagedinstall/Android.bp
index 35850fe..35c46be 100644
--- a/hostsidetests/stagedinstall/Android.bp
+++ b/hostsidetests/stagedinstall/Android.bp
@@ -53,6 +53,8 @@
         ":ApexKeyRotationTestV2_SignedBobRotRollback",
         ":ApexKeyRotationTestV3_SignedBob",
         ":ApexKeyRotationTestV3_SignedBobRot",
+        ":HelloWorldSdk1",
+        ":HelloWorldUsingSdk1",
         ":StagedInstallTestApexV1_NotPreInstalled",
         ":StagedInstallTestApexV2_AdditionalFile",
         ":StagedInstallTestApexV2_AdditionalFolder",
diff --git a/hostsidetests/stagedinstall/AndroidTest.xml b/hostsidetests/stagedinstall/AndroidTest.xml
index 5a4074a..75700bc 100644
--- a/hostsidetests/stagedinstall/AndroidTest.xml
+++ b/hostsidetests/stagedinstall/AndroidTest.xml
@@ -33,9 +33,13 @@
         <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
         <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
         <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.S" />
+        <option name="run-command" value="pm uninstall com.test.sdk.user" />
+        <option name="run-command" value="pm uninstall com.test.sdk1_1" />
         <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
         <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
         <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.S" />
+        <option name="teardown-command" value="pm uninstall com.test.sdk.user" />
+        <option name="teardown-command" value="pm uninstall com.test.sdk1_1" />
         <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
         <option name="run-command" value="wm dismiss-keyguard" />
     </target_preparer>
diff --git a/hostsidetests/stagedinstall/app/AndroidManifest.xml b/hostsidetests/stagedinstall/app/AndroidManifest.xml
index b0f548c..61d29c7 100644
--- a/hostsidetests/stagedinstall/app/AndroidManifest.xml
+++ b/hostsidetests/stagedinstall/app/AndroidManifest.xml
@@ -19,6 +19,7 @@
 
     <queries>
         <package android:name="com.android.cts.ctsshim"/>
+        <package android:name="com.test.sdk1_1"/>
     </queries>
 
     <application>
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
index 5e4edc8..07f5fd1 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
@@ -16,6 +16,8 @@
 
 package com.android.tests.stagedinstall;
 
+import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES;
+
 import static com.android.cts.install.lib.InstallUtils.assertStatusFailure;
 import static com.android.cts.install.lib.InstallUtils.assertStatusSuccess;
 import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
@@ -76,6 +78,7 @@
 import java.nio.file.SimpleFileVisitor;
 import java.nio.file.StandardCopyOption;
 import java.nio.file.attribute.BasicFileAttributes;
+import java.security.MessageDigest;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -113,6 +116,8 @@
 
     private static final String TAG = "StagedInstallTest";
 
+    private static final int MATCH_STATIC_SHARED_AND_SDK_LIBRARIES = 0x04000000;
+
     private File mTestStateFile = new File(
             InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(),
             "ctsstagedinstall_state");
@@ -181,6 +186,10 @@
             /*isApex*/true, "com.android.apex.cts.shim.v3_rebootless.apex");
     private static final TestApp Apex2AsApk = new TestApp("Apex2", SHIM_APEX_PACKAGE_NAME, 2,
             /*isApex*/false, "com.android.apex.cts.shim.v2.apex");
+    private static final TestApp HelloWorldSdk1 = new TestApp("HelloWorldSdk1", "com.test.sdk1_1",
+            1, false, "HelloWorldSdk1.apk");
+    private static final TestApp HelloWorldUsingSdk1 = new TestApp("HelloWorldUsingSdk1",
+            "com.test.sdk.user", 1, false, "HelloWorldUsingSdk1.apk");
 
     @Before
     public void adoptShellPermissions() {
@@ -1614,6 +1623,50 @@
     }
 
     @Test
+    public void testCheckInstallConstraints_UsesLibrary() throws Exception {
+        final String propKey = "debug.pm.uses_sdk_library_default_cert_digest";
+
+        try {
+            Install.single(TestApp.B1).commit();
+            Install.single(HelloWorldSdk1).commit();
+            // Override the certificate digest so HelloWorldUsingSdk1 can be installed
+            SystemUtil.runShellCommand("setprop " + propKey + " "
+                    + getPackageCertDigest(HelloWorldSdk1.getPackageName()));
+            Install.single(HelloWorldUsingSdk1).commit();
+
+            // HelloWorldSdk1 will be considered foreground as HelloWorldUsingSdk1 is foreground
+            startActivity(HelloWorldUsingSdk1.getPackageName(),
+                    "com.example.helloworld.MainActivityNoExit");
+
+            var pi = InstallUtils.getPackageInstaller();
+            var f1 = new CompletableFuture<InstallConstraintsResult>();
+            var constraints = new InstallConstraints.Builder().requireAppNotForeground().build();
+            pi.checkInstallConstraints(
+                    Arrays.asList(HelloWorldSdk1.getPackageName()),
+                    constraints,
+                    r -> r.run(),
+                    result -> f1.complete(result));
+            assertThat(f1.join().isAllConstraintsSatisfied()).isFalse();
+
+            // HelloWorldUsingSdk1 is no longer foreground. So is HelloWorldSdk1.
+            startActivity(TestApp.B);
+            PollingCheck.waitFor(() -> {
+                var importance = getPackageImportance(HelloWorldUsingSdk1.getPackageName());
+                return importance > ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+            });
+            var f2 = new CompletableFuture<InstallConstraintsResult>();
+            pi.checkInstallConstraints(
+                    Arrays.asList(HelloWorldSdk1.getPackageName()),
+                    constraints,
+                    r -> r.run(),
+                    result -> f2.complete(result));
+            assertThat(f2.join().isAllConstraintsSatisfied()).isTrue();
+        } finally {
+            SystemUtil.runShellCommand("setprop " + propKey + " invalid");
+        }
+    }
+
+    @Test
     public void testWaitForInstallConstraints_AppIsForeground() throws Exception {
         Install.single(TestApp.A1).commit();
         Install.single(TestApp.B1).commit();
@@ -1658,6 +1711,40 @@
         assertThat(result.isAllConstraintsSatisfied()).isTrue();
     }
 
+    private static byte[] computeSha256DigestBytes(byte[] data) {
+        try {
+            var messageDigest = MessageDigest.getInstance("SHA256");
+            messageDigest.update(data);
+            return messageDigest.digest();
+        } catch (Exception ignore) {
+            /* can't happen */
+            return null;
+        }
+    }
+
+    private static String encodeHex(byte[] data) {
+        final var hexDigits = "0123456789abcdef".toCharArray();
+        int len = data.length;
+        StringBuilder result = new StringBuilder(len * 2);
+        for (int i = 0; i < len; i++) {
+            byte b = data[i];
+            result.append(hexDigits[(b >>> 4) & 0x0f]);
+            result.append(hexDigits[b & 0x0f]);
+        }
+        return result.toString();
+    }
+
+    private String getPackageCertDigest(String packageName) throws Exception {
+        final var pm = InstrumentationRegistry.getInstrumentation()
+                .getContext().getPackageManager();
+        var packageInfo = pm.getPackageInfo(packageName,
+                PackageManager.PackageInfoFlags.of(
+                        GET_SIGNING_CERTIFICATES | MATCH_STATIC_SHARED_AND_SDK_LIBRARIES));
+        var signatures = packageInfo.signingInfo.getSigningCertificateHistory();
+        var digest = computeSha256DigestBytes(signatures[0].toByteArray());
+        return encodeHex(digest);
+    }
+
     private static void startActivity(String packageName) {
         startActivity(packageName, "com.android.cts.install.lib.testapp.MainActivity");
     }
diff --git a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
index 5e33e2c..631c03b 100644
--- a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
@@ -970,6 +970,11 @@
     }
 
     @Test
+    public void testCheckInstallConstraints_UsesLibrary() throws Exception {
+        runPhase("testCheckInstallConstraints_UsesLibrary");
+    }
+
+    @Test
     public void testWaitForInstallConstraints_AppIsForeground() throws Exception {
         runPhase("testWaitForInstallConstraints_AppIsForeground");
     }
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk1.xml b/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk1.xml
index d24276b..6eb0b8a 100644
--- a/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk1.xml
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk1.xml
@@ -17,10 +17,14 @@
              android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:name="com.example.helloworld.MainActivityNoExit"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme.NoActionBar"
+            android:exported="true">
+        </activity>
 
         <uses-sdk-library android:name="com.test.sdk1" android:versionMajor="1" android:certDigest="" />
 
diff --git a/tests/tests/content/HelloWorldApp/sdk_user/com/example/helloworld/MainActivityNoExit.java b/tests/tests/content/HelloWorldApp/sdk_user/com/example/helloworld/MainActivityNoExit.java
new file mode 100644
index 0000000..ea20c63
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/sdk_user/com/example/helloworld/MainActivityNoExit.java
@@ -0,0 +1,29 @@
+/*
+ * 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.example.helloworld;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class MainActivityNoExit extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+}