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