diff options
Diffstat (limited to 'tests')
482 files changed, 23802 insertions, 10969 deletions
diff --git a/tests/ActivityManagerPerfTests/stub-app/AndroidManifest.xml b/tests/ActivityManagerPerfTests/stub-app/AndroidManifest.xml index 6bdeea06053f..02d03bbfdb1e 100644 --- a/tests/ActivityManagerPerfTests/stub-app/AndroidManifest.xml +++ b/tests/ActivityManagerPerfTests/stub-app/AndroidManifest.xml @@ -19,6 +19,12 @@ package="com.android.stubs.am"> <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/> + <queries> + <intent> + <action android:name="com.android.stubs.am.ACTION_BROADCAST_TEST"/> + </intent> + </queries> + <application android:label="Android TestCase"> <provider android:authorities="@string/authority" android:name=".TestContentProvider" diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java index daff76f4f522..5d4a4efecf83 100644 --- a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java +++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java @@ -61,10 +61,11 @@ public class BasePerfTest { return intent; } - protected Intent createBroadcastIntent(String action) { + protected Intent createFgBroadcastIntent(String action) { final Intent intent = new Intent(action); - intent.addFlags( - Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND | Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND + | Intent.FLAG_INCLUDE_STOPPED_PACKAGES); putTimeReceiverBinderExtra(intent); return intent; } diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BroadcastPerfTest.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BroadcastPerfTest.java index bc528d4d4fb7..6a535759c699 100644 --- a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BroadcastPerfTest.java +++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BroadcastPerfTest.java @@ -30,11 +30,11 @@ import org.junit.runner.RunWith; @LargeTest public class BroadcastPerfTest extends BasePerfTest { @Test - public void manifestBroadcastRunning() { + public void manifestFgBroadcastRunning() { runPerfFunction(() -> { startTargetPackage(); - final Intent intent = createBroadcastIntent( + final Intent intent = createFgBroadcastIntent( Constants.ACTION_BROADCAST_MANIFEST_RECEIVE); final long startTime = System.nanoTime(); @@ -48,9 +48,9 @@ public class BroadcastPerfTest extends BasePerfTest { } @Test - public void manifestBroadcastNotRunning() { + public void manifestFgBroadcastNotRunning() { runPerfFunction(() -> { - final Intent intent = createBroadcastIntent( + final Intent intent = createFgBroadcastIntent( Constants.ACTION_BROADCAST_MANIFEST_RECEIVE); final long startTime = System.nanoTime(); @@ -64,11 +64,11 @@ public class BroadcastPerfTest extends BasePerfTest { } @Test - public void registeredBroadcast() { + public void registeredFgBroadcast() { runPerfFunction(() -> { startTargetPackage(); - final Intent intent = createBroadcastIntent( + final Intent intent = createFgBroadcastIntent( Constants.ACTION_BROADCAST_REGISTERED_RECEIVE); final long startTime = System.nanoTime(); diff --git a/tests/ActivityManagerPerfTests/utils/Android.bp b/tests/ActivityManagerPerfTests/utils/Android.bp index 99c43c8d8fca..5902c1cbda15 100644 --- a/tests/ActivityManagerPerfTests/utils/Android.bp +++ b/tests/ActivityManagerPerfTests/utils/Android.bp @@ -32,6 +32,6 @@ java_test { static_libs: [ "androidx.test.rules", "junit", - "ub-uiautomator", + "androidx.test.uiautomator_uiautomator", ], } diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java index fc787bafa93a..9bd94f2a9a1e 100644 --- a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java +++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java @@ -19,10 +19,10 @@ package com.android.frameworks.perftests.am.util; import android.content.Intent; import android.os.RemoteException; import android.os.ResultReceiver; -import android.support.test.uiautomator.UiDevice; import android.util.Log; import androidx.test.InstrumentationRegistry; +import androidx.test.uiautomator.UiDevice; import java.io.IOException; diff --git a/tests/ApkVerityTest/AndroidTest.xml b/tests/ApkVerityTest/AndroidTest.xml index 39b75cc27acb..4487cefb4f9c 100644 --- a/tests/ApkVerityTest/AndroidTest.xml +++ b/tests/ApkVerityTest/AndroidTest.xml @@ -16,6 +16,11 @@ <configuration description="APK fs-verity integration/regression test"> <option name="test-suite-tag" value="apct" /> + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController"> + <!-- fs-verity is required since R/30 --> + <option name="vsr-min-api-level" value="30" /> + </object> + <!-- This test requires root to write against block device. --> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> @@ -43,6 +48,8 @@ <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" /> </target_preparer> + <!-- Skip on HWASan. TODO(b/232288278): Re-enable --> + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.SkipHWASanModuleController" /> <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" > <option name="jar" value="ApkVerityTest.jar" /> </test> diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java index d96005b8a71a..591ffeb39721 100644 --- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java +++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java @@ -25,7 +25,6 @@ import static org.junit.Assert.fail; import android.platform.test.annotations.RootPermissionTest; import com.android.blockdevicewriter.BlockDeviceWriter; -import com.android.fsverity.AddFsVerityCertRule; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.log.LogUtil.CLog; @@ -36,7 +35,6 @@ import com.android.tradefed.util.CommandStatus; import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -44,6 +42,7 @@ import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.Set; /** * This test makes sure app installs with fs-verity signature, and on-access verification works. @@ -90,10 +89,6 @@ public class ApkVerityTest extends BaseHostJUnit4Test { /** Only 4K page is supported by fs-verity currently. */ private static final int FSVERITY_PAGE_SIZE = 4096; - @Rule - public final AddFsVerityCertRule mAddFsVerityCertRule = - new AddFsVerityCertRule(this, CERT_PATH); - private ITestDevice mDevice; private boolean mDmRequireFsVerity; @@ -103,11 +98,13 @@ public class ApkVerityTest extends BaseHostJUnit4Test { mDmRequireFsVerity = "true".equals( mDevice.getProperty("pm.dexopt.dm.require_fsverity")); + expectRemoteCommandToSucceed("cmd file_integrity append-cert " + CERT_PATH); uninstallPackage(TARGET_PACKAGE); } @After public void tearDown() throws DeviceNotAvailableException { + expectRemoteCommandToSucceed("cmd file_integrity remove-last-cert"); uninstallPackage(TARGET_PACKAGE); } @@ -469,10 +466,10 @@ public class ApkVerityTest extends BaseHostJUnit4Test { break; } try { - CLog.d("lsof: " + expectRemoteCommandToSucceed("lsof " + apkPath)); + String openFiles = expectRemoteCommandToSucceed("lsof " + apkPath); + CLog.d("lsof: " + openFiles); Thread.sleep(1000); - String pid = expectRemoteCommandToSucceed("pidof system_server"); - mDevice.executeShellV2Command("kill -10 " + pid); // force GC + forceGCOnOpenFilesProcess(getOpenFilesPIDs(openFiles)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; @@ -482,6 +479,35 @@ public class ApkVerityTest extends BaseHostJUnit4Test { } } + /** + * This is a helper method that parses the lsof output to get PIDs of process holding FD. + * Here is an example output of lsof. This method extracts the second columns(PID). + * + * Example lsof output: + * COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME + * .example.app 1063 u0_a38 mem REG 253,6 8599 12826 example.apk + * .example.app 1063 u0_a38 99r REG 253,6 8599 12826 example.apk + */ + private Set<String> getOpenFilesPIDs(String lsof) { + Set<String> openFilesPIDs = new HashSet<>(); + String[] lines = lsof.split("\n"); + for (int i = 1; i < lines.length; i++) { + openFilesPIDs.add(lines[i].split("\\s+")[1]); + } + return openFilesPIDs; + } + + /** + * This is a helper method that forces GC on processes given their PIDs. + * That is to execute shell command "kill -10" on PIDs. + */ + private void forceGCOnOpenFilesProcess(Set<String> openFilesPIDs) + throws DeviceNotAvailableException { + for (String openFilePID : openFilesPIDs) { + mDevice.executeShellV2Command("kill -10 " + openFilePID); + } + } + private void verifyInstalledFiles(String... filenames) throws DeviceNotAvailableException { String apkPath = getApkPath(TARGET_PACKAGE); String appDir = apkPath.substring(0, apkPath.lastIndexOf("/")); diff --git a/tests/AttestationVerificationTest/assets/test_owned_by_system_certs.pem b/tests/AttestationVerificationTest/assets/test_owned_by_system_certs.pem new file mode 100644 index 000000000000..34d55fffc6bf --- /dev/null +++ b/tests/AttestationVerificationTest/assets/test_owned_by_system_certs.pem @@ -0,0 +1,81 @@ +-----BEGIN CERTIFICATE----- +MIIClzCCAjygAwIBAgIBATAKBggqhkjOPQQDAjA5MQwwCgYDVQQMDANURUUxKTAn +BgNVBAUTIDRiY2Q3MzM5MjZmZDkwYjhlMDE1ZDczYmIxYmE0MjZhMCAXDTIzMDIw +OTAwMDQwMloYDzIyOTYxMTI0MDAwNDAyWjAfMR0wGwYDVQQDExRBbmRyb2lkIEtl +eXN0b3JlIEtleTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAxz70D0rX31tqv+ +mMjyet+KlfVF4h5zJeHVP6BPtqP9AM/l0KQEuttYEKbtmw4k/phS9hdjHoiitUTO +7gD5gRqjggFLMIIBRzAOBgNVHQ8BAf8EBAMCB4AwggEzBgorBgEEAdZ5AgERBIIB +IzCCAR8CAgDICgEBAgIAyAoBAQQRYWN0aXZlVW5sb2NrVmFsaWQEADBQv4MQCAIG +AYYzfH33v4MRCAIGCWHbnf33v4MSCAIGCWHbnf33v4U9CAIGAYYzfICnv4VFHAQa +MBgxFDASBA1BbmRyb2lkU3lzdGVtAgEBMQAwgaehCDEGAgECAgEDogMCAQOjBAIC +AQClCDEGAgEEAgEGqgMCAQG/g3cCBQC/hT4DAgEAv4VATDBKBCAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAoBAgQguYuTO2l0Dwq5FwpSl+Selr+Y +ky0NvCPbBXFMqRVWStW/hUEFAgMB+9C/hUIFAgMDFj+/hU4GAgQBNLChv4VPBgIE +ATSwoTAKBggqhkjOPQQDAgNJADBGAiEA9+6Y5LEvdxER46O3V+2H4MYn1ILLJk56 +Uo5uGZqbIfECIQDtITu0l4fKeTVE3sQo50oFd4iCVKVp62PlpTEJ+D1hOQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIB8zCCAXmgAwIBAgIQD9aWSuFM+VgqbBcSDskVWjAKBggqhkjOPQQDAjA5MQww +CgYDVQQMDANURUUxKTAnBgNVBAUTIDRiODI4ZTI2MjM2YjRmMGJiNjIwZWRiZjI4 +MTRiMmQyMB4XDTIxMDExMzIwNTkxOVoXDTMxMDExMTIwNTkxOVowOTEMMAoGA1UE +DAwDVEVFMSkwJwYDVQQFEyA0YmNkNzMzOTI2ZmQ5MGI4ZTAxNWQ3M2JiMWJhNDI2 +YTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKhknO2s1yuEK9NXTneVzwHXVL9N ++AyjLIakshRvMupH341gZNI8H9nuGomXhfPLH4igCB50IIdpjZyUe87DWrmjYzBh +MB0GA1UdDgQWBBSVB/mrPxEP0okhRNeyAxgtM1KDlzAfBgNVHSMEGDAWgBRGO8we +F/IGW5HwMQUf5yM8ZmQ/JjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIC +BDAKBggqhkjOPQQDAgNoADBlAjEA0+6jQMnBNqmzHGRTjrq6bC5PHlF/nN4FOLt3 +a8HhiiXAKddnq38PBI5JBM/+kT7jAjBMpt56pMdwDWag+c+FCB1wCtRvIVic6ATU +EvY+ZsuRQ0d1ZGvfO3s79j9T/xv7B7E= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDkzCCAXugAwIBAgIQBMTs9wahoh5aI97uDtkVIjANBgkqhkiG9w0BAQsFADAb +MRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MB4XDTIxMDExMzIwNTgyM1oXDTMx +MDExMTIwNTgyM1owOTEMMAoGA1UEDAwDVEVFMSkwJwYDVQQFEyA0YjgyOGUyNjIz +NmI0ZjBiYjYyMGVkYmYyODE0YjJkMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJTy +ijqyb9Ay9rys3DDQgn2Lr8n/NDzKmbmITHWWrnbc2POKyCBzcBXo597ewSyLgQcp +CKSW7R2vRzTWvxFHVNRdEM1H3k4OKhQS8VpTVeHIlGsN37G6jXJJpFhHOW40uqNj +MGEwHQYDVR0OBBYEFEY7zB4X8gZbkfAxBR/nIzxmZD8mMB8GA1UdIwQYMBaAFDZh +4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgIEMA0GCSqGSIb3DQEBCwUAA4ICAQBFoMIAhtEFxu5YUKD7WJj4X8nTRAK7W1Li +X3AssR769CyrNO5OttkU+5LQb8EGyGs90OhTy/eA7m1sPAh4mMHV5yL+td/Sdg2j +fZ2ZBayeZuteihaLwB9SpHKUPR3+VDMPBNevUWLpjiwRfbNzb1A40LlRIsfFFs2s +I33WVcpEH5KAj5ci7UtRIF8ryw3FyFNsbHqvdVf1Wet4JosIhnbZuOruB4qUq8oW +ZOi6nXBKnY+ebdZusPRUE/6h8pDS5xZrN/is2HmFDXuEjuMibw1bZirQ5cygn+fQ +DUjjK+4/k/IlfoLL8A07XdrtlYynMZm88FzB5tkwdwXkJTW8l+vZv6fhXv34go6N +MoZNpLZZaoJDckvpPP4oXdpOLtUJyaNoMWPtn97q8eQklkHK1e8SSfYN5GShCxOF +wC+797rRfm1qG2CLZ3lMsI70AGOFAyb76VS0s8vpTjDbU5RBHQqfZ1HbYzRSt8GS +4QlOeJx71IZjI8F3BhkjrQncjKLrcF+lDUA/AVuezn2kxAAeNiNHYWrDq+C6Odaj +PIr4DDrtfgsnemBy7TG2eubWmc761DVqtgP/To7QySg7vbwqNsX3BcJsE0AeD1Jy +0BhfNgvhhq9N8zlzLhv4dAHK/HiRQdtb7Z6qEPawihXxY5so3R+K4shfHet98Ri9 +naCVlMdaOg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFHDCCAwSgAwIBAgIJANUP8luj8tazMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV +BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTkxMTIyMjAzNzU4WhcNMzQxMTE4MjAz +NzU4WjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS +Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7 +tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj +nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq +C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ +oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O +JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg +sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi +igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M +RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E +aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um +AGMCAwEAAaNjMGEwHQYDVR0OBBYEFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMB8GA1Ud +IwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4ICAQBOMaBc8oumXb2voc7XCWnu +XKhBBK3e2KMGz39t7lA3XXRe2ZLLAkLM5y3J7tURkf5a1SutfdOyXAmeE6SRo83U +h6WszodmMkxK5GM4JGrnt4pBisu5igXEydaW7qq2CdC6DOGjG+mEkN8/TA6p3cno +L/sPyz6evdjLlSeJ8rFBH6xWyIZCbrcpYEJzXaUOEaxxXxgYz5/cTiVKN2M1G2ok +QBUIYSY6bjEL4aUN5cfo7ogP3UvliEo3Eo0YgwuzR2v0KR6C1cZqZJSTnghIC/vA +D32KdNQ+c3N+vl2OTsUVMC1GiWkngNx1OO1+kXW+YTnnTUOtOIswUP/Vqd5SYgAI +mMAfY8U9/iIgkQj6T2W6FsScy94IN9fFhE1UtzmLoBIuUFsVXJMTz+Jucth+IqoW +Fua9v1R93/k98p41pjtFX+H8DslVgfP097vju4KDlqN64xV1grw3ZLl4CiOe/A91 +oeLm2UHOq6wn3esB4r2EIQKb6jTVGu5sYCcdWpXr0AUVqcABPdgL+H7qJguBw09o +jm6xNIrw2OocrDKsudk/okr/AwqEyPKw9WnMlQgLIKw1rODG2NvU9oR3GVGdMkUB +ZutL8VuFkERQGt6vQ2OCw0sV47VMkuYbacK/xyZFiRcrPJPb41zgbQj9XAEyLKCH +ex0SdDrx+tWUDqG8At2JHA== +-----END CERTIFICATE----- diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt index 45f2e5c6fdf7..dfbbda6c6f5e 100644 --- a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt +++ b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt @@ -89,6 +89,34 @@ class AttestationVerificationPeerDeviceVerifierTest { } @Test + fun verifyAttestation_returnsSuccessOwnedBySystem() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 1, 1)) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "activeUnlockValid".encodeToByteArray()) + challengeRequirements.putBoolean("android.key_owned_by_system", true) + + val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, + TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray()) + assertThat(result).isEqualTo(RESULT_SUCCESS) + } + + @Test + fun verifyAttestation_returnsFailureOwnedBySystem() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 1, 1)) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + challengeRequirements.putBoolean("android.key_owned_by_system", true) + + val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + assertThat(result).isEqualTo(RESULT_FAILURE) + } + + @Test fun verifyAttestation_returnsFailurePatchDateNotWithinOneYearLocalPatch() { val verifier = AttestationVerificationPeerDeviceVerifier( context, trustAnchors, false, LocalDate.of(2023, 3, 1), @@ -171,5 +199,6 @@ class AttestationVerificationPeerDeviceVerifierTest { private const val TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME = "test_attestation_with_root_certs.pem" private const val TEST_ATTESTATION_CERT_FILENAME = "test_attestation_wrong_root_certs.pem" + private const val TEST_OWNED_BY_SYSTEM_FILENAME = "test_owned_by_system_certs.pem" } } diff --git a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java index 90ddb6ffb34a..81efda17abe3 100644 --- a/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java +++ b/tests/BackgroundDexOptServiceIntegrationTests/src/com/android/server/pm/BackgroundDexOptServiceIntegrationTests.java @@ -16,6 +16,8 @@ package com.android.server.pm; +import static org.junit.Assume.assumeFalse; + import android.app.AlarmManager; import android.content.Context; import android.os.Environment; @@ -31,6 +33,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -111,6 +114,7 @@ public final class BackgroundDexOptServiceIntegrationTests { @Before public void setUp() throws IOException { + assumeFalse(SystemProperties.getBoolean("dalvik.vm.useartservice", false)); File dataDir = getContext().getDataDir(); mBigFile = new File(dataDir, BIG_FILE); } @@ -299,6 +303,8 @@ public final class BackgroundDexOptServiceIntegrationTests { // Test that background dexopt under low storage conditions downgrades unused packages. @Test + @Ignore("b/251438180: This test has been failing for a long time; temporarily disable it while" + + " we investigate this issue.") public void testBackgroundDexOptDowngradeSuccessful() throws IOException { // Should be more than DOWNGRADE_AFTER_DAYS. long deltaDays = DOWNGRADE_AFTER_DAYS + 1; diff --git a/tests/BinaryTransparencyHostTest/Android.bp b/tests/BinaryTransparencyHostTest/Android.bp new file mode 100644 index 000000000000..dc6bdff6716c --- /dev/null +++ b/tests/BinaryTransparencyHostTest/Android.bp @@ -0,0 +1,43 @@ +// Copyright (C) 2023 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +java_test_host { + name: "BinaryTransparencyHostTest", + srcs: ["src/**/*.java"], + libs: [ + "tradefed", + "compatibility-tradefed", + "compatibility-host-util", + ], + static_libs: [ + "truth-prebuilt", + ], + data: [ + ":BinaryTransparencyTestApp", + ":EasterEgg", + ":com.android.apex.cts.shim.v2_rebootless_prebuilt", + ], + test_suites: [ + "general-tests", + ], +} diff --git a/tests/BinaryTransparencyHostTest/AndroidTest.xml b/tests/BinaryTransparencyHostTest/AndroidTest.xml new file mode 100644 index 000000000000..e0d11c0c1097 --- /dev/null +++ b/tests/BinaryTransparencyHostTest/AndroidTest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 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. +--> +<configuration description="Binary Transparency integration test"> + <option name="test-suite-tag" value="apct" /> + + <!-- Service is not exposed to apps. Disable SELinux for testing purpose. --> + <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="BinaryTransparencyTestApp.apk" /> + </target_preparer> + + <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" > + <option name="jar" value="BinaryTransparencyHostTest.jar" /> + <option name="runtime-hint" value="1m" /> + </test> +</configuration> diff --git a/tests/BinaryTransparencyHostTest/OWNERS b/tests/BinaryTransparencyHostTest/OWNERS new file mode 100644 index 000000000000..ca84550c76a4 --- /dev/null +++ b/tests/BinaryTransparencyHostTest/OWNERS @@ -0,0 +1 @@ +include /core/java/android/transparency/OWNERS diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BaseInstallMultiple.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BaseInstallMultiple.java new file mode 100644 index 000000000000..3e94f25b5fc7 --- /dev/null +++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BaseInstallMultiple.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2019 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 android.transparency.test; + +import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; + +import junit.framework.TestCase; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Base class for invoking the install-multiple command via ADB. Subclass this for less typing: + * + * <code> private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> { public + * InstallMultiple() { super(getDevice(), null); } } </code> + */ +/*package*/ class BaseInstallMultiple<T extends BaseInstallMultiple<?>> { + + private final ITestDevice mDevice; + private final IBuildInfo mBuild; + + private final List<String> mArgs = new ArrayList<>(); + private final Map<File, String> mFileToRemoteMap = new HashMap<>(); + + /*package*/ BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo) { + mDevice = device; + mBuild = buildInfo; + addArg("-g"); + } + + T addArg(String arg) { + mArgs.add(arg); + return (T) this; + } + + T addFile(String filename) throws FileNotFoundException { + return addFile(filename, filename); + } + + T addFile(String filename, String remoteName) throws FileNotFoundException { + CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild); + mFileToRemoteMap.put(buildHelper.getTestFile(filename), remoteName); + return (T) this; + } + + T inheritFrom(String packageName) { + addArg("-r"); + addArg("-p " + packageName); + return (T) this; + } + + void run() throws DeviceNotAvailableException { + run(true); + } + + void runExpectingFailure() throws DeviceNotAvailableException { + run(false); + } + + private void run(boolean expectingSuccess) throws DeviceNotAvailableException { + final ITestDevice device = mDevice; + + // Create an install session + final StringBuilder cmd = new StringBuilder(); + cmd.append("pm install-create"); + for (String arg : mArgs) { + cmd.append(' ').append(arg); + } + + String result = device.executeShellCommand(cmd.toString()); + TestCase.assertTrue(result, result.startsWith("Success")); + + final int start = result.lastIndexOf("["); + final int end = result.lastIndexOf("]"); + int sessionId = -1; + try { + if (start != -1 && end != -1 && start < end) { + sessionId = Integer.parseInt(result.substring(start + 1, end)); + } + } catch (NumberFormatException e) { + throw new IllegalStateException("Failed to parse install session: " + result); + } + if (sessionId == -1) { + throw new IllegalStateException("Failed to create install session: " + result); + } + + // Push our files into session. Ideally we'd use stdin streaming, + // but ddmlib doesn't support it yet. + for (final Map.Entry<File, String> entry : mFileToRemoteMap.entrySet()) { + final File file = entry.getKey(); + final String remoteName = entry.getValue(); + final String remotePath = "/data/local/tmp/" + file.getName(); + if (!device.pushFile(file, remotePath)) { + throw new IllegalStateException("Failed to push " + file); + } + + cmd.setLength(0); + cmd.append("pm install-write"); + cmd.append(' ').append(sessionId); + cmd.append(' ').append(remoteName); + cmd.append(' ').append(remotePath); + + result = device.executeShellCommand(cmd.toString()); + TestCase.assertTrue(result, result.startsWith("Success")); + } + + // Everything staged; let's pull trigger + cmd.setLength(0); + cmd.append("pm install-commit"); + cmd.append(' ').append(sessionId); + + result = device.executeShellCommand(cmd.toString()); + if (expectingSuccess) { + TestCase.assertTrue(result, result.contains("Success")); + } else { + TestCase.assertFalse(result, result.contains("Success")); + } + } +} diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java new file mode 100644 index 000000000000..346622f0f467 --- /dev/null +++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2023 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 android.transparency.test; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.platform.test.annotations.LargeTest; +import android.platform.test.annotations.Presubmit; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; +import com.android.tradefed.testtype.junit4.DeviceTestRunOptions; +import com.android.tradefed.util.CommandResult; +import com.android.tradefed.util.CommandStatus; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.TimeUnit; + +@Presubmit +@RunWith(DeviceJUnit4ClassRunner.class) +public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test { + private static final String PACKAGE_NAME = "android.transparency.test.app"; + + private static final String JOB_ID = "1740526926"; + + /** Waiting time for the job to be scheduled */ + private static final int JOB_CREATION_MAX_SECONDS = 30; + + @Before + public void setUp() throws Exception { + cancelPendingJob(); + } + + @Test + public void testCollectAllApexInfo() throws Exception { + var options = new DeviceTestRunOptions(PACKAGE_NAME); + options.setTestClassName(PACKAGE_NAME + ".BinaryTransparencyTest"); + options.setTestMethodName("testCollectAllApexInfo"); + + // Collect APEX package names from /apex, then pass them as expectation to be verified. + // The package names are collected from the find name with deduplication (NB: we used to + // deduplicate by dropping directory names with '@', but there's a DCLA case where it only + // has one directory with '@'. So we have to keep it and deduplicate the current way). + CommandResult result = getDevice().executeShellV2Command( + "ls -d /apex/*/ |grep -v /apex/sharedlibs |cut -d/ -f3 |cut -d@ -f1 |sort |uniq"); + assertTrue(result.getStatus() == CommandStatus.SUCCESS); + String[] packageNames = result.getStdout().split("\n"); + for (var i = 0; i < packageNames.length; i++) { + options.addInstrumentationArg("apex-" + String.valueOf(i), packageNames[i]); + } + options.addInstrumentationArg("apex-number", Integer.toString(packageNames.length)); + runDeviceTests(options); + } + + @Test + public void testCollectAllUpdatedPreloadInfo() throws Exception { + try { + updatePreloadApp(); + runDeviceTest("testCollectAllUpdatedPreloadInfo"); + } finally { + // No need to wait until job complete, since we can't verifying very meaningfully. + cancelPendingJob(); + uninstallPackage("com.android.egg"); + } + } + + @Test + public void testCollectAllSilentInstalledMbaInfo() throws Exception { + try { + new InstallMultiple() + .addFile("ApkVerityTestApp.apk") + .addFile("ApkVerityTestAppSplit.apk") + .run(); + updatePreloadApp(); + assertNotNull(getDevice().getAppPackageInfo("com.android.apkverity")); + assertNotNull(getDevice().getAppPackageInfo("com.android.egg")); + + assertTrue(getDevice().setProperty("debug.transparency.bg-install-apps", + "com.android.apkverity,com.android.egg")); + runDeviceTest("testCollectAllSilentInstalledMbaInfo"); + } finally { + // No need to wait until job complete, since we can't verifying very meaningfully. + cancelPendingJob(); + uninstallPackage("com.android.apkverity"); + uninstallPackage("com.android.egg"); + } + } + + @LargeTest + @Test + public void testRebootlessApexUpdateTriggersJobScheduling() throws Exception { + try { + installRebootlessApex(); + + // Verify + expectJobToBeScheduled(); + } finally { + // No need to wait until job complete, since we can't verifying very meaningfully. + uninstallRebootlessApexThenReboot(); + } + } + + @Test + public void testPreloadUpdateTriggersJobScheduling() throws Exception { + try { + updatePreloadApp(); + + // Verify + expectJobToBeScheduled(); + } finally { + // No need to wait until job complete, since we can't verifying very meaningfully. + cancelPendingJob(); + uninstallPackage("com.android.egg"); + } + } + + private void runDeviceTest(String method) throws DeviceNotAvailableException { + var options = new DeviceTestRunOptions(PACKAGE_NAME); + options.setTestClassName(PACKAGE_NAME + ".BinaryTransparencyTest"); + options.setTestMethodName(method); + runDeviceTests(options); + } + + private void cancelPendingJob() throws DeviceNotAvailableException { + CommandResult result = getDevice().executeShellV2Command( + "cmd jobscheduler cancel android " + JOB_ID); + if (result.getStatus() == CommandStatus.SUCCESS) { + CLog.d("Canceling, output: " + result.getStdout()); + } else { + CLog.d("Something went wrong, error: " + result.getStderr()); + } + } + + private void expectJobToBeScheduled() throws Exception { + for (int i = 0; i < JOB_CREATION_MAX_SECONDS; i++) { + CommandResult result = getDevice().executeShellV2Command( + "cmd jobscheduler get-job-state android " + JOB_ID); + String state = result.getStdout().toString(); + CLog.i("Job status: " + state); + if (state.startsWith("unknown")) { + // The job hasn't been scheduled yet. So try again. + TimeUnit.SECONDS.sleep(1); + } else if (result.getExitCode() != 0) { + fail("Failing due to unexpected job state: " + result); + } else { + // The job exists, which is all we care about here + return; + } + } + fail("Timed out waiting for the job to be scheduled"); + } + + private void installRebootlessApex() throws Exception { + installPackage("com.android.apex.cts.shim.v2_rebootless.apex", "--force-non-staged"); + } + + private void uninstallRebootlessApexThenReboot() throws DeviceNotAvailableException { + // Reboot only if the APEX is not the pre-install one. + CommandResult result = getDevice().executeShellV2Command( + "pm list packages -f --apex-only |grep com.android.apex.cts.shim"); + assertTrue(result.getStatus() == CommandStatus.SUCCESS); + if (result.getStdout().contains("/data/apex/active/")) { + uninstallPackage("com.android.apex.cts.shim"); + getDevice().reboot(); + + // Reboot enforces SELinux. Make it permissive again. + CommandResult runResult = getDevice().executeShellV2Command("setenforce 0"); + assertTrue(runResult.getStatus() == CommandStatus.SUCCESS); + } + } + + private void updatePreloadApp() throws DeviceNotAvailableException { + CommandResult result = getDevice().executeShellV2Command("pm path com.android.egg"); + assertTrue(result.getStatus() == CommandStatus.SUCCESS); + assertThat(result.getStdout()).startsWith("package:/system/app/"); + String path = result.getStdout().replaceFirst("^package:", ""); + + result = getDevice().executeShellV2Command("pm install " + path); + assertTrue(result.getStatus() == CommandStatus.SUCCESS); + } + + private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> { + InstallMultiple() { + super(getDevice(), getBuild()); + // Needed since in getMockBackgroundInstalledPackages, getPackageInfo runs as the caller + // uid. This also makes it consistent with installPackage's behavior. + addArg("--force-queryable"); + } + } +} diff --git a/tests/BinaryTransparencyHostTest/test-app/Android.bp b/tests/BinaryTransparencyHostTest/test-app/Android.bp new file mode 100644 index 000000000000..b5193ddf7c2c --- /dev/null +++ b/tests/BinaryTransparencyHostTest/test-app/Android.bp @@ -0,0 +1,40 @@ +// Copyright (C) 2023 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "BinaryTransparencyTestApp", + manifest: "AndroidManifest.xml", + srcs: ["src/**/*.java"], + static_libs: [ + "androidx.test.core", + "compatibility-device-util-axt", + "junit", + ], + test_suites: [ + "general-tests", + ], + platform_apis: true, + dex_preopt: { + enabled: false, + }, +} diff --git a/tests/componentalias/AndroidManifest.xml b/tests/BinaryTransparencyHostTest/test-app/AndroidManifest.xml index 7bb83a336833..42e616e24eb9 100755..100644 --- a/tests/componentalias/AndroidManifest.xml +++ b/tests/BinaryTransparencyHostTest/test-app/AndroidManifest.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2023 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. @@ -16,10 +16,12 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.content.componentalias.tests" > - + package="android.transparency.test.app"> <application> <uses-library android:name="android.test.runner" /> - <property android:name="com.android.EXPERIMENTAL_COMPONENT_ALIAS_OPT_IN" android:value="true" /> </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:label="APCT tests for binary transparency" + android:targetPackage="android.transparency.test.app" /> </manifest> diff --git a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java new file mode 100644 index 000000000000..c087a85da2a8 --- /dev/null +++ b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2023 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 android.transparency.test.app; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import android.content.Context; +import android.os.Bundle; +import android.transparency.BinaryTransparencyManager; +import android.util.Log; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.os.IBinaryTransparencyService.AppInfo; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.HexFormat; +import java.util.Set; +import java.util.stream.Collectors; + +@RunWith(AndroidJUnit4.class) +public class BinaryTransparencyTest { + private static final String TAG = "BinaryTransparencyTest"; + + private BinaryTransparencyManager mBt; + + @Before + public void setUp() { + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + mBt = context.getSystemService(BinaryTransparencyManager.class); + } + + @Test + public void testCollectAllApexInfo() { + // Prepare the expectation received from host's shell command + Bundle args = InstrumentationRegistry.getArguments(); + assertThat(args).isNotNull(); + int number = Integer.valueOf(args.getString("apex-number")); + assertThat(number).isGreaterThan(0); + var expectedApexNames = new ArrayList<String>(); + for (var i = 0; i < number; i++) { + String moduleName = args.getString("apex-" + Integer.toString(i)); + expectedApexNames.add(moduleName); + } + assertThat(expectedApexNames).containsNoDuplicates(); + + // Action + var apexInfoList = mBt.collectAllApexInfo(/* includeTestOnly */ true); + + // Verify actual apex names + var actualApexesNames = apexInfoList.stream().map((apex) -> apex.moduleName) + .collect(Collectors.toList()); + assertThat(actualApexesNames).containsExactlyElementsIn(expectedApexNames); + + // Perform more valitidy checks + var digestsSeen = new HashSet<String>(); + var hexFormatter = HexFormat.of(); + for (var apex : apexInfoList) { + Log.d(TAG, "Verifying " + apex.packageName + " / " + apex.moduleName); + + assertThat(apex.longVersion).isGreaterThan(0); + assertThat(apex.digestAlgorithm).isGreaterThan(0); + assertThat(apex.signerDigests).asList().containsNoneOf(null, ""); + + assertThat(apex.digest).isNotNull(); + String digestHex = hexFormatter.formatHex(apex.digest); + boolean isNew = digestsSeen.add(digestHex); + assertWithMessage( + "Digest should be unique, but received a dup: " + digestHex) + .that(isNew).isTrue(); + } + } + + @Test + public void testCollectAllUpdatedPreloadInfo() { + var preloadInfoList = mBt.collectAllUpdatedPreloadInfo(new Bundle()); + assertThat(preloadInfoList).isNotEmpty(); // because we just installed from the host side + AppInfo updatedPreload = null; + for (var preload : preloadInfoList) { + Log.d(TAG, "Received " + preload.packageName); + if (preload.packageName.equals("com.android.egg")) { + assertWithMessage("Received the same package").that(updatedPreload).isNull(); + updatedPreload = preload; + } + } + + // Verify + assertThat(updatedPreload.longVersion).isGreaterThan(0); + assertThat(updatedPreload.digestAlgorithm).isGreaterThan(0); + assertThat(updatedPreload.digest).isNotEmpty(); + assertThat(updatedPreload.mbaStatus).isEqualTo(/* MBA_STATUS_UPDATED_PRELOAD */ 2); + assertThat(updatedPreload.signerDigests).asList().containsNoneOf(null, ""); + } + + @Test + public void testCollectAllSilentInstalledMbaInfo() { + // Action + var appInfoList = mBt.collectAllSilentInstalledMbaInfo(new Bundle()); + + // Verify + assertThat(appInfoList).isNotEmpty(); // because we just installed from the host side + + var expectedAppNames = Set.of("com.android.apkverity", "com.android.egg"); + var actualAppNames = appInfoList.stream().map((appInfo) -> appInfo.packageName) + .collect(Collectors.toList()); + assertThat(actualAppNames).containsAtLeastElementsIn(expectedAppNames); + + var actualSplitNames = new ArrayList<String>(); + for (var appInfo : appInfoList) { + Log.d(TAG, "Received " + appInfo.packageName + " as a silent install"); + if (expectedAppNames.contains(appInfo.packageName)) { + assertThat(appInfo.longVersion).isGreaterThan(0); + assertThat(appInfo.digestAlgorithm).isGreaterThan(0); + assertThat(appInfo.digest).isNotEmpty(); + assertThat(appInfo.mbaStatus).isEqualTo(/* MBA_STATUS_NEW_INSTALL */ 3); + assertThat(appInfo.signerDigests).asList().containsNoneOf(null, ""); + + if (appInfo.splitName != null) { + actualSplitNames.add(appInfo.splitName); + } + } + } + assertThat(actualSplitNames).containsExactly("feature_x"); // Name of ApkVerityTestAppSplit + } +} diff --git a/tests/Camera2Tests/CameraToo/Android.bp b/tests/Camera2Tests/CameraToo/Android.bp new file mode 100644 index 000000000000..ebc6fed9fefd --- /dev/null +++ b/tests/Camera2Tests/CameraToo/Android.bp @@ -0,0 +1,28 @@ +// Copyright (C) 2014 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 { + // See: http://go/android-license-faq + default_applicable_licenses: [ + "frameworks_base_license", + ], +} + +android_test { + name: "CameraToo", + + sdk_version: "current", + srcs: ["src/**/*.java"], + +} diff --git a/tests/Camera2Tests/CameraToo/Android.mk b/tests/Camera2Tests/CameraToo/Android.mk deleted file mode 100644 index 33473143c8cb..000000000000 --- a/tests/Camera2Tests/CameraToo/Android.mk +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (C) 2014 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. - -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests -LOCAL_PACKAGE_NAME := CameraToo -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../../NOTICE -LOCAL_SDK_VERSION := current -LOCAL_SRC_FILES := $(call all-java-files-under,src) - -include $(BUILD_PACKAGE) diff --git a/tests/CanvasCompare/Android.bp b/tests/CanvasCompare/Android.bp deleted file mode 100644 index 98831154ddc2..000000000000 --- a/tests/CanvasCompare/Android.bp +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright (C) 2012 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 { - // See: http://go/android-license-faq - default_applicable_licenses: [ - "frameworks_base_license", - ], -} - -android_test { - name: "CanvasCompare", - srcs: [ - "src/**/*.java", - ":CanvasCompare-rscript{CanvasCompare.srcjar}", - ], - resource_zips: [ - ":CanvasCompare-rscript{CanvasCompare.res.zip}", - ], - platform_apis: true, - libs: [ - "android.test.runner", - "android.test.base", - ], - static_libs: ["junit"], -} - -genrule { - name: "CanvasCompare-rscript", - srcs: [ - "src/**/*.rscript", - ":rs_script_api", - ":rs_clang_headers", - ], - tools: [ - "llvm-rs-cc", - "soong_zip", - ], - out: [ - "CanvasCompare.srcjar", - "CanvasCompare.res.zip", - ], - cmd: "for f in $(locations src/**/*.rscript); do " + - " $(location llvm-rs-cc) -o $(genDir)/res/raw -p $(genDir)/src " + - " -I $$(dirname $$(echo $(locations :rs_script_api) | awk '{ print $$1 }')) " + - " -I $$(dirname $$(echo $(locations :rs_clang_headers) | awk '{ print $$1 }')) $${f}; " + - "done && " + - "$(location soong_zip) -srcjar -o $(location CanvasCompare.srcjar) -C $(genDir)/src -D $(genDir)/src &&" + - "$(location soong_zip) -o $(location CanvasCompare.res.zip) -C $(genDir)/res -D $(genDir)/res", -} diff --git a/tests/CanvasCompare/AndroidManifest.xml b/tests/CanvasCompare/AndroidManifest.xml deleted file mode 100644 index 2734e7f07f27..000000000000 --- a/tests/CanvasCompare/AndroidManifest.xml +++ /dev/null @@ -1,48 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2012 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. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.test.hwuicompare"> - - <uses-permission android:name="android.permission.INTERNET"/> - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> - - <application android:label="@string/app_name" - android:theme="@android:style/Theme.Holo.Light.NoActionBar"> - <activity android:name="AutomaticActivity" - android:label="CanvasAutoCompare" - 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="ManualActivity" - android:label="CanvasManualCompare" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - <uses-library android:name="android.test.runner"/> - </application> - <instrumentation android:name="android.test.InstrumentationTestRunner" - android:targetPackage="com.android.test.hwuicompare" - android:label="HW/SW Canvas comparison tool."/> - -</manifest> diff --git a/tests/CanvasCompare/OWNERS b/tests/CanvasCompare/OWNERS deleted file mode 100644 index c88a9f82c347..000000000000 --- a/tests/CanvasCompare/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /libs/hwui/OWNERS diff --git a/tests/CanvasCompare/res/drawable/sunset1.jpg b/tests/CanvasCompare/res/drawable/sunset1.jpg Binary files differdeleted file mode 100644 index 3b4e056b70d0..000000000000 --- a/tests/CanvasCompare/res/drawable/sunset1.jpg +++ /dev/null diff --git a/tests/CanvasCompare/res/layout/automatic_layout.xml b/tests/CanvasCompare/res/layout/automatic_layout.xml deleted file mode 100644 index e049ec0a5000..000000000000 --- a/tests/CanvasCompare/res/layout/automatic_layout.xml +++ /dev/null @@ -1,38 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2012 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. ---> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" > - - <com.android.test.hwuicompare.MainView - android:id="@+id/hardware_view" - android:layout_width="@dimen/layer_width" - android:layout_height="@dimen/layer_width" /> - - <ImageView - android:id="@+id/software_image_view" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentRight="true" /> - - <ImageView - android:id="@+id/hardware_image_view" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentBottom="true" - android:layout_alignParentRight="true" /> - -</RelativeLayout> diff --git a/tests/CanvasCompare/res/layout/manual_layout.xml b/tests/CanvasCompare/res/layout/manual_layout.xml deleted file mode 100644 index 1a9288ce1993..000000000000 --- a/tests/CanvasCompare/res/layout/manual_layout.xml +++ /dev/null @@ -1,119 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2012 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. ---> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="center_horizontal" - android:orientation="vertical" > - - <HorizontalScrollView - android:layout_width="wrap_content" - android:layout_height="wrap_content" > - - <LinearLayout - android:id="@+id/spinner_layout" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" /> - </HorizontalScrollView> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="0dip" - android:layout_weight="1" - android:baselineAligned="true" - android:orientation="horizontal" > - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_weight="1" - android:gravity="center" - android:orientation="horizontal" > - - <com.android.test.hwuicompare.MainView - android:id="@+id/hardware_view" - android:layout_width="@dimen/layer_width" - android:layout_height="@dimen/layer_width" /> - </LinearLayout> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_weight="1" - android:gravity="center" - android:orientation="horizontal" > - - <com.android.test.hwuicompare.MainView - android:id="@+id/software_view" - android:layout_width="@dimen/layer_width" - android:layout_height="@dimen/layer_width" /> - </LinearLayout> - </LinearLayout> - - <ImageView - android:id="@+id/compare_image_view" - android:layout_width="@dimen/layer_width_double" - android:layout_height="@dimen/layer_height_double" - android:filter="false" /> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center" - android:orientation="horizontal" > - - <ImageButton - android:id="@+id/previous" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:contentDescription="@string/previous_combination" - android:src="@android:drawable/ic_media_previous" /> - - <ImageButton - android:id="@+id/next" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:contentDescription="@string/next_combination" - android:src="@android:drawable/ic_media_next" /> - - <TextView - android:id="@+id/current_error" - android:layout_width="100dp" - android:layout_height="wrap_content" - android:gravity="center" - android:textAppearance="?android:attr/textAppearanceLarge" /> - - <Button - android:id="@+id/show_hardware_version" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/show_hardware_version" /> - - <Button - android:id="@+id/show_software_version" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/show_software_version" /> - - <Button - android:id="@+id/show_error_heatmap" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/show_error_heatmap" /> - </LinearLayout> - -</LinearLayout> diff --git a/tests/CanvasCompare/res/values/strings.xml b/tests/CanvasCompare/res/values/strings.xml deleted file mode 100644 index edd46103f4f5..000000000000 --- a/tests/CanvasCompare/res/values/strings.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2012 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. ---> -<resources> - <string name="app_name">Canvas Compare Test</string> - - <!-- show hardware rendered version of the layer --> - <string name="show_hardware_version">Hardware</string> - <!-- show software rendered version of the layer --> - <string name="show_software_version">Software</string> - <!-- show layer error --> - <string name="show_error_values">Error</string> - <!-- show layer error heatmap --> - <string name="show_error_heatmap">Heatmap</string> - <!-- select and display the next combination of painting options--> - <string name="next_combination">Next Combination</string> - <!-- select and display the previous combination of painting options--> - <string name="previous_combination">Previous Combination</string> -</resources> diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/AutomaticActivity.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/AutomaticActivity.java deleted file mode 100644 index 8ccd4e2181ed..000000000000 --- a/tests/CanvasCompare/src/com/android/test/hwuicompare/AutomaticActivity.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright (C) 2012 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.android.test.hwuicompare; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.TreeSet; - -import org.json.JSONException; -import org.json.JSONObject; - -import android.os.Bundle; -import android.os.Environment; -import android.os.Trace; -import android.util.Log; -import android.widget.ImageView; -import android.widget.Toast; - -public class AutomaticActivity extends CompareActivity { - private static final String LOG_TAG = "AutomaticActivity"; - private static final float ERROR_DISPLAY_THRESHOLD = 0.01f; - protected static final boolean DRAW_BITMAPS = false; - - /** - * Threshold of error change required to consider a test regressed/improved - */ - private static final float ERROR_CHANGE_THRESHOLD = 0.001f; - - private static final float[] ERROR_CUTOFFS = { - 0, 0.005f, 0.01f, 0.02f, 0.05f, 0.1f, 0.25f, 0.5f, 1f, 2f - }; - - private final float[] mErrorRates = new float[ERROR_CUTOFFS.length]; - private float mTotalTests = 0; - private float mTotalError = 0; - private int mTestsRegressed = 0; - private int mTestsImproved = 0; - - private ImageView mSoftwareImageView = null; - private ImageView mHardwareImageView = null; - - - public abstract static class FinalCallback { - abstract void report(String name, float value); - void complete() {}; - } - - private final ArrayList<FinalCallback> mFinalCallbacks = new ArrayList<FinalCallback>(); - - Runnable mRunnable = new Runnable() { - @Override - public void run() { - loadBitmaps(); - if (mSoftwareBitmap == null || mHardwareBitmap == null) { - Log.e(LOG_TAG, "bitmap is null"); - return; - } - - if (DRAW_BITMAPS) { - mSoftwareImageView.setImageBitmap(mSoftwareBitmap); - mHardwareImageView.setImageBitmap(mHardwareBitmap); - } - - Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, "calculateError"); - float error = mErrorCalculator.calcErrorRS(mSoftwareBitmap, mHardwareBitmap); - Trace.traceEnd(Trace.TRACE_TAG_ALWAYS); - - final String[] modifierNames = DisplayModifier.getLastAppliedModifications(); - handleError(modifierNames, error); - - if (DisplayModifier.step()) { - finishTest(); - } else { - mHardwareView.invalidate(); - if (DRAW_BITMAPS) { - mSoftwareImageView.invalidate(); - mHardwareImageView.invalidate(); - } - } - mHandler.removeCallbacks(mRunnable); - } - }; - - @Override - protected void onPause() { - super.onPause(); - mHandler.removeCallbacks(mRunnable); - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.automatic_layout); - - mSoftwareImageView = findViewById(R.id.software_image_view); - mHardwareImageView = findViewById(R.id.hardware_image_view); - - onCreateCommon(mRunnable); - beginTest(); - } - - private static class TestResult { - TestResult(String label, float error) { - mLabel = label; - mTotalError = error; - mCount = 1; - } - public void addInto(float error) { - mTotalError += error; - mCount++; - } - public float getAverage() { - return mTotalError / mCount; - } - final String mLabel; - float mTotalError; - int mCount; - } - - JSONObject mOutputJson = null; - JSONObject mInputJson = null; - final HashMap<String, TestResult> mModifierResults = new HashMap<String, TestResult>(); - final HashMap<String, TestResult> mIndividualResults = new HashMap<String, TestResult>(); - final HashMap<String, TestResult> mModifierDiffResults = new HashMap<String, TestResult>(); - final HashMap<String, TestResult> mIndividualDiffResults = new HashMap<String, TestResult>(); - private void beginTest() { - mFinalCallbacks.add(new FinalCallback() { - @Override - void report(String name, float value) { - Log.d(LOG_TAG, name + " " + value); - }; - }); - - File inputFile = new File(Environment.getExternalStorageDirectory(), - "CanvasCompareInput.json"); - if (inputFile.exists() && inputFile.canRead() && inputFile.length() > 0) { - try { - FileInputStream inputStream = new FileInputStream(inputFile); - Log.d(LOG_TAG, "Parsing input file..."); - StringBuffer content = new StringBuffer((int)inputFile.length()); - byte[] buffer = new byte[1024]; - while (inputStream.read(buffer) != -1) { - content.append(new String(buffer)); - } - mInputJson = new JSONObject(content.toString()); - inputStream.close(); - Log.d(LOG_TAG, "Parsed input file with " + mInputJson.length() + " entries"); - } catch (JSONException e) { - Log.e(LOG_TAG, "error parsing input json", e); - } catch (IOException e) { - Log.e(LOG_TAG, "error reading input json from sd", e); - } - } - - mOutputJson = new JSONObject(); - } - - private static void logTestResultHash(String label, HashMap<String, TestResult> map) { - Log.d(LOG_TAG, "---------------"); - Log.d(LOG_TAG, label + ":"); - Log.d(LOG_TAG, "---------------"); - TreeSet<TestResult> set = new TreeSet<TestResult>(new Comparator<TestResult>() { - @Override - public int compare(TestResult lhs, TestResult rhs) { - if (lhs == rhs) return 0; // don't need to worry about complex equality - - int cmp = Float.compare(lhs.getAverage(), rhs.getAverage()); - if (cmp != 0) { - return cmp; - } - return lhs.mLabel.compareTo(rhs.mLabel); - } - }); - - for (TestResult t : map.values()) { - set.add(t); - } - - for (TestResult t : set.descendingSet()) { - if (Math.abs(t.getAverage()) > ERROR_DISPLAY_THRESHOLD) { - Log.d(LOG_TAG, String.format("%2.4f : %s", t.getAverage(), t.mLabel)); - } - } - Log.d(LOG_TAG, ""); - } - - private void finishTest() { - for (FinalCallback c : mFinalCallbacks) { - c.report("averageError", (mTotalError / mTotalTests)); - for (int i = 1; i < ERROR_CUTOFFS.length; i++) { - c.report(String.format("tests with error over %1.3f", ERROR_CUTOFFS[i]), - mErrorRates[i]); - } - if (mInputJson != null) { - c.report("tests regressed", mTestsRegressed); - c.report("tests improved", mTestsImproved); - } - c.complete(); - } - - try { - if (mOutputJson != null) { - String outputString = mOutputJson.toString(4); - File outputFile = new File(Environment.getExternalStorageDirectory(), - "CanvasCompareOutput.json"); - FileOutputStream outputStream = new FileOutputStream(outputFile); - outputStream.write(outputString.getBytes()); - outputStream.close(); - Log.d(LOG_TAG, "Saved output file with " + mOutputJson.length() + " entries"); - } - } catch (JSONException e) { - Log.e(LOG_TAG, "error during JSON stringify", e); - } catch (IOException e) { - Log.e(LOG_TAG, "error storing JSON output on sd", e); - } - - logTestResultHash("Modifier change vs previous", mModifierDiffResults); - logTestResultHash("Invidual test change vs previous", mIndividualDiffResults); - logTestResultHash("Modifier average test results", mModifierResults); - logTestResultHash("Individual test results", mIndividualResults); - - Toast.makeText(getApplicationContext(), "done!", Toast.LENGTH_SHORT).show(); - finish(); - } - - /** - * Inserts the error value into all TestResult objects, associated with each of its modifiers - */ - private static void addForAllModifiers(String fullName, float error, String[] modifierNames, - HashMap<String, TestResult> modifierResults) { - for (String modifierName : modifierNames) { - TestResult r = modifierResults.get(fullName); - if (r == null) { - modifierResults.put(modifierName, new TestResult(modifierName, error)); - } else { - r.addInto(error); - } - } - } - - private void handleError(final String[] modifierNames, final float error) { - String fullName = ""; - for (String s : modifierNames) { - fullName = fullName.concat("." + s); - } - fullName = fullName.substring(1); - - float deltaError = 0; - if (mInputJson != null) { - try { - deltaError = error - (float)mInputJson.getDouble(fullName); - } catch (JSONException e) { - Log.w(LOG_TAG, "Warning: unable to read from input json", e); - } - if (deltaError > ERROR_CHANGE_THRESHOLD) mTestsRegressed++; - if (deltaError < -ERROR_CHANGE_THRESHOLD) mTestsImproved++; - mIndividualDiffResults.put(fullName, new TestResult(fullName, deltaError)); - addForAllModifiers(fullName, deltaError, modifierNames, mModifierDiffResults); - } - - mIndividualResults.put(fullName, new TestResult(fullName, error)); - addForAllModifiers(fullName, error, modifierNames, mModifierResults); - - try { - if (mOutputJson != null) { - mOutputJson.put(fullName, error); - } - } catch (JSONException e) { - Log.e(LOG_TAG, "exception during JSON recording", e); - mOutputJson = null; - } - - for (int i = 0; i < ERROR_CUTOFFS.length; i++) { - if (error <= ERROR_CUTOFFS[i]) break; - mErrorRates[i]++; - } - mTotalError += error; - mTotalTests++; - } - - @Override - protected boolean forceRecreateBitmaps() { - // disable, unless needed for drawing into imageviews - return DRAW_BITMAPS; - } - - // FOR TESTING - public void setFinalCallback(FinalCallback c) { - mFinalCallbacks.add(c); - } -} diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/CompareActivity.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/CompareActivity.java deleted file mode 100644 index 0dec1de79a46..000000000000 --- a/tests/CanvasCompare/src/com/android/test/hwuicompare/CompareActivity.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2012 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.android.test.hwuicompare; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import com.android.test.hwuicompare.R; - -import android.app.Activity; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.os.Handler; -import android.os.Trace; -import android.util.Log; -import android.view.View; - -abstract public class CompareActivity extends Activity { - private static final String LOG_TAG = "CompareActivity"; - - protected MainView mHardwareView = null; - - protected Bitmap mSoftwareBitmap; - protected Bitmap mHardwareBitmap; - - protected ErrorCalculator mErrorCalculator; - - protected Handler mHandler; - - Runnable mDrawCallback = null; - protected boolean mRedrewFlag = true; - - protected void onCreateCommon(final Runnable postDrawCallback) { - mDrawCallback = new Runnable() { - @Override - public void run() { - mRedrewFlag = true; - mHandler.post(postDrawCallback); - }; - }; - getWindow().setBackgroundDrawable(new ColorDrawable(0xffefefef)); - ResourceModifiers.init(getResources()); - - mHardwareView = findViewById(R.id.hardware_view); - mHardwareView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - mHardwareView.setBackgroundColor(Color.WHITE); - mHardwareView.addDrawCallback(mDrawCallback); - - int width = getResources().getDimensionPixelSize(R.dimen.layer_width); - int height = getResources().getDimensionPixelSize(R.dimen.layer_height); - mSoftwareBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - mHardwareBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - - mErrorCalculator = new ErrorCalculator(getApplicationContext(), getResources()); - - mHandler = new Handler(); - } - - protected abstract boolean forceRecreateBitmaps(); - - protected void loadBitmaps() { - Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, "loadBitmaps"); - if (forceRecreateBitmaps()) { - int width = mSoftwareBitmap.getWidth(); - int height = mSoftwareBitmap.getHeight(); - - mSoftwareBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - mHardwareBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - } - - Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, "softwareDraw"); - mHardwareView.draw(new Canvas(mSoftwareBitmap)); - Trace.traceEnd(Trace.TRACE_TAG_ALWAYS); - - try { - Method getHardwareLayer = View.class.getDeclaredMethod("getHardwareLayer"); - if (!getHardwareLayer.isAccessible()) - getHardwareLayer.setAccessible(true); - Object hardwareLayer = getHardwareLayer.invoke(mHardwareView); - if (hardwareLayer == null) { - Log.d(LOG_TAG, "failure to access hardware layer"); - return; - } - Method copyInto = hardwareLayer.getClass() - .getDeclaredMethod("copyInto", Bitmap.class); - if (!copyInto.isAccessible()) - copyInto.setAccessible(true); - - Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, "copyInto"); - boolean success = (Boolean) copyInto.invoke(hardwareLayer, mHardwareBitmap); - Trace.traceEnd(Trace.TRACE_TAG_ALWAYS); - if (!success) { - Log.d(LOG_TAG, "failure to copy hardware layer into bitmap"); - } - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - Trace.traceEnd(Trace.TRACE_TAG_ALWAYS); - } -} diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java deleted file mode 100644 index 0f4e122d147a..000000000000 --- a/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java +++ /dev/null @@ -1,532 +0,0 @@ -/* - * Copyright (C) 2012 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.android.test.hwuicompare; - -import java.util.LinkedHashMap; -import java.util.Map.Entry; - -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; -import android.util.Log; - -public abstract class DisplayModifier { - - // automated tests ignore any combination of operations that don't together return TOTAL_MASK - protected final static int TOTAL_MASK = 0x1F; - - // if we're filling, ensure we're not also sweeping over stroke parameters - protected final static int SWEEP_STROKE_WIDTH_BIT = 0x1 << 0; - protected final static int SWEEP_STROKE_CAP_BIT = 0x1 << 1; - protected final static int SWEEP_STROKE_JOIN_BIT = 0x1 << 2; - - protected final static int SWEEP_SHADER_BIT = 0x1 << 3; // only allow non-simple shaders to use rectangle drawing - protected final static int SWEEP_TRANSFORM_BIT = 0x1 << 4; // only sweep over specified transforms - - abstract public void modifyDrawing(Paint paint, Canvas canvas); - protected int mask() { return 0x0; }; - - private static final RectF gRect = new RectF(0, 0, 200, 175); - private static final float[] gPts = new float[] { - 0, 100, 100, 0, 100, 200, 200, 100 - }; - - private static final int NUM_PARALLEL_LINES = 24; - private static final float[] gTriPts = new float[] { - 75, 0, 130, 130, 130, 130, 0, 130, 0, 130, 75, 0 - }; - private static final float[] gLinePts = new float[NUM_PARALLEL_LINES * 8 + gTriPts.length]; - static { - int index; - for (index = 0; index < gTriPts.length; index++) { - gLinePts[index] = gTriPts[index]; - } - float val = 0; - for (int i = 0; i < NUM_PARALLEL_LINES; i++) { - gLinePts[index + 0] = 150; - gLinePts[index + 1] = val; - gLinePts[index + 2] = 300; - gLinePts[index + 3] = val; - index += 4; - val += 8 + (2.0f/NUM_PARALLEL_LINES); - } - val = 0; - for (int i = 0; i < NUM_PARALLEL_LINES; i++) { - gLinePts[index + 0] = val; - gLinePts[index + 1] = 150; - gLinePts[index + 2] = val; - gLinePts[index + 3] = 300; - index += 4; - val += 8 + (2.0f/NUM_PARALLEL_LINES); - } - }; - - @SuppressWarnings("serial") - private static final LinkedHashMap<String, LinkedHashMap<String, DisplayModifier>> gMaps = new LinkedHashMap<String, LinkedHashMap<String, DisplayModifier>>() { - { - put("aa", new LinkedHashMap<String, DisplayModifier>() { - { - put("true", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setAntiAlias(true); - } - }); - put("false", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setAntiAlias(false); - } - }); - } - }); - put("style", new LinkedHashMap<String, DisplayModifier>() { - { - put("fill", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setStyle(Paint.Style.FILL); - } - }); - put("stroke", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setStyle(Paint.Style.STROKE); - } - @Override - protected int mask() { return SWEEP_STROKE_WIDTH_BIT; } - }); - put("fillAndStroke", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setStyle(Paint.Style.FILL_AND_STROKE); - } - - @Override - protected int mask() { return SWEEP_STROKE_WIDTH_BIT; } - }); - } - }); - put("strokeWidth", new LinkedHashMap<String, DisplayModifier>() { - { - put("hair", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setStrokeWidth(0); - } - @Override - protected int mask() { return SWEEP_STROKE_WIDTH_BIT; } - }); - put("0.3", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setStrokeWidth(0.3f); - } - }); - put("1", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setStrokeWidth(1); - } - }); - put("5", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setStrokeWidth(5); - } - }); - put("30", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setStrokeWidth(30); - } - }); - } - }); - put("strokeCap", new LinkedHashMap<String, DisplayModifier>() { - { - put("butt", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setStrokeCap(Paint.Cap.BUTT); - } - @Override - protected int mask() { return SWEEP_STROKE_CAP_BIT; } - }); - put("round", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setStrokeCap(Paint.Cap.ROUND); - } - }); - put("square", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setStrokeCap(Paint.Cap.SQUARE); - } - }); - } - }); - put("strokeJoin", new LinkedHashMap<String, DisplayModifier>() { - { - put("bevel", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setStrokeJoin(Paint.Join.BEVEL); - } - @Override - protected int mask() { return SWEEP_STROKE_JOIN_BIT; } - }); - put("round", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setStrokeJoin(Paint.Join.ROUND); - } - }); - put("miter", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setStrokeJoin(Paint.Join.MITER); - } - }); - // TODO: add miter0, miter1 etc to test miter distances - } - }); - - put("transform", new LinkedHashMap<String, DisplayModifier>() { - { - put("noTransform", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) {} - @Override - protected int mask() { return SWEEP_TRANSFORM_BIT; }; - }); - put("rotate5", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - canvas.rotate(5); - } - }); - put("rotate45", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - canvas.rotate(45); - } - }); - put("rotate90", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - canvas.rotate(90); - canvas.translate(0, -200); - } - }); - put("scale2x2", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - canvas.scale(2, 2); - } - @Override - protected int mask() { return SWEEP_TRANSFORM_BIT; }; - }); - put("rot20scl1x4", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - canvas.rotate(20); - canvas.scale(1, 4); - } - @Override - protected int mask() { return SWEEP_TRANSFORM_BIT; }; - }); - } - }); - - put("shader", new LinkedHashMap<String, DisplayModifier>() { - { - put("noShader", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) {} - @Override - protected int mask() { return SWEEP_SHADER_BIT; }; - }); - put("repeatShader", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setShader(ResourceModifiers.instance().mRepeatShader); - } - @Override - protected int mask() { return SWEEP_SHADER_BIT; }; - }); - put("translatedShader", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setShader(ResourceModifiers.instance().mTranslatedShader); - } - }); - put("scaledShader", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setShader(ResourceModifiers.instance().mScaledShader); - } - }); - put("horGradient", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setShader(ResourceModifiers.instance().mHorGradient); - } - }); - put("diagGradient", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setShader(ResourceModifiers.instance().mDiagGradient); - } - @Override - protected int mask() { return SWEEP_SHADER_BIT; }; - }); - put("vertGradient", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setShader(ResourceModifiers.instance().mVertGradient); - } - }); - put("radGradient", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setShader(ResourceModifiers.instance().mRadGradient); - } - }); - put("sweepGradient", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setShader(ResourceModifiers.instance().mSweepGradient); - } - }); - put("composeShader", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setShader(ResourceModifiers.instance().mComposeShader); - } - }); - put("bad composeShader", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setShader(ResourceModifiers.instance().mBadComposeShader); - } - }); - put("bad composeShader 2", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setShader(ResourceModifiers.instance().mAnotherBadComposeShader); - } - }); - } - }); - - // FINAL MAP: DOES ACTUAL DRAWING - put("drawing", new LinkedHashMap<String, DisplayModifier>() { - { - put("roundRect", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - canvas.drawRoundRect(gRect, 20, 20, paint); - } - }); - put("rect", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - canvas.drawRect(gRect, paint); - } - @Override - protected int mask() { return SWEEP_SHADER_BIT | SWEEP_STROKE_CAP_BIT; }; - }); - put("circle", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - canvas.drawCircle(100, 100, 75, paint); - } - }); - put("oval", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - canvas.drawOval(gRect, paint); - } - }); - put("lines", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - canvas.drawLines(gLinePts, paint); - } - @Override - protected int mask() { return SWEEP_STROKE_CAP_BIT; }; - }); - put("plusPoints", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - canvas.drawPoints(gPts, paint); - } - }); - put("text", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setTextSize(36); - canvas.drawText("TEXTTEST", 0, 50, paint); - } - }); - put("shadowtext", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - paint.setTextSize(36); - paint.setShadowLayer(3.0f, 0.0f, 3.0f, 0xffff00ff); - canvas.drawText("TEXTTEST", 0, 50, paint); - } - }); - put("bitmapMesh", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - canvas.drawBitmapMesh(ResourceModifiers.instance().mBitmap, 3, 3, - ResourceModifiers.instance().mBitmapVertices, 0, null, 0, null); - } - }); - put("arc", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - canvas.drawArc(gRect, 260, 285, false, paint); - } - @Override - protected int mask() { return SWEEP_STROKE_CAP_BIT; }; - }); - put("arcFromCenter", new DisplayModifier() { - @Override - public void modifyDrawing(Paint paint, Canvas canvas) { - canvas.drawArc(gRect, 260, 285, true, paint); - } - @Override - protected int mask() { return SWEEP_STROKE_JOIN_BIT; }; - }); - } - }); - // WARNING: DON'T PUT MORE MAPS BELOW THIS - } - }; - - private static LinkedHashMap<String, DisplayModifier> getMapAtIndex(int index) { - for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) { - if (index == 0) { - return map; - } - index--; - } - return null; - } - - // indices instead of iterators for easier bidirectional traversal - private static final int mIndices[] = new int[gMaps.size()]; - private static final String[] mLastAppliedModifications = new String[gMaps.size()]; - - private static boolean stepInternal(boolean forward) { - int modifierMapIndex = gMaps.size() - 1; - while (modifierMapIndex >= 0) { - LinkedHashMap<String, DisplayModifier> map = getMapAtIndex(modifierMapIndex); - mIndices[modifierMapIndex] += (forward ? 1 : -1); - - if (mIndices[modifierMapIndex] >= 0 && mIndices[modifierMapIndex] < map.size()) { - break; - } - - mIndices[modifierMapIndex] = (forward ? 0 : map.size() - 1); - modifierMapIndex--; - } - return modifierMapIndex < 0; // true if resetting - } - - public static boolean step() { - boolean ret = false; - do { - ret |= stepInternal(true); - } while (!checkModificationStateMask()); - return ret; - } - - public static boolean stepBack() { - boolean ret = false; - do { - ret |= stepInternal(false); - } while (!checkModificationStateMask()); - return ret; - } - - private static boolean checkModificationStateMask() { - int operatorMask = 0x0; - int mapIndex = 0; - for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) { - int displayModifierIndex = mIndices[mapIndex]; - for (Entry<String, DisplayModifier> modifierEntry : map.entrySet()) { - if (displayModifierIndex == 0) { - mLastAppliedModifications[mapIndex] = modifierEntry.getKey(); - operatorMask |= modifierEntry.getValue().mask(); - break; - } - displayModifierIndex--; - } - mapIndex++; - } - return operatorMask == TOTAL_MASK; - } - - public static void apply(Paint paint, Canvas canvas) { - int mapIndex = 0; - for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) { - int displayModifierIndex = mIndices[mapIndex]; - for (Entry<String, DisplayModifier> modifierEntry : map.entrySet()) { - if (displayModifierIndex == 0) { - mLastAppliedModifications[mapIndex] = modifierEntry.getKey(); - modifierEntry.getValue().modifyDrawing(paint, canvas); - break; - } - displayModifierIndex--; - } - mapIndex++; - } - } - - public static String[] getLastAppliedModifications() { - return mLastAppliedModifications.clone(); - } - - public static String[][] getStrings() { - String[][] keys = new String[gMaps.size()][]; - - int i = 0; - for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) { - keys[i] = new String[map.size()]; - int j = 0; - for (String key : map.keySet()) { - keys[i][j++] = key; - } - i++; - } - - return keys; - } - - public static void setIndex(int mapIndex, int newIndexValue) { - mIndices[mapIndex] = newIndexValue; - } - - public static int[] getIndices() { - return mIndices; - } -} diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/ErrorCalculator.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/ErrorCalculator.java deleted file mode 100644 index d402699b0979..000000000000 --- a/tests/CanvasCompare/src/com/android/test/hwuicompare/ErrorCalculator.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2012 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.android.test.hwuicompare; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.renderscript.Allocation; -import android.renderscript.Element; -import android.renderscript.RenderScript; -import android.util.Log; - -public class ErrorCalculator { - private static final String LOG_TAG = "ErrorCalculator"; - private static final int REGION_SIZE = 8; - - private static final boolean LOG_TIMING = false; - private static final boolean LOG_CALC = false; - - private RenderScript mRS; - private Allocation mIdealPixelsAllocation; - private Allocation mGivenPixelsAllocation; - private Allocation mOutputPixelsAllocation; - - private Allocation mInputRowsAllocation; - private Allocation mOutputRegionsAllocation; - - private ScriptC_errorCalculator mScript; - - private int[] mOutputRowRegions; - - public ErrorCalculator(Context c, Resources resources) { - int width = resources.getDimensionPixelSize(R.dimen.layer_width); - int height = resources.getDimensionPixelSize(R.dimen.layer_height); - mOutputRowRegions = new int[height / REGION_SIZE]; - - mRS = RenderScript.create(c); - int[] rowIndices = new int[height / REGION_SIZE]; - for (int i = 0; i < rowIndices.length; i++) - rowIndices[i] = i * REGION_SIZE; - - mScript = new ScriptC_errorCalculator(mRS); - mScript.set_HEIGHT(height); - mScript.set_WIDTH(width); - mScript.set_REGION_SIZE(REGION_SIZE); - - mInputRowsAllocation = Allocation.createSized(mRS, Element.I32(mRS), rowIndices.length, - Allocation.USAGE_SCRIPT); - mInputRowsAllocation.copyFrom(rowIndices); - mOutputRegionsAllocation = Allocation.createSized(mRS, Element.I32(mRS), - mOutputRowRegions.length, Allocation.USAGE_SCRIPT); - } - - - private static long startMillis, middleMillis; - - public float calcErrorRS(Bitmap ideal, Bitmap given) { - if (LOG_TIMING) { - startMillis = System.currentTimeMillis(); - } - - mIdealPixelsAllocation = Allocation.createFromBitmap(mRS, ideal, - Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); - mGivenPixelsAllocation = Allocation.createFromBitmap(mRS, given, - Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); - - mScript.set_ideal(mIdealPixelsAllocation); - mScript.set_given(mGivenPixelsAllocation); - - mScript.forEach_countInterestingRegions(mInputRowsAllocation, mOutputRegionsAllocation); - mOutputRegionsAllocation.copyTo(mOutputRowRegions); - - int regionCount = 0; - for (int region : mOutputRowRegions) { - regionCount += region; - } - int interestingPixels = Math.max(1, regionCount) * REGION_SIZE * REGION_SIZE; - - if (LOG_TIMING) { - long startMillis2 = System.currentTimeMillis(); - } - - mScript.forEach_accumulateError(mInputRowsAllocation, mOutputRegionsAllocation); - mOutputRegionsAllocation.copyTo(mOutputRowRegions); - float totalError = 0; - for (int row : mOutputRowRegions) { - totalError += row; - } - totalError /= 1024.0f; - - if (LOG_TIMING) { - long finalMillis = System.currentTimeMillis(); - Log.d(LOG_TAG, "rs: first part took " + (middleMillis - startMillis) + "ms"); - Log.d(LOG_TAG, "rs: last part took " + (finalMillis - middleMillis) + "ms"); - } - if (LOG_CALC) { - Log.d(LOG_TAG, "rs: error " + totalError + ", pixels " + interestingPixels); - } - return totalError / interestingPixels; - } - - public void calcErrorHeatmapRS(Bitmap ideal, Bitmap given, Bitmap output) { - mIdealPixelsAllocation = Allocation.createFromBitmap(mRS, ideal, - Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); - mGivenPixelsAllocation = Allocation.createFromBitmap(mRS, given, - Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); - - mScript.set_ideal(mIdealPixelsAllocation); - mScript.set_given(mGivenPixelsAllocation); - - mOutputPixelsAllocation = Allocation.createFromBitmap(mRS, output, - Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); - mScript.forEach_displayDifference(mOutputPixelsAllocation, mOutputPixelsAllocation); - mOutputPixelsAllocation.copyTo(output); - } - - public static float calcError(Bitmap ideal, Bitmap given) { - if (LOG_TIMING) { - startMillis = System.currentTimeMillis(); - } - - int interestingRegions = 0; - for (int x = 0; x < ideal.getWidth(); x += REGION_SIZE) { - for (int y = 0; y < ideal.getWidth(); y += REGION_SIZE) { - if (inspectRegion(ideal, x, y)) { - interestingRegions++; - } - } - } - - int interestingPixels = Math.max(1, interestingRegions) * REGION_SIZE * REGION_SIZE; - - if (LOG_TIMING) { - long startMillis2 = System.currentTimeMillis(); - } - - float totalError = 0; - for (int x = 0; x < ideal.getWidth(); x++) { - for (int y = 0; y < ideal.getHeight(); y++) { - int idealColor = ideal.getPixel(x, y); - int givenColor = given.getPixel(x, y); - if (idealColor == givenColor) - continue; - totalError += Math.abs(Color.red(idealColor) - Color.red(givenColor)); - totalError += Math.abs(Color.green(idealColor) - Color.green(givenColor)); - totalError += Math.abs(Color.blue(idealColor) - Color.blue(givenColor)); - totalError += Math.abs(Color.alpha(idealColor) - Color.alpha(givenColor)); - } - } - totalError /= 1024.0f; - if (LOG_TIMING) { - long finalMillis = System.currentTimeMillis(); - Log.d(LOG_TAG, "dvk: first part took " + (middleMillis - startMillis) + "ms"); - Log.d(LOG_TAG, "dvk: last part took " + (finalMillis - middleMillis) + "ms"); - } - if (LOG_CALC) { - Log.d(LOG_TAG, "dvk: error " + totalError + ", pixels " + interestingPixels); - } - return totalError / interestingPixels; - } - - private static boolean inspectRegion(Bitmap ideal, int x, int y) { - int regionColor = ideal.getPixel(x, y); - for (int i = 0; i < REGION_SIZE; i++) { - for (int j = 0; j < REGION_SIZE; j++) { - if (ideal.getPixel(x + i, y + j) != regionColor) - return true; - } - } - return false; - } -} diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/MainView.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/MainView.java deleted file mode 100644 index 454fe7b50ad7..000000000000 --- a/tests/CanvasCompare/src/com/android/test/hwuicompare/MainView.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2012 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.android.test.hwuicompare; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.util.AttributeSet; -import android.view.View; - -public class MainView extends View { - Paint mPaint = new Paint(); - - public MainView(Context context) { - super(context); - } - - public MainView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public MainView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - mPaint.reset(); - DisplayModifier.apply(mPaint, canvas); - - if (mDrawCallback != null) { - mDrawCallback.run(); - } - } - - private Runnable mDrawCallback; - public void addDrawCallback(Runnable drawCallback) { - mDrawCallback = drawCallback; - } -} diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/ManualActivity.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/ManualActivity.java deleted file mode 100644 index 405ff65a34fd..000000000000 --- a/tests/CanvasCompare/src/com/android/test/hwuicompare/ManualActivity.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) 2012 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.android.test.hwuicompare; - -import com.android.test.hwuicompare.R; - -import android.graphics.Bitmap; -import android.graphics.Color; -import android.os.Bundle; -import android.util.Log; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.Spinner; -import android.widget.TextView; - -public class ManualActivity extends CompareActivity { - private static final String LOG_TAG = "ManualActivity"; - private ImageView mCompareImageView; - private Bitmap mCompareBitmap; - private TextView mErrorTextView; - private MainView mSoftwareView; - - private static final int COMPARE_VIEW_UNINITIALIZED = -1; - private static final int COMPARE_VIEW_HARDWARE = 0; - private static final int COMPARE_VIEW_SOFTWARE = 1; - private static final int COMPARE_VIEW_HEATMAP = 2; // TODO: add more like this? any ideas? - - private int mCompareImageViewState = COMPARE_VIEW_UNINITIALIZED; - private int mLastCompareImageViewState = COMPARE_VIEW_UNINITIALIZED; - - Runnable mRunnable = new Runnable() { - @Override - public void run() { - Log.d(LOG_TAG, "mRunnable running, mRedrewFlag = " + mRedrewFlag); - - if (mRedrewFlag) { - loadBitmaps(); - // recalculate error - float error = mErrorCalculator.calcErrorRS(mSoftwareBitmap, mHardwareBitmap); - String modname = ""; - for (String s : DisplayModifier.getLastAppliedModifications()) { - modname = modname.concat(s + "."); - } - - Log.d(LOG_TAG, "error for " + modname + " is " + error); - mErrorTextView.setText(String.format("%.4f", error)); - } - - if (mCompareImageViewState != mLastCompareImageViewState || mRedrewFlag) { - switch (mCompareImageViewState) { - case COMPARE_VIEW_UNINITIALIZED: - // set to hardware - case COMPARE_VIEW_HARDWARE: - mCompareImageView.setImageBitmap(mHardwareBitmap); - break; - case COMPARE_VIEW_SOFTWARE: - mCompareImageView.setImageBitmap(mSoftwareBitmap); - break; - case COMPARE_VIEW_HEATMAP: - mErrorCalculator.calcErrorHeatmapRS(mSoftwareBitmap, mHardwareBitmap, - mCompareBitmap); - mCompareImageView.setImageBitmap(mCompareBitmap); - break; - } - mCompareImageView.getDrawable().setFilterBitmap(false); - mCompareImageView.invalidate(); - } - - mLastCompareImageViewState = mCompareImageViewState; - mRedrewFlag = false; - mHandler.removeCallbacks(mRunnable); - } - }; - - private void redrawViews() { - mHardwareView.invalidate(); - mSoftwareView.invalidate(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.manual_layout); - onCreateCommon(mRunnable); - - mSoftwareView = (MainView) findViewById(R.id.software_view); - mSoftwareView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - mSoftwareView.setBackgroundColor(Color.WHITE); - mSoftwareView.addDrawCallback(mDrawCallback); - - mCompareImageView = (ImageView) findViewById(R.id.compare_image_view); - - int width = getResources().getDimensionPixelSize(R.dimen.layer_width); - int height = getResources().getDimensionPixelSize(R.dimen.layer_height); - mCompareBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - - mErrorTextView = (TextView) findViewById(R.id.current_error); - ((ImageButton) findViewById(R.id.next)).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - DisplayModifier.step(); - updateSpinners(); - redrawViews(); - } - }); - ((ImageButton) findViewById(R.id.previous)).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - DisplayModifier.stepBack(); - updateSpinners(); - redrawViews(); - } - }); - ((Button) findViewById(R.id.show_hardware_version)) - .setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mCompareImageViewState = COMPARE_VIEW_HARDWARE; - mHandler.post(mRunnable); - } - }); - ((Button) findViewById(R.id.show_software_version)) - .setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mCompareImageViewState = COMPARE_VIEW_SOFTWARE; - mHandler.post(mRunnable); - } - }); - ((Button) findViewById(R.id.show_error_heatmap)).setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mCompareImageViewState = COMPARE_VIEW_HEATMAP; - mHandler.post(mRunnable); - } - }); - - buildSpinnerLayout(); - } - - private class DisplayModifierSpinner extends Spinner { - private final int mIndex; - - public DisplayModifierSpinner(int index) { - super(ManualActivity.this); - mIndex = index; - setOnItemSelectedListener(new OnItemSelectedListener() { - - @Override - public void onItemSelected(AdapterView<?> parentView, View selectedItem, - int position, long id) { - DisplayModifier.setIndex(mIndex, position); - redrawViews(); - } - - @Override - public void onNothingSelected(AdapterView<?> parentView) { - } - }); - } - } - - private Spinner[] mSpinners; - - private void buildSpinnerLayout() { - LinearLayout layout = (LinearLayout) findViewById(R.id.spinner_layout); - String[][] mapsStrings = DisplayModifier.getStrings(); - mSpinners = new Spinner[mapsStrings.length]; - int index = 0; - for (String[] spinnerValues : mapsStrings) { - mSpinners[index] = new DisplayModifierSpinner(index); - mSpinners[index].setAdapter(new ArrayAdapter<String>(this, - android.R.layout.simple_spinner_dropdown_item, spinnerValues)); - layout.addView(mSpinners[index]); - index++; - } - Log.d(LOG_TAG, "created " + index + " spinners"); - } - - private void updateSpinners() { - int[] indices = DisplayModifier.getIndices(); - for (int i = 0; i < mSpinners.length; i++) { - mSpinners[i].setSelection(indices[i]); - } - } - - @Override - protected boolean forceRecreateBitmaps() { - // continually recreate bitmaps to avoid modifying bitmaps currently being drawn - return true; - } -} diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/ResourceModifiers.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/ResourceModifiers.java deleted file mode 100644 index d5224813c0bc..000000000000 --- a/tests/CanvasCompare/src/com/android/test/hwuicompare/ResourceModifiers.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2012 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.android.test.hwuicompare; - -import com.android.test.hwuicompare.R; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.BitmapShader; -import android.graphics.Color; -import android.graphics.ComposeShader; -import android.graphics.LinearGradient; -import android.graphics.PorterDuff; -import android.graphics.RadialGradient; -import android.graphics.SweepGradient; -import android.graphics.Matrix; -import android.graphics.Shader; - -public class ResourceModifiers { - public final BitmapShader mRepeatShader; - public final BitmapShader mTranslatedShader; - public final BitmapShader mScaledShader; - private final int mTexWidth; - private final int mTexHeight; - private final float mDrawWidth; - private final float mDrawHeight; - public final LinearGradient mHorGradient; - public final LinearGradient mDiagGradient; - public final LinearGradient mVertGradient; - public final RadialGradient mRadGradient; - public final SweepGradient mSweepGradient; - public final ComposeShader mComposeShader; - public final ComposeShader mBadComposeShader; - public final ComposeShader mAnotherBadComposeShader; - public final Bitmap mBitmap; - private final Matrix mMtx1; - private final Matrix mMtx2; - private final Matrix mMtx3; - - public final float[] mBitmapVertices; - public final int[] mBitmapColors; - - private static ResourceModifiers sInstance = null; - public static ResourceModifiers instance() { return sInstance; } - public static void init(Resources resources) { - sInstance = new ResourceModifiers(resources); - } - - public ResourceModifiers(Resources resources) { - mBitmap = BitmapFactory.decodeResource(resources, R.drawable.sunset1); - mTexWidth = mBitmap.getWidth(); - mTexHeight = mBitmap.getHeight(); - - mDrawWidth = resources.getDimensionPixelSize(R.dimen.layer_width); - mDrawHeight = resources.getDimensionPixelSize(R.dimen.layer_height); - - mRepeatShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, - Shader.TileMode.REPEAT); - - mTranslatedShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, - Shader.TileMode.REPEAT); - mMtx1 = new Matrix(); - mMtx1.setTranslate(mTexWidth / 2.0f, mTexHeight / 2.0f); - mMtx1.postRotate(45, 0, 0); - mTranslatedShader.setLocalMatrix(mMtx1); - - mScaledShader = new BitmapShader(mBitmap, Shader.TileMode.MIRROR, - Shader.TileMode.MIRROR); - mMtx2 = new Matrix(); - mMtx2.setScale(0.5f, 0.5f); - mScaledShader.setLocalMatrix(mMtx2); - - mHorGradient = new LinearGradient(0.0f, 0.0f, 1.0f, 0.0f, - Color.RED, Color.GREEN, Shader.TileMode.CLAMP); - mMtx3 = new Matrix(); - mMtx3.setScale(mDrawHeight, 1.0f); - mMtx3.postRotate(-90.0f); - mMtx3.postTranslate(0.0f, mDrawHeight); - mHorGradient.setLocalMatrix(mMtx3); - - mDiagGradient = new LinearGradient(0.0f, 0.0f, mDrawWidth / 2.0f, mDrawHeight / 2.0f, - Color.BLUE, Color.RED, Shader.TileMode.CLAMP); - - mVertGradient = new LinearGradient(0.0f, 0.0f, 0.0f, mDrawHeight / 2.0f, - Color.YELLOW, Color.MAGENTA, Shader.TileMode.MIRROR); - - mSweepGradient = new SweepGradient(mDrawWidth / 2.0f, mDrawHeight / 2.0f, - Color.YELLOW, Color.MAGENTA); - - mComposeShader = new ComposeShader(mRepeatShader, mHorGradient, - PorterDuff.Mode.MULTIPLY); - - final float width = mBitmap.getWidth() / 8.0f; - final float height = mBitmap.getHeight() / 8.0f; - - mBitmapVertices = new float[] { - 0.0f, 0.0f, width, 0.0f, width * 2, 0.0f, width * 3, 0.0f, - 0.0f, height, width, height, width * 2, height, width * 4, height, - 0.0f, height * 2, width, height * 2, width * 2, height * 2, width * 3, height * 2, - 0.0f, height * 4, width, height * 4, width * 2, height * 4, width * 4, height * 4, - }; - - mBitmapColors = new int[] { - 0xffff0000, 0xff00ff00, 0xff0000ff, 0xffff0000, - 0xff0000ff, 0xffff0000, 0xff00ff00, 0xff00ff00, - 0xff00ff00, 0xff0000ff, 0xffff0000, 0xff00ff00, - 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00ff0000, - }; - - // Use a repeating gradient with many colors to test the non simple case. - mRadGradient = new RadialGradient(mDrawWidth / 4.0f, mDrawHeight / 4.0f, 4.0f, - mBitmapColors, null, Shader.TileMode.REPEAT); - - mBadComposeShader = new ComposeShader(mRadGradient, mComposeShader, - PorterDuff.Mode.MULTIPLY); - - mAnotherBadComposeShader = new ComposeShader(mRadGradient, mVertGradient, - PorterDuff.Mode.MULTIPLY); - } - -} diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/Test.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/Test.java deleted file mode 100644 index 1ff153c003c1..000000000000 --- a/tests/CanvasCompare/src/com/android/test/hwuicompare/Test.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.android.test.hwuicompare; - -import com.android.test.hwuicompare.AutomaticActivity.FinalCallback; - -import android.os.Bundle; -import android.test.ActivityInstrumentationTestCase2; - -public class Test extends ActivityInstrumentationTestCase2<AutomaticActivity> { - AutomaticActivity mActivity; - private Bundle mBundle; - - public Test() { - super(AutomaticActivity.class); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - mBundle = new Bundle(); - mActivity = getActivity(); - mActivity.setFinalCallback(new FinalCallback() { - - @Override - void report(String key, float value) { - mBundle.putFloat(key, value); - } - @Override - void complete() { - synchronized(mBundle) { - mBundle.notify(); - } - } - }); - } - - public void testCanvas() { - synchronized(mBundle) { - try { - mBundle.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - getInstrumentation().sendStatus(0, mBundle); - } -} diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rscript b/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rscript deleted file mode 100644 index 0a1742ef3867..000000000000 --- a/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rscript +++ /dev/null @@ -1,61 +0,0 @@ -#pragma version(1) -#pragma rs java_package_name(com.android.test.hwuicompare) - -int REGION_SIZE; -int WIDTH; -int HEIGHT; - -rs_allocation ideal; -rs_allocation given; - -void countInterestingRegions(const int32_t *v_in, int32_t *v_out) { - int y = v_in[0]; - v_out[0] = 0; - - for (int x = 0; x < HEIGHT; x += REGION_SIZE) { - bool interestingRegion = false; - uchar4 regionColor = rsGetElementAt_uchar4(ideal, x, y); - for (int i = 0; i < REGION_SIZE && !interestingRegion; i++) { - for (int j = 0; j < REGION_SIZE && !interestingRegion; j++) { - uchar4 testVal = rsGetElementAt_uchar4(ideal, x + j, y + i); - interestingRegion |= (testVal.r != regionColor.r); - interestingRegion |= (testVal.g != regionColor.g); - interestingRegion |= (testVal.b != regionColor.b); - interestingRegion |= (testVal.a != regionColor.a); - } - } - if (interestingRegion) { - v_out[0]++; - } - } -} - -void accumulateError(const int32_t *v_in, int32_t *v_out) { - int startY = v_in[0]; - int error = 0; - for (int y = startY; y < startY + REGION_SIZE; y++) { - for (int x = 0; x < HEIGHT; x++) { - uchar4 idealPixel = rsGetElementAt_uchar4(ideal, x, y); - uchar4 givenPixel = rsGetElementAt_uchar4(given, x, y); - - error += abs(idealPixel.x - givenPixel.x); - error += abs(idealPixel.y - givenPixel.y); - error += abs(idealPixel.z - givenPixel.z); - error += abs(idealPixel.w - givenPixel.w); - } - } - v_out[0] = error; -} - -void displayDifference(const uchar4 *v_in, uchar4 *v_out, uint32_t x, uint32_t y) { - float4 idealPixel = rsGetElementAt_float4(ideal, x, y); - float4 givenPixel = rsGetElementAt_float4(given, x, y); - - float4 diff = idealPixel - givenPixel; - float totalDiff = diff.x + diff.y + diff.z + diff.w; - if (totalDiff < 0) { - v_out[0] = rsPackColorTo8888(0, 0, clamp(-totalDiff/2.f, 0.f, 1.f)); - } else { - v_out[0] = rsPackColorTo8888(clamp(totalDiff/2.f, 0.f, 1.f), 0, 0); - } -} diff --git a/tests/ChoreographerTests/Android.bp b/tests/ChoreographerTests/Android.bp new file mode 100644 index 000000000000..ca3026705c63 --- /dev/null +++ b/tests/ChoreographerTests/Android.bp @@ -0,0 +1,49 @@ +// Copyright 2023 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "ChoreographerTests", + srcs: ["src/**/*.java"], + libs: [ + "android.test.runner", + "android.test.base", + ], + static_libs: [ + "androidx.test.core", + "androidx.test.ext.junit", + "androidx.test.rules", + "compatibility-device-util-axt", + "com.google.android.material_material", + "truth-prebuilt", + ], + jni_libs: [ + "libchoreographertests_jni", + ], + resource_dirs: ["src/main/res"], + certificate: "platform", + platform_apis: true, + test_suites: ["device-tests"], + optimize: { + enabled: false, + }, +} diff --git a/tests/ChoreographerTests/AndroidManifest.xml b/tests/ChoreographerTests/AndroidManifest.xml new file mode 100644 index 000000000000..3283c90bd2e5 --- /dev/null +++ b/tests/ChoreographerTests/AndroidManifest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2023 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.view.choreographertests"> + + <application android:debuggable="true" android:testOnly="true"> + <uses-library android:name="android.test.runner"/> + <activity + android:name=".GraphicsActivity" + android:exported="false"> + </activity> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.view.choreographertests" + android:label="Tests of android.view.ChoreographerTests"> + </instrumentation> +</manifest>
\ No newline at end of file diff --git a/tests/ChoreographerTests/AndroidTest.xml b/tests/ChoreographerTests/AndroidTest.xml new file mode 100644 index 000000000000..e7176996a49a --- /dev/null +++ b/tests/ChoreographerTests/AndroidTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2023 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. + --> +<configuration description="Config for ChoreographerTests cases"> + <option name="test-suite-tag" value="cts" /> + <option name="test-tag" value="ChoreographerTests"/> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="ChoreographerTests.apk" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="android.view.choreographertests" /> + <option name="hidden-api-checks" value="false" /> + <option name="isolated-storage" value="false" /> + </test> +</configuration>
\ No newline at end of file diff --git a/tests/ChoreographerTests/OWNERS b/tests/ChoreographerTests/OWNERS new file mode 100644 index 000000000000..2b7de2555587 --- /dev/null +++ b/tests/ChoreographerTests/OWNERS @@ -0,0 +1,2 @@ +include platform/frameworks/base:/graphics/java/android/graphics/OWNERS +include platform/frameworks/native:/services/surfaceflinger/OWNERS
\ No newline at end of file diff --git a/tests/ChoreographerTests/TEST_MAPPING b/tests/ChoreographerTests/TEST_MAPPING new file mode 100644 index 000000000000..16a48eadc0cd --- /dev/null +++ b/tests/ChoreographerTests/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "ChoreographerTests" + } + ] +}
\ No newline at end of file diff --git a/tests/ChoreographerTests/jni/Android.bp b/tests/ChoreographerTests/jni/Android.bp new file mode 100644 index 000000000000..7198c511489b --- /dev/null +++ b/tests/ChoreographerTests/jni/Android.bp @@ -0,0 +1,44 @@ +// Copyright 2023 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_test_library { + name: "libchoreographertests_jni", + cflags: [ + "-Werror", + "-Wthread-safety", + ], + + gtest: false, + + srcs: [ + "ChoreographerTestsJniOnLoad.cpp", + "android_view_tests_ChoreographerNativeTest.cpp", + ], + + shared_libs: [ + "libandroid", + "libnativehelper", + "liblog", + ], + + header_libs: [ + "libandroid_headers_private", + ], + + stl: "c++_static", +} diff --git a/tests/ChoreographerTests/jni/ChoreographerTestsJniOnLoad.cpp b/tests/ChoreographerTests/jni/ChoreographerTestsJniOnLoad.cpp new file mode 100644 index 000000000000..447376fca78f --- /dev/null +++ b/tests/ChoreographerTests/jni/ChoreographerTestsJniOnLoad.cpp @@ -0,0 +1,33 @@ +/* + * Copyright 2023 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. + */ +#include <jni.h> + +#define LOG_TAG "ChoreographerTestsJniOnLoad" + +extern int register_android_android_view_tests_ChoreographerNativeTest(JNIEnv* env); + +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) { + JNIEnv* env = NULL; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { + return JNI_ERR; + } + + if (register_android_android_view_tests_ChoreographerNativeTest(env)) { + return JNI_ERR; + } + + return JNI_VERSION_1_6; +}
\ No newline at end of file diff --git a/tests/ChoreographerTests/jni/android_view_tests_ChoreographerNativeTest.cpp b/tests/ChoreographerTests/jni/android_view_tests_ChoreographerNativeTest.cpp new file mode 100644 index 000000000000..27f4bae9e65a --- /dev/null +++ b/tests/ChoreographerTests/jni/android_view_tests_ChoreographerNativeTest.cpp @@ -0,0 +1,167 @@ +/* + * Copyright 2023 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. + * + */ + +#include <android/choreographer.h> +#include <android/log.h> +#include <android/surface_control_jni.h> +#include <jni.h> +#include <private/surface_control_private.h> +#include <time.h> +#include <utils/Log.h> +#include <utils/Mutex.h> + +#include <chrono> +#include <cmath> +#include <condition_variable> +#include <mutex> +#include <thread> + +#undef LOG_TAG +#define LOG_TAG "AttachedChoreographerNativeTest" + +// Copied from cts/tests/tests/view/jni/jniAssert.h, to be removed when integrated in CTS. +#define ASSERT(condition, format, args...) \ + if (!(condition)) { \ + fail(env, format, ##args); \ + return; \ + } + +using namespace std::chrono_literals; + +static constexpr std::chrono::nanoseconds kMaxRuntime{1s}; +static constexpr float kFpsTolerance = 5.0f; + +static constexpr int kNumOfFrames = 20; + +struct { + struct { + jclass clazz; + jmethodID endTest; + } attachedChoreographerNativeTest; +} gJni; + +struct CallbackData { + std::mutex mutex; + + // Condition to signal callbacks are done running and test can be verified. + std::condition_variable_any condition; + + // Flag to ensure not to lock on the condition if notify is called before wait_for. + bool callbacksComplete = false; + + AChoreographer* choreographer = nullptr; + int count GUARDED_BY(mutex){0}; + std::chrono::nanoseconds frameTime GUARDED_BY(mutex){0}; + std::chrono::nanoseconds startTime; + std::chrono::nanoseconds endTime GUARDED_BY(mutex){0}; +}; + +static std::chrono::nanoseconds now() { + return std::chrono::steady_clock::now().time_since_epoch(); +} + +static void vsyncCallback(const AChoreographerFrameCallbackData* callbackData, void* data) { + ALOGI("%s: Vsync callback running", __func__); + long frameTimeNanos = AChoreographerFrameCallbackData_getFrameTimeNanos(callbackData); + + auto* cb = static_cast<CallbackData*>(data); + { + std::lock_guard<std::mutex> _l(cb->mutex); + cb->count++; + cb->endTime = now(); + cb->frameTime = std::chrono::nanoseconds{frameTimeNanos}; + + ALOGI("%s: ran callback now %ld, frameTimeNanos %ld, new count %d", __func__, + static_cast<long>(cb->endTime.count()), frameTimeNanos, cb->count); + if (cb->endTime - cb->startTime > kMaxRuntime) { + cb->callbacksComplete = true; + cb->condition.notify_all(); + return; + } + } + + ALOGI("%s: Posting next callback", __func__); + AChoreographer_postVsyncCallback(cb->choreographer, vsyncCallback, data); +} + +static void fail(JNIEnv* env, const char* format, ...) { + va_list args; + + va_start(args, format); + char* msg; + int rc = vasprintf(&msg, format, args); + va_end(args); + + jclass exClass; + const char* className = "java/lang/AssertionError"; + exClass = env->FindClass(className); + env->ThrowNew(exClass, msg); + free(msg); +} + +jlong SurfaceControl_getChoreographer(JNIEnv* env, jclass, jobject surfaceControlObj) { + return reinterpret_cast<jlong>( + ASurfaceControl_getChoreographer(ASurfaceControl_fromJava(env, surfaceControlObj))); +} + +static bool frameRateEquals(float fr1, float fr2) { + return std::abs(fr1 - fr2) <= kFpsTolerance; +} + +static void endTest(JNIEnv* env, jobject clazz) { + env->CallVoidMethod(clazz, gJni.attachedChoreographerNativeTest.endTest); +} + +static void android_view_ChoreographerNativeTest_testPostVsyncCallbackAtFrameRate( + JNIEnv* env, jobject clazz, jlong choreographerPtr, jfloat expectedFrameRate) { + AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr); + CallbackData cb; + cb.choreographer = choreographer; + cb.startTime = now(); + ALOGI("%s: Post first callback at %ld", __func__, static_cast<long>(cb.startTime.count())); + AChoreographer_postVsyncCallback(choreographer, vsyncCallback, &cb); + + std::scoped_lock<std::mutex> conditionLock(cb.mutex); + ASSERT(cb.condition.wait_for(cb.mutex, 2 * kMaxRuntime, [&cb] { return cb.callbacksComplete; }), + "Never received callbacks!"); + + float actualFrameRate = static_cast<float>(cb.count) / + (static_cast<double>((cb.endTime - cb.startTime).count()) / 1'000'000'000.0); + ALOGI("%s: callback called %d times with final start time %ld, end time %ld, effective " + "frame rate %f", + __func__, cb.count, static_cast<long>(cb.startTime.count()), + static_cast<long>(cb.endTime.count()), actualFrameRate); + ASSERT(frameRateEquals(actualFrameRate, expectedFrameRate), + "Effective frame rate is %f but expected to be %f", actualFrameRate, expectedFrameRate); + + endTest(env, clazz); +} + +static JNINativeMethod gMethods[] = { + {"nativeSurfaceControl_getChoreographer", "(Landroid/view/SurfaceControl;)J", + (void*)SurfaceControl_getChoreographer}, + {"nativeTestPostVsyncCallbackAtFrameRate", "(JF)V", + (void*)android_view_ChoreographerNativeTest_testPostVsyncCallbackAtFrameRate}, +}; + +int register_android_android_view_tests_ChoreographerNativeTest(JNIEnv* env) { + jclass clazz = + env->FindClass("android/view/choreographertests/AttachedChoreographerNativeTest"); + gJni.attachedChoreographerNativeTest.clazz = static_cast<jclass>(env->NewGlobalRef(clazz)); + gJni.attachedChoreographerNativeTest.endTest = env->GetMethodID(clazz, "endTest", "()V"); + return env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod)); +} diff --git a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerNativeTest.java b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerNativeTest.java new file mode 100644 index 000000000000..1118eb3bfc6e --- /dev/null +++ b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerNativeTest.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2023 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 android.view.choreographertests; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.Manifest; +import android.hardware.display.DisplayManager; +import android.support.test.uiautomator.UiDevice; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import androidx.lifecycle.Lifecycle; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +public class AttachedChoreographerNativeTest { + private static final String TAG = "AttachedChoreographerNativeTest"; + + static { + System.loadLibrary("choreographertests_jni"); + } + + private final CountDownLatch mSurfaceCreationCountDown = new CountDownLatch(1); + private CountDownLatch mTestCompleteSignal; + private long mChoreographerPtr; + private SurfaceView mSurfaceView; + private SurfaceHolder mSurfaceHolder; + private ActivityScenario<GraphicsActivity> mScenario; + private int mInitialMatchContentFrameRate; + private DisplayManager mDisplayManager; + + private static native long nativeSurfaceControl_getChoreographer(SurfaceControl surfaceControl); + private native void nativeTestPostVsyncCallbackAtFrameRate( + long choreographerPtr, float expectedFrameRate); + + @Before + public void setup() throws Exception { + mScenario = ActivityScenario.launch(GraphicsActivity.class); + mScenario.moveToState(Lifecycle.State.CREATED); + mScenario.onActivity(activity -> { + mSurfaceView = activity.findViewById(R.id.surface); + mSurfaceHolder = mSurfaceView.getHolder(); + mSurfaceHolder.addCallback(new SurfaceHolder.Callback() { + @Override + public void surfaceChanged( + SurfaceHolder holder, int format, int width, int height) {} + + @Override + public void surfaceCreated(SurfaceHolder holder) { + mSurfaceCreationCountDown.countDown(); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) {} + }); + }); + + mScenario.moveToState(Lifecycle.State.RESUMED); + UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + uiDevice.wakeUp(); + uiDevice.executeShellCommand("wm dismiss-keyguard"); + + InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( + android.Manifest.permission.LOG_COMPAT_CHANGE, + android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG, + android.Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE, + android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS, + Manifest.permission.MANAGE_GAME_MODE); + mScenario.onActivity(activity -> { + mDisplayManager = activity.getSystemService(DisplayManager.class); + mInitialMatchContentFrameRate = + toSwitchingType(mDisplayManager.getMatchContentFrameRateUserPreference()); + mDisplayManager.setRefreshRateSwitchingType( + DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY); + mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true); + }); + } + + @After + public void tearDown() { + mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate); + mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false); + InstrumentationRegistry.getInstrumentation() + .getUiAutomation() + .dropShellPermissionIdentity(); + } + + @Test + public void test_choreographer_callbacksForVariousFrameRates() { + for (int divisor : new int[] {2, 3}) { + mTestCompleteSignal = new CountDownLatch(1); + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds= */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + + SurfaceControl surfaceControl = mSurfaceView.getSurfaceControl(); + mChoreographerPtr = nativeSurfaceControl_getChoreographer(surfaceControl); + Log.i(TAG, "mChoreographerPtr value " + mChoreographerPtr); + + float displayRefreshRate = activity.getDisplay().getMode().getRefreshRate(); + float expectedFrameRate = displayRefreshRate / divisor; + + SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + transaction + .setFrameRate(surfaceControl, expectedFrameRate, + Surface.FRAME_RATE_COMPATIBILITY_DEFAULT) + .addTransactionCommittedListener(Runnable::run, + () -> { + assertTrue(mChoreographerPtr != 0L); + Log.i(TAG, "Testing frame rate of " + expectedFrameRate); + nativeTestPostVsyncCallbackAtFrameRate( + mChoreographerPtr, expectedFrameRate); + }) + .apply(); + }); + // wait for the previous callbacks to finish before moving to the next divisor + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds= */ 5L)) { + fail("Test for divisor " + divisor + " not finished in 5 seconds"); + } + } + } + + /** Call from native to trigger test completion. */ + private void endTest() { + Log.i(TAG, "Signal test completion!"); + mTestCompleteSignal.countDown(); + } + + private boolean waitForCountDown(CountDownLatch countDownLatch, long timeoutInSeconds) { + try { + return !countDownLatch.await(timeoutInSeconds, TimeUnit.SECONDS); + } catch (InterruptedException ex) { + throw new AssertionError("Test interrupted", ex); + } + } + + private int toSwitchingType(int matchContentFrameRateUserPreference) { + switch (matchContentFrameRateUserPreference) { + case DisplayManager.MATCH_CONTENT_FRAMERATE_NEVER: + return DisplayManager.SWITCHING_TYPE_NONE; + case DisplayManager.MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY: + return DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS; + case DisplayManager.MATCH_CONTENT_FRAMERATE_ALWAYS: + return DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS; + default: + return -1; + } + } +} diff --git a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java new file mode 100644 index 000000000000..48d050ce4391 --- /dev/null +++ b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java @@ -0,0 +1,481 @@ +/* + * Copyright 2023 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 android.view.choreographertests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.Manifest; +import android.app.compat.CompatChanges; +import android.hardware.display.DisplayManager; +import android.os.Looper; +import android.support.test.uiautomator.UiDevice; +import android.util.Log; +import android.view.Choreographer; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import androidx.lifecycle.Lifecycle; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +public class AttachedChoreographerTest { + private static final String TAG = "AttachedChoreographerTest"; + private static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID = 170503758; + private static final long THRESHOLD_MS = 10; + private static final int FRAME_ITERATIONS = 21; + private static final int CALLBACK_MISSED_THRESHOLD = 2; + + private final CountDownLatch mTestCompleteSignal = new CountDownLatch(2); + private final CountDownLatch mSurfaceCreationCountDown = new CountDownLatch(1); + private final CountDownLatch mNoCallbackSignal = new CountDownLatch(1); + + private ActivityScenario<GraphicsActivity> mScenario; + private int mInitialMatchContentFrameRate; + private DisplayManager mDisplayManager; + private SurfaceView mSurfaceView; + private SurfaceHolder mSurfaceHolder; + private Choreographer mChoreographer; + private boolean mIsFirstCallback = true; + private int mCallbackMissedCounter = 0; + + @Before + public void setUp() throws Exception { + mScenario = ActivityScenario.launch(GraphicsActivity.class); + mScenario.moveToState(Lifecycle.State.CREATED); + mScenario.onActivity(activity -> { + mSurfaceView = activity.findViewById(R.id.surface); + mSurfaceHolder = mSurfaceView.getHolder(); + mSurfaceHolder.addCallback(new SurfaceHolder.Callback() { + + @Override + public void surfaceCreated(SurfaceHolder holder) { + mSurfaceCreationCountDown.countDown(); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, + int height) { + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + } + }); + }); + + UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + uiDevice.wakeUp(); + uiDevice.executeShellCommand("wm dismiss-keyguard"); + mScenario.moveToState(Lifecycle.State.RESUMED); + + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE, + Manifest.permission.READ_COMPAT_CHANGE_CONFIG, + Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE, + Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS, + Manifest.permission.MANAGE_GAME_MODE); + mScenario.onActivity(activity -> { + mDisplayManager = activity.getSystemService(DisplayManager.class); + mInitialMatchContentFrameRate = toSwitchingType( + mDisplayManager.getMatchContentFrameRateUserPreference()); + mDisplayManager.setRefreshRateSwitchingType( + DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY); + mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true); + boolean changeIsEnabled = + CompatChanges.isChangeEnabled( + DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID); + Log.i(TAG, "DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGE_ID is " + + (changeIsEnabled ? "enabled" : "disabled")); + }); + } + + @After + public void tearDown() { + mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate); + mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false); + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .dropShellPermissionIdentity(); + } + + @Test + public void testCreateChoreographer() { + Looper testLooper = Looper.myLooper(); + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + mChoreographer = sc.getChoreographer(); + mTestCompleteSignal.countDown(); + SurfaceControl sc1 = new SurfaceControl(sc, "AttachedChoreographerTests"); + // Create attached choreographer with getChoreographer + Choreographer choreographer1 = sc1.getChoreographer(); + assertTrue(sc1.hasChoreographer()); + assertTrue(sc1.isValid()); + assertEquals(choreographer1, sc1.getChoreographer()); + assertEquals(choreographer1, sc1.getChoreographer(Looper.myLooper())); + assertEquals(choreographer1, sc1.getChoreographer(Looper.getMainLooper())); + assertThrows(IllegalStateException.class, () -> sc1.getChoreographer(testLooper)); + + SurfaceControl sc2 = new SurfaceControl(sc, "AttachedChoreographerTests"); + // Create attached choreographer with Looper.myLooper + Choreographer choreographer2 = sc2.getChoreographer(Looper.myLooper()); + assertTrue(sc2.hasChoreographer()); + assertTrue(sc2.isValid()); + assertEquals(choreographer2, sc2.getChoreographer(Looper.myLooper())); + assertEquals(choreographer2, sc2.getChoreographer(Looper.getMainLooper())); + assertEquals(choreographer2, sc2.getChoreographer()); + assertThrows(IllegalStateException.class, () -> sc2.getChoreographer(testLooper)); + + SurfaceControl sc3 = new SurfaceControl(sc, "AttachedChoreographerTests"); + // Create attached choreographer with Looper.myLooper + Choreographer choreographer3 = sc3.getChoreographer(Looper.getMainLooper()); + assertTrue(sc3.hasChoreographer()); + assertTrue(sc3.isValid()); + assertEquals(choreographer3, sc3.getChoreographer(Looper.getMainLooper())); + assertEquals(choreographer3, sc3.getChoreographer(Looper.myLooper())); + assertEquals(choreographer3, sc3.getChoreographer()); + assertThrows(IllegalStateException.class, () -> sc3.getChoreographer(testLooper)); + + assertNotEquals(choreographer1, choreographer2); + assertNotEquals(choreographer1, choreographer3); + assertNotEquals(choreographer2, choreographer3); + sc1.release(); + sc2.release(); + sc3.release(); + mTestCompleteSignal.countDown(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) { + fail("Test not finished in 2 Seconds"); + } + SurfaceControl surfaceControl = mSurfaceView.getSurfaceControl(); + assertTrue(surfaceControl.hasChoreographer()); + assertEquals(mChoreographer, surfaceControl.getChoreographer()); + assertThrows(IllegalStateException.class, + () -> surfaceControl.getChoreographer(testLooper)); + } + + @Test + public void testCopySurfaceControl() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + // Create attached choreographer + sc.getChoreographer(); + assertTrue(sc.hasChoreographer()); + + // Use copy constructor + SurfaceControl copyConstructorSc = new SurfaceControl(sc, "AttachedChoreographerTests"); + //Choreographer isn't copied over. + assertFalse(copyConstructorSc.hasChoreographer()); + copyConstructorSc.getChoreographer(); + assertTrue(copyConstructorSc.hasChoreographer()); + mTestCompleteSignal.countDown(); + + // Use copyFrom + SurfaceControl copyFromSc = new SurfaceControl(); + copyFromSc.copyFrom(sc, "AttachedChoreographerTests"); + //Choreographer isn't copied over. + assertFalse(copyFromSc.hasChoreographer()); + copyFromSc.getChoreographer(); + assertTrue(copyFromSc.hasChoreographer()); + mTestCompleteSignal.countDown(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) { + fail("Test not finished in 2 Seconds"); + } + } + + @Test + public void testMirrorSurfaceControl() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + // Create attached choreographer + sc.getChoreographer(); + assertTrue(sc.hasChoreographer()); + mTestCompleteSignal.countDown(); + + // Use mirrorSurface + SurfaceControl mirrorSc = SurfaceControl.mirrorSurface(sc); + //Choreographer isn't copied over. + assertFalse(mirrorSc.hasChoreographer()); + mirrorSc.getChoreographer(); + assertTrue(mirrorSc.hasChoreographer()); + // make SurfaceControl invalid by releasing it. + mirrorSc.release(); + + assertTrue(sc.isValid()); + assertFalse(mirrorSc.isValid()); + assertFalse(mirrorSc.hasChoreographer()); + assertThrows(NullPointerException.class, mirrorSc::getChoreographer); + mTestCompleteSignal.countDown(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) { + fail("Test not finished in 2 Seconds"); + } + } + + @Test + public void testPostFrameCallback() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + sc.getChoreographer().postFrameCallback( + frameTimeNanos -> mTestCompleteSignal.countDown()); + + SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests"); + Choreographer copyChoreographer = copySc.getChoreographer(); + // make SurfaceControl invalid by releasing it. + copySc.release(); + + assertTrue(sc.isValid()); + assertFalse(copySc.isValid()); + copyChoreographer.postFrameCallback(frameTimeNanos -> mNoCallbackSignal.countDown()); + assertDoesReceiveCallback(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) { + fail("Test not finished in 2 Seconds"); + } + } + + @Test + public void testPostFrameCallbackDelayed() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + sc.getChoreographer(Looper.getMainLooper()).postFrameCallbackDelayed( + callback -> mTestCompleteSignal.countDown(), + /* delayMillis */ 5); + + SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests"); + Choreographer copyChoreographer = copySc.getChoreographer(); + // make SurfaceControl invalid by releasing it. + copySc.release(); + + assertTrue(sc.isValid()); + assertFalse(copySc.isValid()); + copyChoreographer.postFrameCallbackDelayed( + frameTimeNanos -> mNoCallbackSignal.countDown(), /* delayMillis */5); + assertDoesReceiveCallback(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) { + fail("Test not finished in 2 Seconds"); + } + } + + @Test + public void testPostCallback() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + sc.getChoreographer().postCallback(Choreographer.CALLBACK_COMMIT, + mTestCompleteSignal::countDown, /* token */ this); + + SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests"); + Choreographer copyChoreographer = copySc.getChoreographer(); + // make SurfaceControl invalid by releasing it. + copySc.release(); + + assertTrue(sc.isValid()); + assertFalse(copySc.isValid()); + copyChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, + mNoCallbackSignal::countDown, /* token */ this); + assertDoesReceiveCallback(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) { + fail("Test not finished in 2 Seconds"); + } + } + + @Test + public void testPostCallbackDelayed() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + sc.getChoreographer().postCallbackDelayed(Choreographer.CALLBACK_COMMIT, + mTestCompleteSignal::countDown, /* token */ this, /* delayMillis */ 5); + + SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests"); + Choreographer copyChoreographer = copySc.getChoreographer(); + // make SurfaceControl invalid by releasing it. + copySc.release(); + + assertTrue(sc.isValid()); + assertFalse(copySc.isValid()); + copyChoreographer.postCallbackDelayed(Choreographer.CALLBACK_COMMIT, + mNoCallbackSignal::countDown, /* token */ this, /* delayMillis */ 5); + assertDoesReceiveCallback(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) { + fail("Test not finished in 2 Seconds"); + } + } + + @Test + public void testPostVsyncCallback() { + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeout */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + sc.getChoreographer().postVsyncCallback(data -> mTestCompleteSignal.countDown()); + + SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests"); + Choreographer copyChoreographer = copySc.getChoreographer(); + // make SurfaceControl invalid by releasing it. + copySc.release(); + + assertTrue(sc.isValid()); + assertFalse(copySc.isValid()); + copyChoreographer.postVsyncCallback(data -> mNoCallbackSignal.countDown()); + assertDoesReceiveCallback(); + }); + if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) { + fail("Test not finished in 2 Seconds"); + } + } + + @Test + public void testChoreographerDivisorRefreshRate() { + for (int divisor : new int[]{2, 3}) { + CountDownLatch continueLatch = new CountDownLatch(1); + mScenario.onActivity(activity -> { + if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) { + fail("Unable to create surface within 1 Second"); + } + SurfaceControl sc = mSurfaceView.getSurfaceControl(); + Choreographer choreographer = sc.getChoreographer(); + SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + float displayRefreshRate = activity.getDisplay().getMode().getRefreshRate(); + float fps = displayRefreshRate / divisor; + long callbackDurationMs = Math.round(1000 / fps); + mCallbackMissedCounter = 0; + transaction.setFrameRate(sc, fps, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE) + .addTransactionCommittedListener(Runnable::run, + () -> verifyVsyncCallbacks(choreographer, + callbackDurationMs, continueLatch, FRAME_ITERATIONS)) + .apply(); + }); + // wait for the previous callbacks to finish before moving to the next divisor + if (waitForCountDown(continueLatch, /* timeoutInSeconds */ 5L)) { + fail("Test not finished in 5 Seconds"); + } + } + } + + private void verifyVsyncCallbacks(Choreographer choreographer, long callbackDurationMs, + CountDownLatch continueLatch, int frameCount) { + long callbackRequestedTimeNs = System.nanoTime(); + choreographer.postVsyncCallback(frameData -> { + if (frameCount > 0) { + if (!mIsFirstCallback) { + // Skip the first callback as it takes 1 frame + // to reflect the new refresh rate + long callbackDurationDiffMs = getCallbackDurationDiffInMs( + frameData.getFrameTimeNanos(), + callbackRequestedTimeNs, callbackDurationMs); + if (callbackDurationDiffMs < 0 || callbackDurationDiffMs > THRESHOLD_MS) { + mCallbackMissedCounter++; + Log.e(TAG, "Frame #" + Math.abs(frameCount - FRAME_ITERATIONS) + + " vsync callback failed, expected callback in " + + callbackDurationMs + + " With threshold of " + THRESHOLD_MS + + " but actual duration difference is " + callbackDurationDiffMs); + } + } + mIsFirstCallback = false; + verifyVsyncCallbacks(choreographer, callbackDurationMs, + continueLatch, frameCount - 1); + } else { + assertTrue("Missed timeline for " + mCallbackMissedCounter + " callbacks, while " + + CALLBACK_MISSED_THRESHOLD + " missed callbacks are allowed", + mCallbackMissedCounter <= CALLBACK_MISSED_THRESHOLD); + continueLatch.countDown(); + } + }); + } + + private long getCallbackDurationDiffInMs(long callbackTimeNs, long requestedTimeNs, + long expectedCallbackMs) { + long actualTimeMs = TimeUnit.NANOSECONDS.toMillis(callbackTimeNs) + - TimeUnit.NANOSECONDS.toMillis(requestedTimeNs); + return Math.abs(expectedCallbackMs - actualTimeMs); + } + + private boolean waitForCountDown(CountDownLatch countDownLatch, long timeoutInSeconds) { + try { + return !countDownLatch.await(timeoutInSeconds, TimeUnit.SECONDS); + } catch (InterruptedException ex) { + throw new AssertionError("Test interrupted", ex); + } + } + + private int toSwitchingType(int matchContentFrameRateUserPreference) { + switch (matchContentFrameRateUserPreference) { + case DisplayManager.MATCH_CONTENT_FRAMERATE_NEVER: + return DisplayManager.SWITCHING_TYPE_NONE; + case DisplayManager.MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY: + return DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS; + case DisplayManager.MATCH_CONTENT_FRAMERATE_ALWAYS: + return DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS; + default: + return -1; + } + } + + private void assertDoesReceiveCallback() { + try { + if (mNoCallbackSignal.await(/* timeout */ 50L, TimeUnit.MILLISECONDS)) { + fail("Callback not supposed to be generated"); + } else { + mTestCompleteSignal.countDown(); + } + } catch (InterruptedException e) { + fail("Callback wait is interrupted " + e); + } + } +} diff --git a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/GraphicsActivity.java b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/GraphicsActivity.java new file mode 100644 index 000000000000..50a68508334e --- /dev/null +++ b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/GraphicsActivity.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 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 android.view.choreographertests; + +import android.app.Activity; +import android.os.Bundle; + +public class GraphicsActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_attached_choreographer); + } +} diff --git a/tests/ChoreographerTests/src/main/res/layout/activity_attached_choreographer.xml b/tests/ChoreographerTests/src/main/res/layout/activity_attached_choreographer.xml new file mode 100644 index 000000000000..d6c821228c9a --- /dev/null +++ b/tests/ChoreographerTests/src/main/res/layout/activity_attached_choreographer.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2023 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. + --> + +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <SurfaceView + android:id="@+id/surface" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent"/> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/tests/ChoreographerTests/src/main/res/mipmap-hdpi/ic_launcher.png b/tests/ChoreographerTests/src/main/res/mipmap-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..cde69bcccec6 --- /dev/null +++ b/tests/ChoreographerTests/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/tests/ChoreographerTests/src/main/res/mipmap-mdpi/ic_launcher.png b/tests/ChoreographerTests/src/main/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..c133a0cbd379 --- /dev/null +++ b/tests/ChoreographerTests/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/tests/ChoreographerTests/src/main/res/mipmap-xhdpi/ic_launcher.png b/tests/ChoreographerTests/src/main/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..bfa42f0e7b91 --- /dev/null +++ b/tests/ChoreographerTests/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/tests/ChoreographerTests/src/main/res/mipmap-xxhdpi/ic_launcher.png b/tests/ChoreographerTests/src/main/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..324e72cdd748 --- /dev/null +++ b/tests/ChoreographerTests/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/tests/CanvasCompare/res/values/values.xml b/tests/ChoreographerTests/src/main/res/values/strings.xml index f69378d34f65..e66b001e105e 100644 --- a/tests/CanvasCompare/res/values/values.xml +++ b/tests/ChoreographerTests/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2012 The Android Open Source Project +<!-- Copyright 2023 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. @@ -14,12 +14,5 @@ limitations under the License. --> <resources> - - <!-- NOTE: the below MUST be multiples of 64 --> - <dimen name="layer_height">320px</dimen> - <dimen name="layer_width">320px</dimen> - - <dimen name="layer_height_double">640px</dimen> - <dimen name="layer_width_double">640px</dimen> - + <string name="app_name">ChoreographerTests</string> </resources> diff --git a/tests/Codegen/Android.bp b/tests/Codegen/Android.bp index ddbf16817b94..7fbe3b37f99e 100644 --- a/tests/Codegen/Android.bp +++ b/tests/Codegen/Android.bp @@ -24,6 +24,14 @@ android_test { plugins: [ "staledataclass-annotation-processor", ], + // Exports needed for staledataclass-annotation-processor, see b/139342589. + javacflags: [ + "-J--add-modules=jdk.compiler", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED", + ], static_libs: [ "junit", "hamcrest", diff --git a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java index 5430dee5ca31..4299e0d616fb 100644 --- a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java +++ b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java @@ -19,8 +19,11 @@ package com.android.server.pm.dex; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assume.assumeFalse; + import android.app.UiAutomation; import android.content.Context; +import android.content.pm.PackageManager; import android.os.Build; import android.os.ParcelFileDescriptor; import android.os.SystemClock; @@ -84,7 +87,7 @@ public final class DynamicCodeLoggerIntegrationTests { // avoid flakiness we run these tests multiple times, allowing progressively longer between // code loading and checking the logs on each try.) private static final int AUDIT_LOG_RETRIES = 10; - private static final int RETRY_DELAY_MS = 2_000; + private static final int RETRY_DELAY_MS = 500; private static Context sContext; private static int sMyUid; @@ -96,7 +99,12 @@ public final class DynamicCodeLoggerIntegrationTests { } @Before - public void primeEventLog() { + public void setup() { + assumeFalse(sContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)); + primeEventLog(); + } + + private void primeEventLog() { // Force a round trip to logd to make sure everything is up to date. // Without this the first test passes and others don't - we don't see new events in the // log. The exact reason is unclear. @@ -245,7 +253,7 @@ public final class DynamicCodeLoggerIntegrationTests { "/DynamicCodeLoggerNativeExecutable", privateCopyFile); EventLog.writeEvent(EventLog.getTagCode("auditd"), - "type=1400 avc: granted { execute_no_trans } " + "type=1400 avc: granted { execute_no_trans } " + "path=\"" + privateCopyFile + "\" " + "scontext=u:r:untrusted_app: " + "tcontext=u:object_r:app_data_file: " diff --git a/tests/EnforcePermission/Android.bp b/tests/EnforcePermission/Android.bp new file mode 100644 index 000000000000..719a89817a9d --- /dev/null +++ b/tests/EnforcePermission/Android.bp @@ -0,0 +1,22 @@ +// Copyright (C) 2023 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 { + default_applicable_licenses: ["frameworks_base_license"], +} + +filegroup { + name: "frameworks-enforce-permission-test-aidl", + srcs: ["aidl/**/*.aidl"], +} diff --git a/tests/EnforcePermission/OWNERS b/tests/EnforcePermission/OWNERS new file mode 100644 index 000000000000..39550a394f33 --- /dev/null +++ b/tests/EnforcePermission/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 315013 +tweek@google.com +brufino@google.com diff --git a/tests/EnforcePermission/TEST_MAPPING b/tests/EnforcePermission/TEST_MAPPING new file mode 100644 index 000000000000..a1bf42a44e86 --- /dev/null +++ b/tests/EnforcePermission/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "EnforcePermissionTests" + } + ] +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java b/tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl index 06f7a13f73d7..1eb773dc19b8 100644 --- a/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java +++ b/tests/EnforcePermission/aidl/android/tests/enforcepermission/INested.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2023 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. @@ -13,7 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.content.componentalias.tests.b; -public class Target01 extends BaseReceiver { +package android.tests.enforcepermission; + +interface INested { + @EnforcePermission("ACCESS_NETWORK_STATE") + void ProtectedByAccessNetworkState(); + + @EnforcePermission("READ_SYNC_SETTINGS") + void ProtectedByReadSyncSettings(); } diff --git a/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl b/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl new file mode 100644 index 000000000000..18e3aecfa832 --- /dev/null +++ b/tests/EnforcePermission/aidl/android/tests/enforcepermission/IProtected.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 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 android.tests.enforcepermission; + +interface IProtected { + @EnforcePermission("INTERNET") + void ProtectedByInternet(); + + @EnforcePermission("VIBRATE") + void ProtectedByVibrate(); + + @EnforcePermission("INTERNET") + void ProtectedByInternetAndVibrateImplicitly(); + + @EnforcePermission("INTERNET") + void ProtectedByInternetAndAccessNetworkStateImplicitly(); + + @EnforcePermission("INTERNET") + void ProtectedByInternetAndReadSyncSettingsImplicitly(); +} diff --git a/tests/EnforcePermission/service-app/Android.bp b/tests/EnforcePermission/service-app/Android.bp new file mode 100644 index 000000000000..a4ac1d7c6134 --- /dev/null +++ b/tests/EnforcePermission/service-app/Android.bp @@ -0,0 +1,32 @@ +// Copyright (C) 2023 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test_helper_app { + name: "EnforcePermissionTestHelper", + srcs: [ + "src/**/*.java", + ":frameworks-enforce-permission-test-aidl", + ], + platform_apis: true, + certificate: "platform", +} diff --git a/tests/EnforcePermission/service-app/AndroidManifest.xml b/tests/EnforcePermission/service-app/AndroidManifest.xml new file mode 100644 index 000000000000..ddafe15ab88f --- /dev/null +++ b/tests/EnforcePermission/service-app/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 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. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.tests.enforcepermission.service"> + <application> + <service + android:name=".TestService" + android:exported="true" /> + + <service + android:name=".NestedTestService" + android:exported="true" /> + </application> +</manifest> diff --git a/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java new file mode 100644 index 000000000000..7879a1214c01 --- /dev/null +++ b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/NestedTestService.java @@ -0,0 +1,48 @@ +/** + * Copyright (C) 2023 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 android.tests.enforcepermission.service; + +import android.annotation.EnforcePermission; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.tests.enforcepermission.INested; +import android.util.Log; + +public class NestedTestService extends Service { + private static final String TAG = "EnforcePermission.NestedTestService"; + + @Override + public IBinder onBind(Intent intent) { + Log.i(TAG, "onBind"); + return mBinder; + } + + private final INested.Stub mBinder = new INested.Stub() { + @Override + @EnforcePermission(android.Manifest.permission.ACCESS_NETWORK_STATE) + public void ProtectedByAccessNetworkState() { + ProtectedByAccessNetworkState_enforcePermission(); + } + + @Override + @EnforcePermission(android.Manifest.permission.READ_SYNC_SETTINGS) + public void ProtectedByReadSyncSettings() { + ProtectedByReadSyncSettings_enforcePermission(); + } + }; +} diff --git a/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java new file mode 100644 index 000000000000..e9b897db1294 --- /dev/null +++ b/tests/EnforcePermission/service-app/src/android/tests/enforcepermission/service/TestService.java @@ -0,0 +1,119 @@ +/** + * Copyright (C) 2023 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 android.tests.enforcepermission.service; + +import android.annotation.EnforcePermission; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.tests.enforcepermission.INested; +import android.tests.enforcepermission.IProtected; +import android.util.Log; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class TestService extends Service { + + private static final String TAG = "EnforcePermission.TestService"; + private volatile ServiceConnection mNestedServiceConnection; + + @Override + public void onCreate() { + mNestedServiceConnection = new ServiceConnection(); + Intent intent = new Intent(this, NestedTestService.class); + boolean bound = bindService(intent, mNestedServiceConnection, Context.BIND_AUTO_CREATE); + if (!bound) { + Log.wtf(TAG, "bindService() on NestedTestService failed"); + } + } + + @Override + public void onDestroy() { + unbindService(mNestedServiceConnection); + } + + private static final class ServiceConnection implements android.content.ServiceConnection { + private volatile CompletableFuture<INested> mFuture = new CompletableFuture<>(); + + public INested get() { + try { + return mFuture.get(1, TimeUnit.SECONDS); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + throw new RuntimeException("Unable to reach NestedTestService: " + e.getMessage()); + } + } + + public void onServiceConnected(ComponentName className, IBinder service) { + mFuture.complete(INested.Stub.asInterface(service)); + } + + public void onServiceDisconnected(ComponentName className) { + mFuture = new CompletableFuture<>(); + } + }; + + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + private final IProtected.Stub mBinder = new IProtected.Stub() { + @Override + @EnforcePermission(android.Manifest.permission.INTERNET) + public void ProtectedByInternet() { + ProtectedByInternet_enforcePermission(); + } + + @Override + @EnforcePermission(android.Manifest.permission.VIBRATE) + public void ProtectedByVibrate() { + ProtectedByVibrate_enforcePermission(); + } + + @Override + @EnforcePermission(android.Manifest.permission.INTERNET) + public void ProtectedByInternetAndVibrateImplicitly() { + ProtectedByInternetAndVibrateImplicitly_enforcePermission(); + + ProtectedByVibrate(); + } + + @Override + @EnforcePermission(android.Manifest.permission.INTERNET) + public void ProtectedByInternetAndAccessNetworkStateImplicitly() throws RemoteException { + ProtectedByInternetAndAccessNetworkStateImplicitly_enforcePermission(); + + mNestedServiceConnection.get().ProtectedByAccessNetworkState(); + + } + + @Override + @EnforcePermission(android.Manifest.permission.INTERNET) + public void ProtectedByInternetAndReadSyncSettingsImplicitly() throws RemoteException { + ProtectedByInternetAndReadSyncSettingsImplicitly_enforcePermission(); + + mNestedServiceConnection.get().ProtectedByReadSyncSettings(); + } + }; +} diff --git a/tests/EnforcePermission/test-app/Android.bp b/tests/EnforcePermission/test-app/Android.bp new file mode 100644 index 000000000000..cd53854189b7 --- /dev/null +++ b/tests/EnforcePermission/test-app/Android.bp @@ -0,0 +1,38 @@ +// Copyright (C) 2023 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 { + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "EnforcePermissionTests", + srcs: [ + "src/**/*.java", + ":frameworks-enforce-permission-test-aidl", + ], + static_libs: [ + "androidx.test.rules", + ], + libs: [ + "android.test.base", + "android.test.runner", + ], + data: [ + ":EnforcePermissionTestHelper", + ], + platform_apis: true, + certificate: "platform", + test_suites: ["device-tests"], +} diff --git a/tests/SoundTriggerTests/AndroidManifest.xml b/tests/EnforcePermission/test-app/AndroidManifest.xml index f7454c752b7d..4a0c6a86628f 100644 --- a/tests/SoundTriggerTests/AndroidManifest.xml +++ b/tests/EnforcePermission/test-app/AndroidManifest.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- Copyright (C) 2023 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. @@ -13,17 +13,20 @@ See the License for the specific language governing permissions and limitations under the License. --> - <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.hardware.soundtrigger"> - <uses-permission android:name="android.permission.MANAGE_SOUND_TRIGGER" /> + package="android.tests.enforcepermission.tests"> + + <!-- Expected for the tests (not actually used) --> <uses-permission android:name="android.permission.INTERNET" /> - + <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> + + <queries> + <package android:name="android.tests.enforcepermission.service" /> + </queries> + <application> <uses-library android:name="android.test.runner" /> </application> - - <instrumentation android:name="android.test.InstrumentationTestRunner" - android:targetPackage="android.hardware.soundtrigger" - android:label="Tests for android.hardware.soundtrigger" /> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.tests.enforcepermission.tests"/> </manifest> diff --git a/tests/EnforcePermission/test-app/AndroidTest.xml b/tests/EnforcePermission/test-app/AndroidTest.xml new file mode 100644 index 000000000000..120381a7fb83 --- /dev/null +++ b/tests/EnforcePermission/test-app/AndroidTest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 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. +--> +<configuration description="Runs EnforcePermission End-to-End Tests"> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="EnforcePermissionTestHelper.apk"/> + <option name="test-file-name" value="EnforcePermissionTests.apk"/> + <option name="cleanup-apks" value="true" /> + </target_preparer> + + <option name="test-tag" value="EnforcePermissionTests"/> + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="android.tests.enforcepermission.tests"/> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/> + </test> +</configuration> diff --git a/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java b/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java new file mode 100644 index 000000000000..d2a4a037f125 --- /dev/null +++ b/tests/EnforcePermission/test-app/src/android/tests/enforcepermission/tests/ServiceTest.java @@ -0,0 +1,129 @@ +/** + * Copyright (C) 2023 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 android.tests.enforcepermission.tests; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; +import android.tests.enforcepermission.IProtected; +import android.util.Log; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +@RunWith(AndroidJUnit4.class) +public class ServiceTest { + + private static final String TAG = "EnforcePermission.Tests"; + private static final String SERVICE_NAME = "android.tests.enforcepermission.service"; + private static final int SERVICE_TIMEOUT_SEC = 5; + + private Context mContext; + private volatile ServiceConnection mServiceConnection; + + @Before + public void bindTestService() throws Exception { + Log.d(TAG, "bindTestService"); + mContext = InstrumentationRegistry.getTargetContext(); + mServiceConnection = new ServiceConnection(); + Intent intent = new Intent(); + intent.setClassName(SERVICE_NAME, SERVICE_NAME + ".TestService"); + assertTrue(mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)); + } + + @After + public void unbindTestService() throws Exception { + mContext.unbindService(mServiceConnection); + } + + private static final class ServiceConnection implements android.content.ServiceConnection { + private volatile CompletableFuture<IProtected> mFuture = new CompletableFuture<>(); + + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + mFuture.complete(IProtected.Stub.asInterface(service)); + } + + @Override + public void onServiceDisconnected(ComponentName className) { + mFuture = new CompletableFuture<>(); + } + + public IProtected get() { + try { + return mFuture.get(SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + throw new RuntimeException("Unable to reach TestService: " + e.toString()); + } + } + } + + @Test + public void testImmediatePermissionGranted_succeeds() + throws RemoteException { + mServiceConnection.get().ProtectedByInternet(); + } + + @Test + public void testImmediatePermissionNotGranted_fails() + throws RemoteException { + final Exception ex = assertThrows(SecurityException.class, + () -> mServiceConnection.get().ProtectedByVibrate()); + assertThat(ex.getMessage(), containsString("VIBRATE")); + } + + @Test + public void testImmediatePermissionGrantedButImplicitLocalNotGranted_fails() + throws RemoteException { + final Exception ex = assertThrows(SecurityException.class, + () -> mServiceConnection.get().ProtectedByInternetAndVibrateImplicitly()); + assertThat(ex.getMessage(), containsString("VIBRATE")); + } + + @Test + public void testImmediatePermissionGrantedButImplicitNestedNotGranted_fails() + throws RemoteException { + final Exception ex = assertThrows(SecurityException.class, + () -> mServiceConnection.get() + .ProtectedByInternetAndAccessNetworkStateImplicitly()); + assertThat(ex.getMessage(), containsString("ACCESS_NETWORK_STATE")); + } + + @Test + public void testImmediatePermissionGrantedAndImplicitNestedGranted_succeeds() + throws RemoteException { + mServiceConnection.get().ProtectedByInternetAndReadSyncSettingsImplicitly(); + } +} diff --git a/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java b/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java index 2e515705a253..761efe4a8484 100644 --- a/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java +++ b/tests/FixVibrateSetting/src/com/android/fixvibratesetting/FixVibrateSetting.java @@ -110,7 +110,7 @@ public class FixVibrateSetting extends Activity implements View.OnClickListener private void test() { Intent intent = new Intent(this, FixVibrateSetting.class); - PendingIntent pending = PendingIntent.getActivity(this, 0, intent, 0); + PendingIntent pending = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE); Notification n = new Notification.Builder(this) .setSmallIcon(R.drawable.stat_sys_warning) diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp index 7731e098d9f5..4ba538ed9d45 100644 --- a/tests/FlickerTests/Android.bp +++ b/tests/FlickerTests/Android.bp @@ -42,10 +42,16 @@ android_test { "androidx.test.ext.junit", "flickertestapplib", "flickerlib", + "flickerlib-apphelpers", + "flickerlib-helpers", "truth-prebuilt", "launcher-helper-lib", "launcher-aosp-tapl", "platform-test-annotations", + "wm-flicker-window-extensions", + ], + data: [ + ":FlickerTestApp", ], } @@ -64,6 +70,7 @@ java_library { ], static_libs: [ "flickerlib", + "flickerlib-helpers", "truth-prebuilt", "app-helpers-core", ], @@ -79,9 +86,27 @@ java_library { "**/helpers/*", ], static_libs: [ - "flickerlib", "flickertestapplib", + "flickerlib", + "flickerlib-apphelpers", + "flickerlib-helpers", "truth-prebuilt", "app-helpers-core", + "wm-flicker-window-extensions", + ], +} + +android_library_import { + name: "wm-flicker-window-extensions_nodeps", + aars: ["libs/window-extensions-release.aar"], + sdk_version: "current", +} + +java_library { + name: "wm-flicker-window-extensions", + sdk_version: "current", + static_libs: [ + "wm-flicker-window-extensions_nodeps", ], + installable: false, } diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/AndroidManifest.xml index fda609196456..462f91bc081c 100644 --- a/tests/FlickerTests/AndroidManifest.xml +++ b/tests/FlickerTests/AndroidManifest.xml @@ -40,9 +40,12 @@ <uses-permission android:name="android.permission.READ_LOGS"/> <!-- ATM.removeRootTasksWithActivityTypes() --> <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" /> + <!-- ActivityOptions.makeCustomTaskAnimation() --> + <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" /> <!-- Allow the test to write directly to /sdcard/ --> - <application android:requestLegacyExternalStorage="true"> + <application android:requestLegacyExternalStorage="true" android:largeHeap="true"> <uses-library android:name="android.test.runner"/> + <uses-library android:name="androidx.window.extensions" android:required="false"/> </application> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml index 566c725a3414..32ff243921ec 100644 --- a/tests/FlickerTests/AndroidTest.xml +++ b/tests/FlickerTests/AndroidTest.xml @@ -13,8 +13,26 @@ <option name="run-command" value="cmd window tracing level all" /> <!-- set WM tracing to frame (avoid incomplete states) --> <option name="run-command" value="cmd window tracing frame" /> + <!-- ensure lock screen mode is swipe --> + <option name="run-command" value="locksettings set-disabled false" /> + <!-- disable betterbug as it's log collection dialogues cause flakes in e2e tests --> + <option name="run-command" value="pm disable com.google.android.internal.betterbug" /> <!-- restart launcher to activate TAPL --> <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" /> + <!-- Ensure output directory is empty at the start --> + <option name="run-command" value="rm -rf /sdcard/flicker" /> + <!-- Increase trace size: 20mb for WM and 80mb for SF --> + <option name="run-command" value="cmd window tracing size 20480" /> + <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" /> + <option name="run-command" value="settings put system show_touches 1" /> + <option name="run-command" value="settings put system pointer_location 1" /> + <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" /> + <option name="teardown-command" value="settings delete system show_touches" /> + <option name="teardown-command" value="settings delete system pointer_location" /> + <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> diff --git a/tests/FlickerTests/libs/window-extensions-release.aar b/tests/FlickerTests/libs/window-extensions-release.aar Binary files differnew file mode 100644 index 000000000000..918e514f4c89 --- /dev/null +++ b/tests/FlickerTests/libs/window-extensions-release.aar diff --git a/tests/FlickerTests/res/anim/show_hide_show_3000ms.xml b/tests/FlickerTests/res/anim/show_hide_show_3000ms.xml new file mode 100644 index 000000000000..7b3f07e3a2f5 --- /dev/null +++ b/tests/FlickerTests/res/anim/show_hide_show_3000ms.xml @@ -0,0 +1,31 @@ +<!-- + ~ 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. + --> + +<set + xmlns:android="http://schemas.android.com/apk/res/android" + android:fillAfter="true"> + + <alpha + android:fromAlpha="1.0" + android:toAlpha="0.0" + android:duration="1000" /> + + <alpha + android:startOffset="2000" + android:fromAlpha="1.0" + android:toAlpha="1.0" + android:duration="1000" /> +</set>
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt new file mode 100644 index 000000000000..efd9b004f8fd --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt @@ -0,0 +1,209 @@ +/* + * 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.android.server.wm.flicker + +import android.app.Instrumentation +import android.platform.test.annotations.Presubmit +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerBuilderProvider +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.util.Log +import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.Assume +import org.junit.AssumptionViolatedException +import org.junit.Test + +/** + * Base test class containing common assertions for [ComponentNameMatcher.NAV_BAR], + * [ComponentNameMatcher.TASK_BAR], [ComponentNameMatcher.STATUS_BAR], and general assertions + * (layers visible in consecutive states, entire screen covered, etc.) + */ +abstract class BaseTest +@JvmOverloads +constructor( + protected val flicker: FlickerTest, + protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(), + protected val tapl: LauncherInstrumentation = LauncherInstrumentation() +) { + init { + tapl.setExpectedRotationCheckEnabled(true) + } + + private val logTag = this::class.java.simpleName + + /** Specification of the test transition to execute */ + abstract val transition: FlickerBuilder.() -> Unit + + /** + * Entry point for the test runner. It will use this method to initialize and cache flicker + * executions + */ + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { flicker.scenario.setIsTablet(wmHelper.currentState.wmState.isTablet) } + transition() + } + } + + /** Checks that all parts of the screen are covered during the transition */ + @Presubmit @Test open fun entireScreenCovered() = flicker.entireScreenCovered() + + /** + * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition + * + * Note: Phones only + */ + @Presubmit + @Test + open fun navBarLayerIsVisibleAtStartAndEnd() { + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.navBarLayerIsVisibleAtStartAndEnd() + } + + /** + * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the + * transition + * + * Note: Phones only + */ + @Presubmit + @Test + open fun navBarLayerPositionAtStartAndEnd() { + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.navBarLayerPositionAtStartAndEnd() + } + + /** + * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition + * + * Note: Phones only + */ + @Presubmit + @Test + open fun navBarWindowIsAlwaysVisible() { + Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(flicker.scenario.isLandscapeOrSeascapeAtStart) + flicker.navBarWindowIsAlwaysVisible() + } + + /** + * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible at the start and end of the + * transition + * + * Note: Phones only + */ + @Presubmit + @Test + open fun navBarWindowIsVisibleAtStartAndEnd() { + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.navBarWindowIsVisibleAtStartAndEnd() + } + + /** + * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible at the start and end of the + * transition + * + * Note: Large screen only + */ + @Presubmit + @Test + open fun taskBarLayerIsVisibleAtStartAndEnd() { + Assume.assumeTrue(flicker.scenario.isTablet) + flicker.taskBarLayerIsVisibleAtStartAndEnd() + } + + /** + * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition + * + * Note: Large screen only + */ + @Presubmit + @Test + open fun taskBarWindowIsAlwaysVisible() { + Assume.assumeTrue(flicker.scenario.isTablet) + flicker.taskBarWindowIsAlwaysVisible() + } + + /** + * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the start and end of + * the transition + */ + @Presubmit + @Test + open fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd() + + /** + * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the + * transition + */ + @Presubmit + @Test + open fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd() + + /** + * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole + * transition + */ + @Presubmit + @Test + open fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible() + + /** + * Checks that all layers that are visible on the trace, are visible for at least 2 consecutive + * entries. + */ + @Presubmit + @Test + open fun visibleLayersShownMoreThanOneConsecutiveEntry() { + flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() } + } + + /** + * Checks that all windows that are visible on the trace, are visible for at least 2 consecutive + * entries. + */ + @Presubmit + @Test + open fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() } + } + + open fun cujCompleted() { + runAndIgnoreAssumptionViolation { entireScreenCovered() } + runAndIgnoreAssumptionViolation { statusBarLayerIsVisibleAtStartAndEnd() } + runAndIgnoreAssumptionViolation { statusBarLayerPositionAtStartAndEnd() } + runAndIgnoreAssumptionViolation { statusBarWindowIsAlwaysVisible() } + runAndIgnoreAssumptionViolation { visibleLayersShownMoreThanOneConsecutiveEntry() } + runAndIgnoreAssumptionViolation { visibleWindowsShownMoreThanOneConsecutiveEntry() } + runAndIgnoreAssumptionViolation { taskBarLayerIsVisibleAtStartAndEnd() } + runAndIgnoreAssumptionViolation { taskBarWindowIsAlwaysVisible() } + runAndIgnoreAssumptionViolation { navBarLayerIsVisibleAtStartAndEnd() } + runAndIgnoreAssumptionViolation { navBarWindowIsAlwaysVisible() } + runAndIgnoreAssumptionViolation { navBarWindowIsVisibleAtStartAndEnd() } + } + + protected fun runAndIgnoreAssumptionViolation(predicate: () -> Unit) { + try { + predicate() + } catch (e: AssumptionViolatedException) { + Log.e(logTag, "Assumption violation on CUJ complete", e) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt index 7f309e1974e1..ed9e14fd86da 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -15,185 +15,301 @@ */ @file:JvmName("CommonAssertions") + package com.android.server.wm.flicker -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.traces.common.FlickerComponentName +import android.tools.common.PlatformConsts +import android.tools.common.flicker.subject.region.RegionSubject +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.common.traces.component.IComponentNameMatcher +import android.tools.common.traces.wm.WindowManagerTrace +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.helpers.WindowUtils -val LAUNCHER_COMPONENT = FlickerComponentName("com.google.android.apps.nexuslauncher", - "com.google.android.apps.nexuslauncher.NexusLauncherActivity") +/** + * Checks that [ComponentNameMatcher.STATUS_BAR] window is visible and above the app windows in all + * WM trace entries + */ +fun FlickerTest.statusBarWindowIsAlwaysVisible() { + assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.STATUS_BAR) } +} /** - * Checks that [FlickerComponentName.STATUS_BAR] window is visible and above the app windows in - * all WM trace entries + * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows in all WM + * trace entries */ -fun FlickerTestParameter.statusBarWindowIsVisible() { - assertWm { - this.isAboveAppWindowVisible(FlickerComponentName.STATUS_BAR) - } +fun FlickerTest.navBarWindowIsAlwaysVisible() { + assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) } } /** - * Checks that [FlickerComponentName.NAV_BAR] window is visible and above the app windows in - * all WM trace entries + * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the + * start and end of the WM trace */ -fun FlickerTestParameter.navBarWindowIsVisible() { - assertWm { - this.isAboveAppWindowVisible(FlickerComponentName.NAV_BAR) - } +fun FlickerTest.navBarWindowIsVisibleAtStartAndEnd() { + this.navBarWindowIsVisibleAtStart() + this.navBarWindowIsVisibleAtEnd() +} + +/** + * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the + * start of the WM trace + */ +fun FlickerTest.navBarWindowIsVisibleAtStart() { + assertWmStart { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) } +} + +/** + * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the end + * of the WM trace + */ +fun FlickerTest.navBarWindowIsVisibleAtEnd() { + assertWmEnd { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) } +} + +/** + * Checks that [ComponentNameMatcher.TASK_BAR] window is visible and above the app windows in all WM + * trace entries + */ +fun FlickerTest.taskBarWindowIsAlwaysVisible() { + assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR) } } /** - * If [allStates] is true, checks if the stack space of all displays is fully covered - * by any visible layer, during the whole transitions + * Checks that [ComponentNameMatcher.TASK_BAR] window is visible and above the app windows in all WM + * trace entries + */ +fun FlickerTest.taskBarWindowIsVisibleAtEnd() { + assertWmEnd { this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR) } +} + +/** + * If [allStates] is true, checks if the stack space of all displays is fully covered by any visible + * layer, during the whole transitions * - * Otherwise, checks if the stack space of all displays is fully covered - * by any visible layer, at the start and end of the transition + * Otherwise, checks if the stack space of all displays is fully covered by any visible layer, at + * the start and end of the transition * * @param allStates if all states should be checked, othersie, just initial and final */ @JvmOverloads -fun FlickerTestParameter.entireScreenCovered(allStates: Boolean = true) { +fun FlickerTest.entireScreenCovered(allStates: Boolean = true) { if (allStates) { assertLayers { this.invoke("entireScreenCovered") { entry -> - entry.entry.displays.forEach { display -> + entry.entry.displays.filter { it.isOn }.forEach { display -> entry.visibleRegion().coversAtLeast(display.layerStackSpace) } } } } else { assertLayersStart { - this.entry.displays.forEach { display -> + this.entry.displays.filter { it.isOn }.forEach { display -> this.visibleRegion().coversAtLeast(display.layerStackSpace) } } assertLayersEnd { - this.entry.displays.forEach { display -> + this.entry.displays.filter { it.isOn }.forEach { display -> this.visibleRegion().coversAtLeast(display.layerStackSpace) } } } } +/** Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the start of the SF trace */ +fun FlickerTest.navBarLayerIsVisibleAtStart() { + assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) } +} + +/** Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the end of the SF trace */ +fun FlickerTest.navBarLayerIsVisibleAtEnd() { + assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) } +} + /** - * Checks that [FlickerComponentName.NAV_BAR] layer is visible at the start and end of the SF - * trace + * Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the start and end of the SF trace */ -fun FlickerTestParameter.navBarLayerIsVisible() { - assertLayersStart { - this.isVisible(FlickerComponentName.NAV_BAR) - } - assertLayersEnd { - this.isVisible(FlickerComponentName.NAV_BAR) - } +fun FlickerTest.navBarLayerIsVisibleAtStartAndEnd() { + this.navBarLayerIsVisibleAtStart() + this.navBarLayerIsVisibleAtEnd() } /** - * Checks that [FlickerComponentName.STATUS_BAR] layer is visible at the start and end of the SF + * Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the start and end of the SF trace + */ +fun FlickerTest.taskBarLayerIsVisibleAtStartAndEnd() { + this.taskBarLayerIsVisibleAtStart() + this.taskBarLayerIsVisibleAtEnd() +} + +/** Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the start of the SF trace */ +fun FlickerTest.taskBarLayerIsVisibleAtStart() { + assertLayersStart { this.isVisible(ComponentNameMatcher.TASK_BAR) } +} + +/** Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the end of the SF trace */ +fun FlickerTest.taskBarLayerIsVisibleAtEnd() { + assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) } +} + +/** + * Checks that [ComponentNameMatcher.STATUS_BAR] layer is visible at the start and end of the SF * trace */ -fun FlickerTestParameter.statusBarLayerIsVisible() { - assertLayersStart { - this.isVisible(FlickerComponentName.STATUS_BAR) - } - assertLayersEnd { - this.isVisible(FlickerComponentName.STATUS_BAR) - } +fun FlickerTest.statusBarLayerIsVisibleAtStartAndEnd() { + assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) } + assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) } } /** - * Asserts that the [FlickerComponentName.NAV_BAR] layer is at the correct position at the start - * of the SF trace + * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the start of + * the SF trace */ -fun FlickerTestParameter.navBarLayerPositionStart() { +fun FlickerTest.navBarLayerPositionAtStart() { assertLayersStart { - val display = this.entry.displays.minByOrNull { it.id } - ?: throw RuntimeException("There is no display!") - this.visibleRegion(FlickerComponentName.NAV_BAR) - .coversExactly(WindowUtils.getNavigationBarPosition(display)) + val display = + this.entry.displays.firstOrNull { !it.isVirtual } ?: error("There is no display!") + this.visibleRegion(ComponentNameMatcher.NAV_BAR) + .coversExactly( + WindowUtils.getNavigationBarPosition(display, scenario.isGesturalNavigation) + ) } } /** - * Asserts that the [FlickerComponentName.NAV_BAR] layer is at the correct position at the end - * of the SF trace + * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the end of + * the SF trace */ -fun FlickerTestParameter.navBarLayerPositionEnd() { +fun FlickerTest.navBarLayerPositionAtEnd() { assertLayersEnd { - val display = this.entry.displays.minByOrNull { it.id } + val display = + this.entry.displays.minByOrNull { it.id } ?: throw RuntimeException("There is no display!") - this.visibleRegion(FlickerComponentName.NAV_BAR) - .coversExactly(WindowUtils.getNavigationBarPosition(display)) + this.visibleRegion(ComponentNameMatcher.NAV_BAR) + .coversExactly( + WindowUtils.getNavigationBarPosition(display, scenario.isGesturalNavigation) + ) } } /** - * Asserts that the [FlickerComponentName.NAV_BAR] layer is at the correct position at the start - * and end of the SF trace + * Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the start and + * end of the SF trace */ -fun FlickerTestParameter.navBarLayerRotatesAndScales() { - navBarLayerPositionStart() - navBarLayerPositionEnd() +fun FlickerTest.navBarLayerPositionAtStartAndEnd() { + navBarLayerPositionAtStart() + navBarLayerPositionAtEnd() } /** - * Asserts that the [FlickerComponentName.STATUS_BAR] layer is at the correct position at the start + * Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the start * of the SF trace */ -fun FlickerTestParameter.statusBarLayerPositionStart() { +fun FlickerTest.statusBarLayerPositionAtStart( + wmTrace: WindowManagerTrace? = this.reader.readWmTrace() +) { + // collect navbar position for the equivalent WM state + val state = wmTrace?.entries?.firstOrNull() ?: error("WM state missing in $this") + val display = state.getDisplay(PlatformConsts.DEFAULT_DISPLAY) ?: error("Display not found") + val navBarPosition = WindowUtils.getExpectedStatusBarPosition(display) assertLayersStart { - val display = this.entry.displays.minByOrNull { it.id } - ?: throw RuntimeException("There is no display!") - this.visibleRegion(FlickerComponentName.STATUS_BAR) - .coversExactly(WindowUtils.getStatusBarPosition(display)) + this.visibleRegion(ComponentNameMatcher.STATUS_BAR).coversExactly(navBarPosition) } } /** - * Asserts that the [FlickerComponentName.STATUS_BAR] layer is at the correct position at the end - * of the SF trace + * Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the end of + * the SF trace */ -fun FlickerTestParameter.statusBarLayerPositionEnd() { +fun FlickerTest.statusBarLayerPositionAtEnd( + wmTrace: WindowManagerTrace? = this.reader.readWmTrace() +) { + // collect navbar position for the equivalent WM state + val state = wmTrace?.entries?.lastOrNull() ?: error("WM state missing in $this") + val display = state.getDisplay(PlatformConsts.DEFAULT_DISPLAY) ?: error("Display not found") + val navBarPosition = WindowUtils.getExpectedStatusBarPosition(display) assertLayersEnd { - val display = this.entry.displays.minByOrNull { it.id } - ?: throw RuntimeException("There is no display!") - this.visibleRegion(FlickerComponentName.STATUS_BAR) - .coversExactly(WindowUtils.getStatusBarPosition(display)) + this.visibleRegion(ComponentNameMatcher.STATUS_BAR).coversExactly(navBarPosition) } } /** - * Asserts that the [FlickerComponentName.STATUS_BAR] layer is at the correct position at the start + * Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the start * and end of the SF trace */ -fun FlickerTestParameter.statusBarLayerRotatesScales() { - statusBarLayerPositionStart() - statusBarLayerPositionEnd() +fun FlickerTest.statusBarLayerPositionAtStartAndEnd() { + statusBarLayerPositionAtStart() + statusBarLayerPositionAtEnd() +} + +/** + * Asserts that the visibleRegion of the [ComponentNameMatcher.SNAPSHOT] layer can cover the + * visibleRegion of the given app component exactly + */ +fun FlickerTest.snapshotStartingWindowLayerCoversExactlyOnApp(component: IComponentNameMatcher) { + assertLayers { + invoke("snapshotStartingWindowLayerCoversExactlyOnApp") { + val snapshotLayers = + it.subjects.filter { subject -> + subject.name.contains(ComponentNameMatcher.SNAPSHOT.toLayerName()) && + subject.isVisible + } + // Verify the size of snapshotRegion covers appVisibleRegion exactly in animation. + if (snapshotLayers.isNotEmpty()) { + val visibleAreas = + snapshotLayers + .mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion } + .toTypedArray() + val snapshotRegion = RegionSubject(visibleAreas, timestamp) + val appVisibleRegion = it.visibleRegion(component) + if (snapshotRegion.region.isNotEmpty) { + snapshotRegion.coversExactly(appVisibleRegion.region) + } + } + } + } } /** * Asserts that: + * ``` * [originalLayer] is visible at the start of the trace * [originalLayer] becomes invisible during the trace and (in the same entry) [newLayer] * becomes visible * [newLayer] remains visible until the end of the trace * - * @param originalLayer Layer that should be visible at the start + * @param originalLayer + * ``` + * + * Layer that should be visible at the start + * * @param newLayer Layer that should be visible at the end * @param ignoreEntriesWithRotationLayer If entries with a visible rotation layer should be ignored + * + * ``` * when checking the transition. If true we will not fail the assertion if a rotation layer is * visible to fill the gap between the [originalLayer] being visible and the [newLayer] being * visible. - * @param ignoreSnapshot If the snapshot layer should be ignored during the transition + * @param ignoreSnapshot + * ``` + * + * If the snapshot layer should be ignored during the transition + * + * ``` * (useful mostly for app launch) - * @param ignoreSplashscreen If the splashscreen layer should be ignored during the transition. + * @param ignoreSplashscreen + * ``` + * + * If the splashscreen layer should be ignored during the transition. + * + * ``` * If true then we will allow for a splashscreen to be shown before the layer is shown, * otherwise we won't and the layer must appear immediately. + * ``` */ -fun FlickerTestParameter.replacesLayer( - originalLayer: FlickerComponentName, - newLayer: FlickerComponentName, +fun FlickerTest.replacesLayer( + originalLayer: IComponentNameMatcher, + newLayer: IComponentNameMatcher, ignoreEntriesWithRotationLayer: Boolean = false, ignoreSnapshot: Boolean = false, ignoreSplashscreen: Boolean = true @@ -202,10 +318,10 @@ fun FlickerTestParameter.replacesLayer( val assertion = this.isVisible(originalLayer) if (ignoreEntriesWithRotationLayer) { - assertion.then().isVisible(FlickerComponentName.ROTATION, isOptional = true) + assertion.then().isVisible(ComponentNameMatcher.ROTATION, isOptional = true) } if (ignoreSnapshot) { - assertion.then().isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + assertion.then().isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) } if (ignoreSplashscreen) { assertion.then().isSplashScreenVisibleFor(newLayer, isOptional = true) @@ -214,13 +330,7 @@ fun FlickerTestParameter.replacesLayer( assertion.then().isVisible(newLayer) } - assertLayersStart { - this.isVisible(originalLayer) - .isInvisible(newLayer) - } + assertLayersStart { this.isVisible(originalLayer).isInvisible(newLayer) } - assertLayersEnd { - this.isInvisible(originalLayer) - .isVisible(newLayer) - } + assertLayersEnd { this.isInvisible(originalLayer).isVisible(newLayer) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt new file mode 100644 index 000000000000..7ef4d939fee2 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt @@ -0,0 +1,32 @@ +/* + * 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.android.server.wm.flicker.activityembedding + +import android.tools.device.flicker.legacy.FlickerTest +import com.android.server.wm.flicker.BaseTest +import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper +import org.junit.Before + +abstract class ActivityEmbeddingTestBase(flicker: FlickerTest) : BaseTest(flicker) { + val testApp = ActivityEmbeddingAppHelper(instrumentation) + + @Before + fun assumeActivityEmbeddingSupported() { + // The test should only be run on devices that support ActivityEmbedding. + ActivityEmbeddingAppHelper.assumeActivityEmbeddingSupportedDevice() + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt new file mode 100644 index 000000000000..ed17059e79e7 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt @@ -0,0 +1,129 @@ +/* + * 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.android.server.wm.flicker.activityembedding + +import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test opening an activity that will launch another activity as ActivityEmbedding placeholder in + * split. + * + * To run this test: `atest FlickerTests:OpenActivityEmbeddingPlaceholderSplitTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenActivityEmbeddingPlaceholderSplitTest(flicker: FlickerTest) : + ActivityEmbeddingTestBase(flicker) { + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + testApp.launchViaIntent(wmHelper) + } + transitions { testApp.launchPlaceholderSplit(wmHelper) } + teardown { + tapl.goHome() + testApp.exit(wmHelper) + } + } + + /** Main activity should become invisible after launching the placeholder primary activity. */ + @Presubmit + @Test + fun mainActivityWindowBecomesInvisible() { + flicker.assertWm { + isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + .then() + .isAppWindowInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + } + } + + /** Main activity should become invisible after launching the placeholder primary activity. */ + @Presubmit + @Test + fun mainActivityLayerBecomesInvisible() { + flicker.assertLayers { + isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + .then() + .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + } + } + + /** + * Placeholder primary and secondary should become visible after launch. The windows are not + * necessarily to become visible at the same time. + */ + @Presubmit + @Test + fun placeholderSplitWindowsBecomeVisible() { + flicker.assertWm { + notContains(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT) + .then() + .isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT) + .then() + .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT) + } + flicker.assertWm { + notContains(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT) + .then() + .isAppWindowInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT) + .then() + .isAppWindowVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT) + } + } + + /** Placeholder primary and secondary should become visible together after launch. */ + @Presubmit + @Test + fun placeholderSplitLayersBecomeVisible() { + flicker.assertLayers { + isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT) + isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT) + .then() + .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT) + .isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT) + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt new file mode 100644 index 000000000000..863828881d36 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.activityembedding + +import android.platform.test.annotations.Presubmit +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test opening a secondary activity that will split with the main activity. + * + * To run this test: `atest FlickerTests:OpenActivityEmbeddingSecondaryToSplitTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenActivityEmbeddingSecondaryToSplitTest(flicker: FlickerTest) : + ActivityEmbeddingTestBase(flicker) { + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + testApp.launchViaIntent(wmHelper) + } + transitions { testApp.launchSecondaryActivity(wmHelper) } + teardown { + tapl.goHome() + testApp.exit(wmHelper) + } + } + + /** Main activity should remain visible when enter split from fullscreen. */ + @Presubmit + @Test + fun mainActivityWindowIsAlwaysVisible() { + flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) } + } + + /** + * Main activity surface is animated from fullscreen to ActivityEmbedding split. During the + * transition, there is a period of time that it is covered by a snapshot of itself. + */ + @Presubmit + @Test + fun mainActivityLayerIsAlwaysVisible() { + flicker.assertLayers { + isVisible( + ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT.or( + ComponentNameMatcher.TRANSITION_SNAPSHOT + ) + ) + } + flicker.assertLayersEnd { + isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + .isInvisible(ComponentNameMatcher.TRANSITION_SNAPSHOT) + } + } + + /** Secondary activity should become visible after launching into split. */ + @Presubmit + @Test + fun secondaryActivityWindowBecomesVisible() { + flicker.assertWm { + notContains(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .then() + .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .then() + .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + } + } + + /** Secondary activity should become visible after launching into split. */ + @Presubmit + @Test + fun secondaryActivityLayerBecomesVisible() { + flicker.assertLayers { + isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .then() + .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt index 20a2e2228d7a..10b71ff1efd7 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt @@ -1,4 +1,3 @@ - /* * Copyright (C) 2020 The Android Open Source Project * @@ -17,16 +16,13 @@ package com.android.server.wm.flicker.close -import androidx.test.filters.FlakyTest +import android.platform.test.annotations.FlakyTest +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import org.junit.Assume.assumeFalse -import org.junit.Assume.assumeTrue import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -39,83 +35,74 @@ import org.junit.runners.Parameterized * To run this test: `atest FlickerTests:CloseAppBackButtonTest` * * Actions: + * ``` * Make sure no apps are running on the device * Launch an app [testApp] and wait animation to complete * Press back button + * ``` * * To run only the presubmit assertions add: `-- + * + * ``` * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` + * ``` * * To run only the postsubmit assertions add: `-- + * + * ``` * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` + * ``` * * To run only the flaky assertions add: `-- + * + * ``` * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` + * ``` * * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [CloseAppTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice +@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group4 -class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) { +open class CloseAppBackButtonTest(flicker: FlickerTest) : CloseAppTransition(flicker) { + /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) transitions { - device.pressBack() - wmHelper.waitForHomeActivityVisible() + tapl.pressBack() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() } } /** {@inheritDoc} */ - @FlakyTest - @Test - override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - - /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() { - // This test doesn't work in shell transitions because of b/206753786 - assumeFalse(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } - - @FlakyTest(bugId = 214452854) - @Test - fun statusBarLayerRotatesScales_shellTransit() { - assumeTrue(isShellTransitionsEnabled) - super.statusBarLayerRotatesScales() - } - - /** {@inheritDoc} */ - @FlakyTest(bugId = 229762973) - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 3) + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests() } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt new file mode 100644 index 000000000000..9fa840190fbf --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.close + +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@FlickerServiceCompatible +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class CloseAppBackButtonTestCfArm(flicker: FlickerTest) : CloseAppBackButtonTest(flicker) { + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt index d00fd7d7f09b..e5bd350019c8 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt @@ -16,14 +16,13 @@ package com.android.server.wm.flicker.close -import android.platform.test.annotations.Presubmit -import androidx.test.filters.FlakyTest +import android.platform.test.annotations.FlakyTest +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -36,88 +35,74 @@ import org.junit.runners.Parameterized * To run this test: `atest FlickerTests:CloseAppHomeButtonTest` * * Actions: + * ``` * Make sure no apps are running on the device * Launch an app [testApp] and wait animation to complete * Press home button + * ``` * * To run only the presubmit assertions add: `-- + * + * ``` * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` + * ``` * * To run only the postsubmit assertions add: `-- + * + * ``` * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` + * ``` * * To run only the flaky assertions add: `-- + * + * ``` * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` + * ``` * * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [CloseAppTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice +@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group4 -class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) { +open class CloseAppHomeButtonTest(flicker: FlickerTest) : CloseAppTransition(flicker) { + /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) + setup { tapl.setExpectedRotationCheckEnabled(false) } transitions { - device.pressHome() - wmHelper.waitForHomeActivityVisible() + // Can't use TAPL at the moment because of rotation test issues + // When pressing home, TAPL expects the orientation to remain constant + // However, when closing a landscape app back to a portrait-only launcher + // this causes an error in verifyActiveContainer(); + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() } } /** {@inheritDoc} */ - @FlakyTest - @Test - override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - - /** {@inheritDoc} */ - @FlakyTest(bugId = 227430489) - @Test - override fun statusBarLayerRotatesScales() { - super.statusBarLayerRotatesScales() - } - - /** {@inheritDoc} */ - @Presubmit - @Test - override fun launcherLayerReplacesApp() { - super.launcherLayerReplacesApp() - } - - /** {@inheritDoc} */ - @Presubmit - @Test - override fun entireScreenCovered() { - super.entireScreenCovered() - } - - /** {@inheritDoc} */ - @FlakyTest(bugId = 229762973) + @FlakyTest(bugId = 206753786) @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() companion object { - /** - * Creates the test configurations. - * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. - */ + /** Creates the test configurations. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 3) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt new file mode 100644 index 000000000000..136995a78fd7 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.close + +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@FlickerServiceCompatible +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class CloseAppHomeButtonTestCfArm(flicker: FlickerTest) : CloseAppHomeButtonTest(flicker) { + companion object { + /** Creates the test configurations. */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt index aaa2db768792..4570fa23dc43 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt @@ -16,176 +16,59 @@ package com.android.server.wm.flicker.close -import android.app.Instrumentation import android.platform.test.annotations.Presubmit -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.LAUNCHER_COMPONENT -import com.android.server.wm.flicker.dsl.FlickerBuilder +import android.tools.common.traces.component.ComponentNameMatcher.Companion.LAUNCHER +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.StandardAppHelper import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.flicker.replacesLayer import org.junit.Test -/** - * Base test class for transitions that close an app back to the launcher screen - */ -abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) { - protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() +/** Base test class for transitions that close an app back to the launcher screen */ +abstract class CloseAppTransition(flicker: FlickerTest) : BaseTest(flicker) { protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation) - /** - * Specification of the test transition to execute - */ - protected open val transition: FlickerBuilder.() -> Unit = { + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { setup { - eachRun { - testApp.launchViaIntent(wmHelper) - this.setRotation(testSpec.startRotation) - } - } - teardown { - test { - testApp.exit(wmHelper) - } - } - } - - /** - * Entry point for the test runner. It will use this method to initialize and cache - * flicker executions - */ - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - transition() - } - } - - /** - * Checks that the navigation bar window is visible during the whole transition - */ - @Presubmit - @Test - open fun navBarWindowIsVisible() { - testSpec.navBarWindowIsVisible() - } - - /** - * Checks that the status bar window is visible during the whole transition - */ - @Presubmit - @Test - open fun statusBarWindowIsVisible() { - testSpec.statusBarWindowIsVisible() - } - - /** - * Checks that the navigation bar layer is visible during the whole transition - */ - @Presubmit - @Test - open fun navBarLayerIsVisible() { - testSpec.navBarLayerIsVisible() - } - - /** - * Checks that the status bar layer is visible during the whole transition - */ - @Presubmit - @Test - open fun statusBarLayerIsVisible() { - testSpec.statusBarLayerIsVisible() - } - - /** - * Checks the position of the navigation bar at the start and end of the transition - */ - @Presubmit - @Test - open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - /** - * Checks the position of the status bar at the start and end of the transition - */ - @Presubmit - @Test - open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - - /** - * Checks that all windows that are visible on the trace, are visible for at least 2 - * consecutive entries. - */ - @Presubmit - @Test - open fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry() + tapl.setExpectedRotation(flicker.scenario.startRotation.value) + testApp.launchViaIntent(wmHelper) + this.setRotation(flicker.scenario.startRotation) } + teardown { testApp.exit(wmHelper) } } /** - * Checks that all layers that are visible on the trace, are visible for at least 2 - * consecutive entries. - */ - @Presubmit - @Test - open fun visibleLayersShownMoreThanOneConsecutiveEntry() { - testSpec.assertLayers { - this.visibleLayersShownMoreThanOneConsecutiveEntry() - } - } - - /** - * Checks that all parts of the screen are covered during the transition - */ - @Presubmit - @Test - open fun entireScreenCovered() = testSpec.entireScreenCovered() - - /** - * Checks that [testApp] is the top visible app window at the start of the transition and - * that it is replaced by [LAUNCHER_COMPONENT] during the transition + * Checks that [testApp] is the top visible app window at the start of the transition and that + * it is replaced by [LAUNCHER] during the transition */ @Presubmit @Test open fun launcherReplacesAppWindowAsTopWindow() { - testSpec.assertWm { - this.isAppWindowOnTop(testApp.component) - .then() - .isAppWindowOnTop(LAUNCHER_COMPONENT) - } + flicker.assertWm { this.isAppWindowOnTop(testApp).then().isAppWindowOnTop(LAUNCHER) } } /** - * Checks that [LAUNCHER_COMPONENT] is invisible at the start of the transition and that - * it becomes visible during the transition + * Checks that [LAUNCHER] is invisible at the start of the transition and that it becomes + * visible during the transition */ @Presubmit @Test open fun launcherWindowBecomesVisible() { - testSpec.assertWm { - this.isAppWindowNotOnTop(LAUNCHER_COMPONENT) - .then() - .isAppWindowOnTop(LAUNCHER_COMPONENT) - } + flicker.assertWm { this.isAppWindowNotOnTop(LAUNCHER).then().isAppWindowOnTop(LAUNCHER) } } - /** - * Checks that [LAUNCHER_COMPONENT] layer becomes visible when [testApp] becomes invisible - */ + /** Checks that [LAUNCHER] layer becomes visible when [testApp] becomes invisible */ @Presubmit @Test open fun launcherLayerReplacesApp() { - testSpec.replacesLayer(testApp.component, LAUNCHER_COMPONENT) + flicker.replacesLayer( + testApp, + LAUNCHER, + ignoreEntriesWithRotationLayer = flicker.scenario.isLandscapeOrSeascapeAtStart + ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt new file mode 100644 index 000000000000..daecfe7e4c4d --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt @@ -0,0 +1,120 @@ +/* + * 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.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.tools.common.PlatformConsts +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent +import android.util.Log +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import androidx.window.extensions.WindowExtensions +import androidx.window.extensions.WindowExtensionsProvider +import androidx.window.extensions.embedding.ActivityEmbeddingComponent +import com.android.server.wm.flicker.testapp.ActivityOptions +import org.junit.Assume.assumeNotNull + +class ActivityEmbeddingAppHelper +@JvmOverloads +constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.ActivityEmbedding.MainActivity.LABEL, + component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT +) : StandardAppHelper(instr, launcherName, component) { + + /** + * Clicks the button to launch the secondary activity, which should split with the main activity + * based on the split pair rule. + */ + fun launchSecondaryActivity(wmHelper: WindowManagerStateHelper) { + val launchButton = + uiDevice.wait( + Until.findObject(By.res(getPackage(), "launch_secondary_activity_button")), + FIND_TIMEOUT + ) + require(launchButton != null) { "Can't find launch secondary activity button on screen." } + launchButton.click() + wmHelper + .StateSyncBuilder() + .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED) + .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED) + .waitForAndVerify() + } + + /** + * Clicks the button to launch the placeholder primary activity, which should launch the + * placeholder secondary activity based on the placeholder rule. + */ + fun launchPlaceholderSplit(wmHelper: WindowManagerStateHelper) { + val launchButton = + uiDevice.wait( + Until.findObject(By.res(getPackage(), "launch_placeholder_split_button")), + FIND_TIMEOUT + ) + require(launchButton != null) { "Can't find launch placeholder split button on screen." } + launchButton.click() + wmHelper + .StateSyncBuilder() + .withActivityState(PLACEHOLDER_PRIMARY_COMPONENT, PlatformConsts.STATE_RESUMED) + .withActivityState(PLACEHOLDER_SECONDARY_COMPONENT, PlatformConsts.STATE_RESUMED) + .waitForAndVerify() + } + + companion object { + private const val TAG = "ActivityEmbeddingAppHelper" + + val MAIN_ACTIVITY_COMPONENT = + ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT.toFlickerComponent() + + val SECONDARY_ACTIVITY_COMPONENT = + ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent() + + val PLACEHOLDER_PRIMARY_COMPONENT = + ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT + .toFlickerComponent() + + val PLACEHOLDER_SECONDARY_COMPONENT = + ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT + .toFlickerComponent() + + @JvmStatic + fun getWindowExtensions(): WindowExtensions? { + try { + return WindowExtensionsProvider.getWindowExtensions() + } catch (e: NoClassDefFoundError) { + Log.d(TAG, "Extension implementation not found") + } catch (e: UnsupportedOperationException) { + Log.d(TAG, "Stub Extension") + } + return null + } + + @JvmStatic + fun getActivityEmbeddingComponent(): ActivityEmbeddingComponent? { + return getWindowExtensions()?.activityEmbeddingComponent + } + + @JvmStatic + fun assumeActivityEmbeddingSupportedDevice() { + assumeNotNull(getActivityEmbeddingComponent()) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt new file mode 100644 index 000000000000..94ac1a6e1e02 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 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.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper + +class AppPairsHelper( + instrumentation: Instrumentation, + activityLabel: String, + component: ComponentNameMatcher +) : StandardAppHelper(instrumentation, activityLabel, component) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt new file mode 100644 index 000000000000..fde098199042 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt @@ -0,0 +1,90 @@ +/* + * 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.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.content.ComponentName +import android.provider.Settings +import android.tools.device.helpers.FIND_TIMEOUT +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions +import org.junit.Assert.assertNotNull + +class AssistantAppHelper +@JvmOverloads +constructor( + val instr: Instrumentation, + val component: ComponentName = ActivityOptions.ASSISTANT_SERVICE_COMPONENT_NAME, +) { + private val uiDevice: UiDevice = UiDevice.getInstance(instr) + private val defaultAssistant: String? = + Settings.Secure.getString(instr.targetContext.contentResolver, Settings.Secure.ASSISTANT) + private val defaultVoiceInteractionService: String? = + Settings.Secure.getString( + instr.targetContext.contentResolver, + Settings.Secure.VOICE_INTERACTION_SERVICE + ) + + fun setDefaultAssistant() { + Settings.Secure.putString( + instr.targetContext.contentResolver, + Settings.Secure.VOICE_INTERACTION_SERVICE, + component.flattenToString() + ) + Settings.Secure.putString( + instr.targetContext.contentResolver, + Settings.Secure.ASSISTANT, + component.flattenToString() + ) + } + + fun resetDefaultAssistant() { + Settings.Secure.putString( + instr.targetContext.contentResolver, + Settings.Secure.VOICE_INTERACTION_SERVICE, + defaultVoiceInteractionService + ) + Settings.Secure.putString( + instr.targetContext.contentResolver, + Settings.Secure.ASSISTANT, + defaultAssistant + ) + } + + /** + * Open Assistance UI. + * + * @param longpress open the UI by long pressing power button. Otherwise open the UI through + * vioceinteraction shell command directly. + */ + @JvmOverloads + fun openUI(longpress: Boolean = false) { + if (longpress) { + uiDevice.executeShellCommand("input keyevent --longpress KEYCODE_POWER") + } else { + uiDevice.executeShellCommand("cmd voiceinteraction show") + } + val ui = + uiDevice.wait( + Until.findObject(By.res(ActivityOptions.FLICKER_APP_PACKAGE, "vis_frame")), + FIND_TIMEOUT + ) + assertNotNull("Can't find Assistant UI after long pressing power button.", ui) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt index 2dbf304a0f23..c6fa1bb89220 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt @@ -17,18 +17,16 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.support.test.launcherhelper.ILauncherStrategy -import android.support.test.launcherhelper.LauncherStrategyFactory +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.toFlickerComponent import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.parser.toFlickerComponent - class FixedOrientationAppHelper @JvmOverloads constructor( - instr: Instrumentation, - launcherName: String = ActivityOptions.PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME, - component: FlickerComponentName = - ActivityOptions.PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy - ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
\ No newline at end of file +class FixedOrientationAppHelper +@JvmOverloads +constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.PortraitOnlyActivity.LABEL, + component: ComponentNameMatcher = + ActivityOptions.PortraitOnlyActivity.COMPONENT.toFlickerComponent() +) : StandardAppHelper(instr, launcherName, component) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt index 75900df978df..9227e07f5b11 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt @@ -15,15 +15,22 @@ */ @file:JvmName("FlickerExtensions") + package com.android.server.wm.flicker.helpers -import com.android.server.wm.flicker.Flicker -import com.android.server.wm.flicker.rules.ChangeDisplayOrientationRule +import android.tools.common.Rotation +import android.tools.device.flicker.legacy.IFlickerTestData +import android.tools.device.flicker.rules.ChangeDisplayOrientationRule /** * Changes the device [rotation] and wait for the rotation animation to complete * * @param rotation New device rotation */ -fun Flicker.setRotation(rotation: Int) = - ChangeDisplayOrientationRule.setRotation(rotation, instrumentation, wmHelper) +fun IFlickerTestData.setRotation(rotation: Rotation) = + ChangeDisplayOrientationRule.setRotation( + rotation, + instrumentation, + clearCacheAfterParsing = false, + wmHelper = wmHelper + ) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt new file mode 100644 index 000000000000..747cf3742bf7 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2021 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.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Direction +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions + +class GameAppHelper +@JvmOverloads +constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.Game.LABEL, + component: ComponentNameMatcher = ActivityOptions.Game.COMPONENT.toFlickerComponent() +) : StandardAppHelper(instr, launcherName, component) { + + /** + * Swipes down in the mock game app. + * + * @return true if the swipe operation is successful. + */ + fun swipeDown(): Boolean { + val gameView = + uiDevice.wait(Until.findObject(By.res(getPackage(), GAME_APP_VIEW_RES)), WAIT_TIME_MS) + require(gameView != null) { "Mock game app view not found." } + + val bound = gameView.getVisibleBounds() + return uiDevice.swipe( + bound.centerX(), + bound.top, + bound.centerX(), + bound.centerY(), + SWIPE_STEPS + ) + } + + /** + * Switches to a recent app by quick switch gesture. This function can be used in both portrait + * and landscape mode. + * + * @param wmHelper Helper used to get window region. + * @param direction UiAutomator Direction enum to indicate the swipe direction. + * @return true if the swipe operation is successful. + */ + fun switchToPreviousAppByQuickSwitchGesture( + wmHelper: WindowManagerStateHelper, + direction: Direction + ): Boolean { + val ratioForScreenBottom = 0.99 + val fullView = wmHelper.getWindowRegion(componentMatcher) + require(!fullView.isEmpty) { "Target $componentMatcher view not found." } + + val bound = fullView.bounds + val targetYPos = bound.bottom * ratioForScreenBottom + val endX = + when (direction) { + Direction.LEFT -> bound.left + Direction.RIGHT -> bound.right + else -> { + throw IllegalStateException("Only left or right direction is allowed.") + } + } + return uiDevice.swipe( + bound.centerX(), + targetYPos.toInt(), + endX, + targetYPos.toInt(), + SWIPE_STEPS + ) + } + + /** + * Waits for view idel with timeout, then checkes the target object whether visible on screen. + * + * @param packageName The targe application's package name. + * @param identifier The resource id of the target object. + * @param timeout The timeout duration in milliseconds. + * @return true if the target object exists. + */ + @JvmOverloads + fun isTargetObjVisible( + packageName: String, + identifier: String, + timeout: Long = WAIT_TIME_MS + ): Boolean { + uiDevice.waitForIdle(timeout) + return uiDevice.hasObject(By.res(packageName, identifier)) + } + + companion object { + private const val GAME_APP_VIEW_RES = "container" + private const val WAIT_TIME_MS = 3_000L + private const val SWIPE_STEPS = 30 + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java new file mode 100644 index 000000000000..a8f1b3de564e --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java @@ -0,0 +1,279 @@ +/* + * 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.android.server.wm.flicker.helpers; + +import android.annotation.NonNull; +import android.app.Instrumentation; +import android.app.UiAutomation; +import android.os.SystemClock; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.MotionEvent; +import android.view.MotionEvent.PointerCoords; +import android.view.MotionEvent.PointerProperties; + +import androidx.annotation.Nullable; + +/** + * Injects gestures given an {@link Instrumentation} object. + */ +public class GestureHelper { + // Inserted after each motion event injection. + private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5; + + private final UiAutomation mUiAutomation; + + /** + * Primary pointer should be cached here for separate release + */ + @Nullable private PointerProperties mPrimaryPtrProp; + @Nullable private PointerCoords mPrimaryPtrCoord; + private long mPrimaryPtrDownTime; + + /** + * A pair of floating point values. + */ + public static class Tuple { + public float x; + public float y; + + public Tuple(float x, float y) { + this.x = x; + this.y = y; + } + } + + public GestureHelper(Instrumentation instrumentation) { + mUiAutomation = instrumentation.getUiAutomation(); + } + + /** + * Injects a series of {@link MotionEvent}s to simulate a drag gesture without pointer release. + * + * Simulates a drag gesture without releasing the primary pointer. The primary pointer info + * will be cached for potential release later on by {@code releasePrimaryPointer()} + * + * @param startPoint initial coordinates of the primary pointer + * @param endPoint final coordinates of the primary pointer + * @param steps number of steps to take to animate dragging + * @return true if gesture is injected successfully + */ + public boolean dragWithoutRelease(@NonNull Tuple startPoint, + @NonNull Tuple endPoint, int steps) { + PointerProperties ptrProp = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER); + PointerCoords ptrCoord = getPointerCoord(startPoint.x, startPoint.y, 1, 1); + + PointerProperties[] ptrProps = new PointerProperties[] { ptrProp }; + PointerCoords[] ptrCoords = new PointerCoords[] { ptrCoord }; + + long downTime = SystemClock.uptimeMillis(); + + if (!primaryPointerDown(ptrProp, ptrCoord, downTime)) { + return false; + } + + // cache the primary pointer info for later potential release + mPrimaryPtrProp = ptrProp; + mPrimaryPtrCoord = ptrCoord; + mPrimaryPtrDownTime = downTime; + + return movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint }, downTime, steps); + } + + /** + * Release primary pointer if previous gesture has cached the primary pointer info. + * + * @return true if the release was injected successfully + */ + public boolean releasePrimaryPointer() { + if (mPrimaryPtrProp != null && mPrimaryPtrCoord != null) { + return primaryPointerUp(mPrimaryPtrProp, mPrimaryPtrCoord, mPrimaryPtrDownTime); + } + + return false; + } + + /** + * Injects a series of {@link MotionEvent} objects to simulate a pinch gesture. + * + * @param startPoint1 initial coordinates of the first pointer + * @param startPoint2 initial coordinates of the second pointer + * @param endPoint1 final coordinates of the first pointer + * @param endPoint2 final coordinates of the second pointer + * @param steps number of steps to take to animate pinching + * @return true if gesture is injected successfully + */ + public boolean pinch(@NonNull Tuple startPoint1, @NonNull Tuple startPoint2, + @NonNull Tuple endPoint1, @NonNull Tuple endPoint2, int steps) { + PointerProperties ptrProp1 = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER); + PointerProperties ptrProp2 = getPointerProp(1, MotionEvent.TOOL_TYPE_FINGER); + + PointerCoords ptrCoord1 = getPointerCoord(startPoint1.x, startPoint1.y, 1, 1); + PointerCoords ptrCoord2 = getPointerCoord(startPoint2.x, startPoint2.y, 1, 1); + + PointerProperties[] ptrProps = new PointerProperties[] { + ptrProp1, ptrProp2 + }; + + PointerCoords[] ptrCoords = new PointerCoords[] { + ptrCoord1, ptrCoord2 + }; + + long downTime = SystemClock.uptimeMillis(); + + if (!primaryPointerDown(ptrProp1, ptrCoord1, downTime)) { + return false; + } + + if (!nonPrimaryPointerDown(ptrProps, ptrCoords, downTime, 1)) { + return false; + } + + if (!movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint1, endPoint2 }, + downTime, steps)) { + return false; + } + + if (!nonPrimaryPointerUp(ptrProps, ptrCoords, downTime, 1)) { + return false; + } + + return primaryPointerUp(ptrProp1, ptrCoord1, downTime); + } + + private boolean primaryPointerDown(@NonNull PointerProperties prop, + @NonNull PointerCoords coord, long downTime) { + MotionEvent event = getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, 1, + new PointerProperties[]{ prop }, new PointerCoords[]{ coord }); + + return injectEventSync(event); + } + + private boolean nonPrimaryPointerDown(@NonNull PointerProperties[] props, + @NonNull PointerCoords[] coords, long downTime, int index) { + // at least 2 pointers are needed + if (props.length != coords.length || coords.length < 2) { + return false; + } + + long eventTime = SystemClock.uptimeMillis(); + + MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_DOWN + + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords); + + return injectEventSync(event); + } + + private boolean movePointers(@NonNull PointerProperties[] props, + @NonNull PointerCoords[] coords, @NonNull Tuple[] endPoints, long downTime, int steps) { + // the number of endpoints should be the same as the number of pointers + if (props.length != coords.length || coords.length != endPoints.length) { + return false; + } + + // prevent division by 0 and negative number of steps + if (steps < 1) { + steps = 1; + } + + // save the starting points before updating any pointers + Tuple[] startPoints = new Tuple[coords.length]; + + for (int i = 0; i < coords.length; i++) { + startPoints[i] = new Tuple(coords[i].x, coords[i].y); + } + + MotionEvent event; + long eventTime; + + for (int i = 0; i < steps; i++) { + // inject a delay between movements + SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS); + + // update the coordinates + for (int j = 0; j < coords.length; j++) { + coords[j].x += (endPoints[j].x - startPoints[j].x) / steps; + coords[j].y += (endPoints[j].y - startPoints[j].y) / steps; + } + + eventTime = SystemClock.uptimeMillis(); + + event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_MOVE, + coords.length, props, coords); + + boolean didInject = injectEventSync(event); + + if (!didInject) { + return false; + } + } + + return true; + } + + private boolean primaryPointerUp(@NonNull PointerProperties prop, + @NonNull PointerCoords coord, long downTime) { + long eventTime = SystemClock.uptimeMillis(); + + MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_UP, 1, + new PointerProperties[]{ prop }, new PointerCoords[]{ coord }); + + return injectEventSync(event); + } + + private boolean nonPrimaryPointerUp(@NonNull PointerProperties[] props, + @NonNull PointerCoords[] coords, long downTime, int index) { + // at least 2 pointers are needed + if (props.length != coords.length || coords.length < 2) { + return false; + } + + long eventTime = SystemClock.uptimeMillis(); + + MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_UP + + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords); + + return injectEventSync(event); + } + + private PointerCoords getPointerCoord(float x, float y, float pressure, float size) { + PointerCoords ptrCoord = new PointerCoords(); + ptrCoord.x = x; + ptrCoord.y = y; + ptrCoord.pressure = pressure; + ptrCoord.size = size; + return ptrCoord; + } + + private PointerProperties getPointerProp(int id, int toolType) { + PointerProperties ptrProp = new PointerProperties(); + ptrProp.id = id; + ptrProp.toolType = toolType; + return ptrProp; + } + + private static MotionEvent getMotionEvent(long downTime, long eventTime, int action, + int pointerCount, PointerProperties[] ptrProps, PointerCoords[] ptrCoords) { + return MotionEvent.obtain(downTime, eventTime, action, pointerCount, + ptrProps, ptrCoords, 0, 0, 1.0f, 1.0f, + 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); + } + + private boolean injectEventSync(InputEvent event) { + return mUiAutomation.injectInputEvent(event, true); + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt deleted file mode 100644 index aacc17a49a24..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2020 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.android.server.wm.flicker.helpers - -import android.view.WindowInsets.Type.ime -import android.view.WindowInsets.Type.navigationBars -import android.view.WindowInsets.Type.statusBars - -import android.app.Instrumentation -import androidx.test.uiautomator.By -import androidx.test.uiautomator.UiDevice -import androidx.test.uiautomator.Until -import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper - -import java.util.regex.Pattern - -class ImeAppAutoFocusHelper @JvmOverloads constructor( - instr: Instrumentation, - private val rotation: Int, - private val imePackageName: String = IME_PACKAGE, - launcherName: String = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_LAUNCHER_NAME, - component: FlickerComponentName = - ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() -) : ImeAppHelper(instr, launcherName, component) { - override fun openIME( - device: UiDevice, - wmHelper: WindowManagerStateHelper? - ) { - // do nothing (the app is focused automatically) - waitIMEShown(device, wmHelper) - } - - override fun launchViaIntent( - wmHelper: WindowManagerStateHelper, - expectedWindowName: String, - action: String?, - stringExtras: Map<String, String> - ) { - super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras) - waitIMEShown(uiDevice, wmHelper) - } - - override fun open() { - val expectedPackage = if (rotation.isRotated()) { - imePackageName - } else { - getPackage() - } - launcherStrategy.launch(appName, expectedPackage) - } - - fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) { - val button = uiDevice.wait(Until.findObject(By.res(getPackage(), - "start_dialog_themed_activity_btn")), FIND_TIMEOUT) - - require(button != null) { - "Button not found, this usually happens when the device " + - "was left in an unknown state (e.g. Screen turned off)" - } - button.click() - wmHelper.waitForAppTransitionIdle() - wmHelper.waitForFullScreenApp( - ActivityOptions.DIALOG_THEMED_ACTIVITY_COMPONENT_NAME.toFlickerComponent()) - mInstrumentation.waitForIdleSync() - } - fun dismissDialog(wmHelper: WindowManagerStateHelper) { - val dialog = uiDevice.wait( - Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT) - - // Pressing back key to dismiss the dialog - if (dialog != null) { - uiDevice.pressBack() - wmHelper.waitForAppTransitionIdle() - } - } - fun getInsetsVisibleFromDialog(type: Int): Boolean { - var insetsVisibilityTextView = uiDevice.wait( - Until.findObject(By.res("android:id/text1")), FIND_TIMEOUT) - if (insetsVisibilityTextView != null) { - var visibility = insetsVisibilityTextView.text.toString() - val matcher = when (type) { - ime() -> { - Pattern.compile("IME\\: (VISIBLE|INVISIBLE)").matcher(visibility) - } - statusBars() -> { - Pattern.compile("StatusBar\\: (VISIBLE|INVISIBLE)").matcher(visibility) - } - navigationBars() -> { - Pattern.compile("NavBar\\: (VISIBLE|INVISIBLE)").matcher(visibility) - } - else -> null - } - if (matcher != null && matcher.find()) { - return matcher.group(1).equals("VISIBLE") - } - } - return false - } -} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt index 5bd365c7eefd..d1722521bba8 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt @@ -17,86 +17,63 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.support.test.launcherhelper.ILauncherStrategy -import android.support.test.launcherhelper.LauncherStrategyFactory +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.uiautomator.By -import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -open class ImeAppHelper @JvmOverloads constructor( +open class ImeAppHelper +@JvmOverloads +constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.IME_ACTIVITY_LAUNCHER_NAME, - component: FlickerComponentName = - ActivityOptions.IME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy -) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { + launcherName: String = ActivityOptions.Ime.Default.LABEL, + component: ComponentNameMatcher = ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent() +) : StandardAppHelper(instr, launcherName, component) { /** * Opens the IME and wait for it to be displayed * - * @param device UIDevice instance to interact with the device * @param wmHelper Helper used to wait for WindowManager states */ - @JvmOverloads - open fun openIME(device: UiDevice, wmHelper: WindowManagerStateHelper? = null) { - val editText = device.wait( - Until.findObject(By.res(getPackage(), "plain_text_input")), - FIND_TIMEOUT) + open fun openIME(wmHelper: WindowManagerStateHelper) { + val editText = + uiDevice.wait(Until.findObject(By.res(getPackage(), "plain_text_input")), FIND_TIMEOUT) - require(editText != null) { + requireNotNull(editText) { "Text field not found, this usually happens when the device " + "was left in an unknown state (e.g. in split screen)" } editText.click() - waitIMEShown(device, wmHelper) + waitIMEShown(wmHelper) } - protected fun waitIMEShown( - device: UiDevice, - wmHelper: WindowManagerStateHelper? = null - ) { - if (wmHelper == null) { - device.waitForIdle() - } else { - wmHelper.waitImeShown() - } + protected fun waitIMEShown(wmHelper: WindowManagerStateHelper) { + wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify() } /** * Opens the IME and wait for it to be gone * - * @param device UIDevice instance to interact with the device * @param wmHelper Helper used to wait for WindowManager states */ - @JvmOverloads - open fun closeIME(device: UiDevice, wmHelper: WindowManagerStateHelper? = null) { - device.pressBack() - // Using only the AccessibilityInfo it is not possible to identify if the IME is active - if (wmHelper == null) { - device.waitForIdle() - } else { - wmHelper.waitImeGone() - } + open fun closeIME(wmHelper: WindowManagerStateHelper) { + uiDevice.pressBack() + wmHelper.StateSyncBuilder().withImeGone().waitForAndVerify() } - @JvmOverloads - open fun finishActivity(device: UiDevice, wmHelper: WindowManagerStateHelper? = null) { - val finishButton = device.wait( + open fun finishActivity(wmHelper: WindowManagerStateHelper) { + val finishButton = + uiDevice.wait( Until.findObject(By.res(getPackage(), "finish_activity_btn")), - FIND_TIMEOUT) - require(finishButton != null) { - "Finish activity button not found, probably IME activity is not on the screen ?" + FIND_TIMEOUT + ) + requireNotNull(finishButton) { + "Finish activity button not found, probably IME activity is not on the screen?" } finishButton.click() - if (wmHelper == null) { - device.waitForIdle() - } else { - wmHelper.waitForActivityRemoved(component) - } + wmHelper.StateSyncBuilder().withActivityRemoved(this).waitForAndVerify() } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt index 172c4330c3c6..7a8d780c3d9f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt @@ -17,44 +17,40 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.uiautomator.By -import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -class ImeEditorPopupDialogAppHelper @JvmOverloads constructor( +class ImeEditorPopupDialogAppHelper +@JvmOverloads +constructor( instr: Instrumentation, - private val rotation: Int, - private val imePackageName: String = IME_PACKAGE, - launcherName: String = ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME, - component: FlickerComponentName = - ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME.toFlickerComponent() + launcherName: String = ActivityOptions.Ime.EditorPopupDialogActivity.LABEL, + component: ComponentNameMatcher = + ActivityOptions.Ime.EditorPopupDialogActivity.COMPONENT.toFlickerComponent() ) : ImeAppHelper(instr, launcherName, component) { - override fun openIME( - device: UiDevice, - wmHelper: WindowManagerStateHelper? - ) { - val editText = device.wait(Until.findObject(By.text("focused editText")), FIND_TIMEOUT) + override fun openIME(wmHelper: WindowManagerStateHelper) { + val editText = uiDevice.wait(Until.findObject(By.text("focused editText")), FIND_TIMEOUT) - require(editText != null) { + requireNotNull(editText) { "Text field not found, this usually happens when the device " + - "was left in an unknown state (e.g. in split screen)" + "was left in an unknown state (e.g. in split screen)" } editText.click() - waitIMEShown(device, wmHelper) + waitIMEShown(wmHelper) } fun dismissDialog(wmHelper: WindowManagerStateHelper) { - val dismissButton = uiDevice.wait( - Until.findObject(By.text("Dismiss")), FIND_TIMEOUT) + val dismissButton = uiDevice.wait(Until.findObject(By.text("Dismiss")), FIND_TIMEOUT) // Pressing back key to dismiss the dialog if (dismissButton != null) { dismissButton.click() - wmHelper.waitForAppTransitionIdle() + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt new file mode 100644 index 000000000000..83a41abbd4bd --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2020 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.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.tools.common.Rotation +import android.tools.common.traces.Condition +import android.tools.common.traces.DeviceStateDump +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.common.traces.component.IComponentMatcher +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.helpers.IME_PACKAGE +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent +import android.view.WindowInsets.Type.ime +import android.view.WindowInsets.Type.navigationBars +import android.view.WindowInsets.Type.statusBars +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions +import java.util.regex.Pattern + +class ImeShownOnAppStartHelper +@JvmOverloads +constructor( + instr: Instrumentation, + private val rotation: Rotation, + private val imePackageName: String = IME_PACKAGE, + launcherName: String = ActivityOptions.Ime.AutoFocusActivity.LABEL, + component: ComponentNameMatcher = + ActivityOptions.Ime.AutoFocusActivity.COMPONENT.toFlickerComponent() +) : ImeAppHelper(instr, launcherName, component) { + override fun openIME(wmHelper: WindowManagerStateHelper) { + // do nothing (the app is focused automatically) + waitIMEShown(wmHelper) + } + + override fun launchViaIntent( + wmHelper: WindowManagerStateHelper, + launchedAppComponentMatcherOverride: IComponentMatcher?, + action: String?, + stringExtras: Map<String, String>, + waitConditions: Array<Condition<DeviceStateDump>> + ) { + super.launchViaIntent( + wmHelper, + launchedAppComponentMatcherOverride, + action, + stringExtras, + waitConditions + ) + waitIMEShown(wmHelper) + } + + override fun open() { + val expectedPackage = + if (rotation.isRotated()) { + imePackageName + } else { + getPackage() + } + open(expectedPackage) + } + + fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) { + val button = + uiDevice.wait( + Until.findObject(By.res(getPackage(), "start_dialog_themed_activity_btn")), + FIND_TIMEOUT + ) + + requireNotNull(button) { + "Button not found, this usually happens when the device " + + "was left in an unknown state (e.g. Screen turned off)" + } + button.click() + wmHelper + .StateSyncBuilder() + .withFullScreenApp(ActivityOptions.DialogThemedActivity.COMPONENT.toFlickerComponent()) + .waitForAndVerify() + } + + fun dismissDialog(wmHelper: WindowManagerStateHelper) { + val dialog = uiDevice.wait(Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT) + + // Pressing back key to dismiss the dialog + if (dialog != null) { + uiDevice.pressBack() + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + } + } + + fun getInsetsVisibleFromDialog(type: Int): Boolean { + val insetsVisibilityTextView = + uiDevice.wait(Until.findObject(By.res("android:id/text1")), FIND_TIMEOUT) + if (insetsVisibilityTextView != null) { + val visibility = insetsVisibilityTextView.text.toString() + val matcher = + when (type) { + ime() -> { + Pattern.compile("IME\\: (VISIBLE|INVISIBLE)").matcher(visibility) + } + statusBars() -> { + Pattern.compile("StatusBar\\: (VISIBLE|INVISIBLE)").matcher(visibility) + } + navigationBars() -> { + Pattern.compile("NavBar\\: (VISIBLE|INVISIBLE)").matcher(visibility) + } + else -> null + } + if (matcher != null && matcher.find()) { + return matcher.group(1).equals("VISIBLE") + } + } + return false + } + + fun toggleFixPortraitOrientation(wmHelper: WindowManagerStateHelper) { + val button = + uiDevice.wait( + Until.findObject(By.res(getPackage(), "toggle_fixed_portrait_btn")), + FIND_TIMEOUT + ) + require(button != null) { + "Button not found, this usually happens when the device " + + "was left in an unknown state (e.g. Screen turned off)" + } + button.click() + mInstrumentation.waitForIdleSync() + // Ensure app relaunching transition finish and the IME has shown + waitIMEShown(wmHelper) + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt index 16c4c254f9e3..b2aeb14aaf78 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt @@ -17,18 +17,16 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.support.test.launcherhelper.ILauncherStrategy -import android.support.test.launcherhelper.LauncherStrategyFactory +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.toFlickerComponent import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.parser.toFlickerComponent -class ImeStateInitializeHelper @JvmOverloads constructor( +class ImeStateInitializeHelper +@JvmOverloads +constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME, - component: FlickerComponentName = - ActivityOptions.IME_ACTIVITY_INITIALIZE_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy -) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
\ No newline at end of file + launcherName: String = ActivityOptions.Ime.StateInitializeActivity.LABEL, + component: ComponentNameMatcher = + ActivityOptions.Ime.StateInitializeActivity.COMPONENT.toFlickerComponent() +) : StandardAppHelper(instr, launcherName, component) diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt index 165d728c92a6..b95d86b72f34 100644 --- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt @@ -13,16 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.content.componentalias.tests; -public final class ComponentAliasTestCommon { - private ComponentAliasTestCommon() { - } +package com.android.server.wm.flicker.helpers - public static final String TAG = "ComponentAliasTest"; +import android.app.Instrumentation +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.toFlickerComponent +import com.android.server.wm.flicker.testapp.ActivityOptions - public static final String MAIN_PACKAGE = "android.content.componentalias.tests"; - - public static final String SUB1_PACKAGE = "android.content.componentalias.tests.sub1"; - public static final String SUB2_PACKAGE = "android.content.componentalias.tests.sub2"; -} +class LaunchBubbleHelper(instrumentation: Instrumentation) : + StandardAppHelper( + instrumentation, + ActivityOptions.Bubbles.LaunchBubble.LABEL, + ActivityOptions.Bubbles.LaunchBubble.COMPONENT.toFlickerComponent() + ) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt new file mode 100644 index 000000000000..a670d68cabd9 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.helpers.SYSTEMUI_PACKAGE +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions + +class LetterboxAppHelper +@JvmOverloads +constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.NonResizeablePortraitActivity.LABEL, + component: ComponentNameMatcher = + ActivityOptions.NonResizeablePortraitActivity.COMPONENT.toFlickerComponent() +) : StandardAppHelper(instr, launcherName, component) { + + fun clickRestart(wmHelper: WindowManagerStateHelper) { + val restartButton = + uiDevice.wait( + Until.findObject(By.res(SYSTEMUI_PACKAGE, "size_compat_restart_button")), + FIND_TIMEOUT + ) + restartButton?.run { restartButton.click() } ?: error("Restart button not found") + + // size compat mode restart confirmation dialog button + val restartDialogButton = + uiDevice.wait( + Until.findObject( + By.res(SYSTEMUI_PACKAGE, "letterbox_restart_dialog_restart_button") + ), + FIND_TIMEOUT + ) + restartDialogButton?.run { restartDialogButton.click() } + ?: error("Restart dialog button not found") + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt new file mode 100644 index 000000000000..c98f1c4b4d29 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt @@ -0,0 +1,68 @@ +/* + * 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.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.toFlickerComponent +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Direction +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions + +class MailAppHelper +@JvmOverloads +constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.Mail.LABEL, + component: ComponentNameMatcher = ActivityOptions.Mail.COMPONENT.toFlickerComponent() +) : StandardAppHelper(instr, launcherName, component) { + + fun openMail(rowIdx: Int) { + val rowSel = + By.res(getPackage(), "mail_row_item_text").textEndsWith(String.format("%04d", rowIdx)) + var row: UiObject2? = null + for (i in 1..1000) { + row = uiDevice.wait(Until.findObject(rowSel), SHORT_WAIT_TIME_MS) + if (row != null) break + scrollDown() + } + require(row != null) { "" } + row.click() + uiDevice.wait(Until.gone(By.res(getPackage(), MAIL_LIST_RES_ID)), FIND_TIMEOUT) + } + + fun scrollDown() { + val listView = waitForMailList() + listView.scroll(Direction.DOWN, 1.0f) + } + + fun waitForMailList(): UiObject2 { + val sel = By.res(getPackage(), MAIL_LIST_RES_ID).scrollable(true) + val ret = uiDevice.wait(Until.findObject(sel), FIND_TIMEOUT) + requireNotNull(ret) { "Unable to find $MAIL_LIST_RES_ID object" } + return ret + } + + companion object { + private const val SHORT_WAIT_TIME_MS = 5000L + private const val MAIL_LIST_RES_ID = "mail_recycle_view" + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt new file mode 100644 index 000000000000..65175ef33afb --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2020 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.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.content.Context +import android.provider.Settings +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.util.Log +import com.android.compatibility.common.util.SystemUtil +import java.io.IOException + +class MultiWindowUtils( + instrumentation: Instrumentation, + activityLabel: String, + componentsInfo: ComponentNameMatcher +) : StandardAppHelper(instrumentation, activityLabel, componentsInfo) { + + companion object { + fun executeShellCommand(instrumentation: Instrumentation, cmd: String) { + try { + SystemUtil.runShellCommand(instrumentation, cmd) + } catch (e: IOException) { + Log.e(MultiWindowUtils::class.simpleName, "executeShellCommand error! $e") + } + } + + fun getDevEnableNonResizableMultiWindow(context: Context): Int = + Settings.Global.getInt( + context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW + ) + + fun setDevEnableNonResizableMultiWindow(context: Context, configValue: Int) = + Settings.Global.putInt( + context.contentResolver, + Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, + configValue + ) + + fun setSupportsNonResizableMultiWindow(instrumentation: Instrumentation, configValue: Int) = + executeShellCommand( + instrumentation, + createConfigSupportsNonResizableMultiWindowCommand(configValue) + ) + + fun resetMultiWindowConfig(instrumentation: Instrumentation) = + executeShellCommand(instrumentation, resetMultiWindowConfigCommand) + + private fun createConfigSupportsNonResizableMultiWindowCommand(configValue: Int): String = + "wm set-multi-window-config --supportsNonResizable $configValue" + + private const val resetMultiWindowConfigCommand: String = "wm reset-multi-window-config" + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt index be68704fc32d..5b3d3083fe1c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt @@ -17,36 +17,32 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.support.test.launcherhelper.ILauncherStrategy -import android.support.test.launcherhelper.LauncherStrategyFactory +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -class NewTasksAppHelper @JvmOverloads constructor( +class NewTasksAppHelper +@JvmOverloads +constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME, - component: FlickerComponentName = - ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy -) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { + launcherName: String = ActivityOptions.LaunchNewTask.LABEL, + component: ComponentNameMatcher = ActivityOptions.LaunchNewTask.COMPONENT.toFlickerComponent() +) : StandardAppHelper(instr, launcherName, component) { fun openNewTask(device: UiDevice, wmHelper: WindowManagerStateHelper) { - val button = device.wait( - Until.findObject(By.res(getPackage(), "launch_new_task")), - FIND_TIMEOUT) + val button = + device.wait(Until.findObject(By.res(getPackage(), "launch_new_task")), FIND_TIMEOUT) - require(button != null) { + requireNotNull(button) { "Button not found, this usually happens when the device " + - "was left in an unknown state (e.g. in split screen)" + "was left in an unknown state (e.g. in split screen)" } button.click() - wmHelper.waitForAppTransitionIdle() - wmHelper.waitForFullScreenApp(component) + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt index f7ca5ce1c001..ee65004e9e78 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt @@ -17,18 +17,16 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.support.test.launcherhelper.ILauncherStrategy -import android.support.test.launcherhelper.LauncherStrategyFactory +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.toFlickerComponent import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.parser.toFlickerComponent -class NonResizeableAppHelper @JvmOverloads constructor( +class NonResizeableAppHelper +@JvmOverloads +constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME, - component: FlickerComponentName = - ActivityOptions.NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy -) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
\ No newline at end of file + launcherName: String = ActivityOptions.NonResizeableActivity.LABEL, + component: ComponentNameMatcher = + ActivityOptions.NonResizeableActivity.COMPONENT.toFlickerComponent() +) : StandardAppHelper(instr, launcherName, component) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt index 4e360f98723e..7665690a3122 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt @@ -17,37 +17,34 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.support.test.launcherhelper.ILauncherStrategy -import android.support.test.launcherhelper.LauncherStrategyFactory +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.uiautomator.By -import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -class NotificationAppHelper @JvmOverloads constructor( +class NotificationAppHelper +@JvmOverloads +constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.NOTIFICATION_ACTIVITY_LAUNCHER_NAME, - component: FlickerComponentName = - ActivityOptions.NOTIFICATION_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy -) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { - fun postNotification(device: UiDevice, wmHelper: WindowManagerStateHelper) { - val button = device.wait( - Until.findObject(By.res(getPackage(), "post_notification")), - FIND_TIMEOUT) + launcherName: String = ActivityOptions.Notification.LABEL, + component: ComponentNameMatcher = ActivityOptions.Notification.COMPONENT.toFlickerComponent() +) : StandardAppHelper(instr, launcherName, component) { + fun postNotification(wmHelper: WindowManagerStateHelper) { + val button = + uiDevice.wait(Until.findObject(By.res(getPackage(), "post_notification")), FIND_TIMEOUT) - require(button != null) { + requireNotNull(button) { "Post notification button not found, this usually happens when the device " + - "was left in an unknown state (e.g. in split screen)" + "was left in an unknown state (e.g. in split screen)" } button.click() - device.wait(Until.findObject(By.text("Flicker Test Notification")), FIND_TIMEOUT) - ?: error("Flicker Notification not found") + uiDevice.wait(Until.findObject(By.text("Flicker Test Notification")), FIND_TIMEOUT) + ?: error("Flicker Notification not found") + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt new file mode 100644 index 000000000000..24e231c73a0f --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2020 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.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.media.session.MediaController +import android.media.session.MediaSessionManager +import android.tools.common.datatypes.Rect +import android.tools.common.datatypes.Region +import android.tools.common.traces.ConditionsFactory +import android.tools.common.traces.component.IComponentMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.helpers.SYSTEMUI_PACKAGE +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent +import android.util.Log +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions + +open class PipAppHelper(instrumentation: Instrumentation) : + StandardAppHelper( + instrumentation, + ActivityOptions.Pip.LABEL, + ActivityOptions.Pip.COMPONENT.toFlickerComponent() + ) { + private val mediaSessionManager: MediaSessionManager + get() = + context.getSystemService(MediaSessionManager::class.java) + ?: error("Could not get MediaSessionManager") + + private val mediaController: MediaController? + get() = + mediaSessionManager.getActiveSessions(null).firstOrNull { it.packageName == `package` } + + private val gestureHelper: GestureHelper = GestureHelper(mInstrumentation) + + open fun clickObject(resId: String) { + val selector = By.res(`package`, resId) + val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object") + + obj.click() + } + + /** Drags the PIP window to the provided final coordinates without releasing the pointer. */ + fun dragPipWindowAwayFromEdgeWithoutRelease(wmHelper: WindowManagerStateHelper, steps: Int) { + val initWindowRect = getWindowRect(wmHelper).clone() + + // initial pointer at the center of the window + val initialCoord = + GestureHelper.Tuple( + initWindowRect.centerX().toFloat(), + initWindowRect.centerY().toFloat() + ) + + // the offset to the right (or left) of the window center to drag the window to + val offset = 50 + + // the actual final x coordinate with the offset included; + // if the pip window is closer to the right edge of the display the offset is negative + // otherwise the offset is positive + val endX = + initWindowRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) -1 else 1) + val finalCoord = GestureHelper.Tuple(endX.toFloat(), initWindowRect.centerY().toFloat()) + + // drag to the final coordinate + gestureHelper.dragWithoutRelease(initialCoord, finalCoord, steps) + } + + /** + * Releases the primary pointer. + * + * Injects the release of the primary pointer if the primary pointer info was cached after + * another gesture was injected without pointer release. + */ + fun releasePipAfterDragging() { + gestureHelper.releasePrimaryPointer() + } + + /** + * Drags the PIP window away from the screen edge while not crossing the display center. + * + * @throws IllegalStateException if default display bounds are not available + */ + fun dragPipWindowAwayFromEdge(wmHelper: WindowManagerStateHelper, steps: Int) { + val initWindowRect = getWindowRect(wmHelper).clone() + + // initial pointer at the center of the window + val startX = initWindowRect.centerX() + val y = initWindowRect.centerY() + + val displayRect = + wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect + ?: throw IllegalStateException("Default display is null") + + // the offset to the right (or left) of the display center to drag the window to + val offset = 20 + + // the actual final x coordinate with the offset included; + // if the pip window is closer to the right edge of the display the offset is positive + // otherwise the offset is negative + val endX = displayRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) 1 else -1) + + // drag the window to the left but not beyond the center of the display + uiDevice.drag(startX, y, endX, y, steps) + } + + /** + * Returns true if PIP window is closer to the right edge of the display than left. + * + * @throws IllegalStateException if default display bounds are not available + */ + fun isCloserToRightEdge(wmHelper: WindowManagerStateHelper): Boolean { + val windowRect = getWindowRect(wmHelper) + + val displayRect = + wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect + ?: throw IllegalStateException("Default display is null") + + return windowRect.centerX() > displayRect.centerX() + } + + /** + * Expands the PIP window by using the pinch out gesture. + * + * @param percent The percentage by which to increase the pip window size. + * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f + */ + fun pinchOpenPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) { + // the percentage must be between 0.0f and 1.0f + if (percent <= 0.0f || percent > 1.0f) { + throw IllegalArgumentException("Percent must be between 0.0f and 1.0f") + } + + val windowRect = getWindowRect(wmHelper) + + // first pointer's initial x coordinate is halfway between the left edge and the center + val initLeftX = (windowRect.centerX() - windowRect.width / 4).toFloat() + // second pointer's initial x coordinate is halfway between the right edge and the center + val initRightX = (windowRect.centerX() + windowRect.width / 4).toFloat() + + // horizontal distance the window should increase by + val distIncrease = windowRect.width * percent + + // final x-coordinates + val finalLeftX = initLeftX - (distIncrease / 2) + val finalRightX = initRightX + (distIncrease / 2) + + // y-coordinate is the same throughout this animation + val yCoord = windowRect.centerY().toFloat() + + var adjustedSteps = MIN_STEPS_TO_ANIMATE + + // if distance per step is at least 1, then we can use the number of steps requested + if (distIncrease.toInt() / (steps * 2) >= 1) { + adjustedSteps = steps + } + + // if the distance per step is less than 1, carry out the animation in two steps + gestureHelper.pinch( + GestureHelper.Tuple(initLeftX, yCoord), + GestureHelper.Tuple(initRightX, yCoord), + GestureHelper.Tuple(finalLeftX, yCoord), + GestureHelper.Tuple(finalRightX, yCoord), + adjustedSteps + ) + + waitForPipWindowToExpandFrom(wmHelper, Region.from(windowRect)) + } + + /** + * Minimizes the PIP window by using the pinch in gesture. + * + * @param percent The percentage by which to decrease the pip window size. + * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f + */ + fun pinchInPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) { + // the percentage must be between 0.0f and 1.0f + if (percent <= 0.0f || percent > 1.0f) { + throw IllegalArgumentException("Percent must be between 0.0f and 1.0f") + } + + val windowRect = getWindowRect(wmHelper) + + // first pointer's initial x coordinate is halfway between the left edge and the center + val initLeftX = (windowRect.centerX() - windowRect.width / 4).toFloat() + // second pointer's initial x coordinate is halfway between the right edge and the center + val initRightX = (windowRect.centerX() + windowRect.width / 4).toFloat() + + // decrease by the distance specified through the percentage + val distDecrease = windowRect.width * percent + + // get the final x-coordinates and make sure they are not passing the center of the window + val finalLeftX = Math.min(initLeftX + (distDecrease / 2), windowRect.centerX().toFloat()) + val finalRightX = Math.max(initRightX - (distDecrease / 2), windowRect.centerX().toFloat()) + + // y-coordinate is the same throughout this animation + val yCoord = windowRect.centerY().toFloat() + + var adjustedSteps = MIN_STEPS_TO_ANIMATE + + // if distance per step is at least 1, then we can use the number of steps requested + if (distDecrease.toInt() / (steps * 2) >= 1) { + adjustedSteps = steps + } + + // if the distance per step is less than 1, carry out the animation in two steps + gestureHelper.pinch( + GestureHelper.Tuple(initLeftX, yCoord), + GestureHelper.Tuple(initRightX, yCoord), + GestureHelper.Tuple(finalLeftX, yCoord), + GestureHelper.Tuple(finalRightX, yCoord), + adjustedSteps + ) + + waitForPipWindowToMinimizeFrom(wmHelper, Region.from(windowRect)) + } + + /** + * Launches the app through an intent instead of interacting with the launcher and waits until + * the app window is in PIP mode + */ + @JvmOverloads + fun launchViaIntentAndWaitForPip( + wmHelper: WindowManagerStateHelper, + launchedAppComponentMatcherOverride: IComponentMatcher? = null, + action: String? = null, + stringExtras: Map<String, String> + ) { + launchViaIntentAndWaitShown( + wmHelper, + launchedAppComponentMatcherOverride, + action, + stringExtras, + waitConditions = arrayOf(ConditionsFactory.hasPipWindow()) + ) + + wmHelper + .StateSyncBuilder() + .withWindowSurfaceAppeared(this) + .withPipShown() + .waitForAndVerify() + } + + /** Expand the PIP window back to full screen via intent and wait until the app is visible */ + fun exitPipToFullScreenViaIntent(wmHelper: WindowManagerStateHelper) = + launchViaIntentAndWaitShown(wmHelper) + + fun clickEnterPipButton(wmHelper: WindowManagerStateHelper) { + clickObject(ENTER_PIP_BUTTON_ID) + + // Wait on WMHelper or simply wait for 3 seconds + wmHelper.StateSyncBuilder().withPipShown().waitForAndVerify() + // when entering pip, the dismiss button is visible at the start. to ensure the pip + // animation is complete, wait until the pip dismiss button is no longer visible. + // b/176822698: dismiss-only state will be removed in the future + uiDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "dismiss")), FIND_TIMEOUT) + } + + fun enableEnterPipOnUserLeaveHint() { + clickObject(ENTER_PIP_ON_USER_LEAVE_HINT) + } + + fun enableAutoEnterForPipActivity() { + clickObject(ENTER_PIP_AUTOENTER) + } + + fun clickStartMediaSessionButton() { + clickObject(MEDIA_SESSION_START_RADIO_BUTTON_ID) + } + + fun checkWithCustomActionsCheckbox() = + uiDevice + .findObject(By.res(`package`, WITH_CUSTOM_ACTIONS_BUTTON_ID)) + ?.takeIf { it.isCheckable } + ?.apply { if (!isChecked) clickObject(WITH_CUSTOM_ACTIONS_BUTTON_ID) } + ?: error("'With custom actions' checkbox not found") + + fun pauseMedia() = + mediaController?.transportControls?.pause() ?: error("No active media session found") + + fun stopMedia() = + mediaController?.transportControls?.stop() ?: error("No active media session found") + + @Deprecated( + "Use PipAppHelper.closePipWindow(wmHelper) instead", + ReplaceWith("closePipWindow(wmHelper)") + ) + open fun closePipWindow() { + closePipWindow(WindowManagerStateHelper(mInstrumentation)) + } + + /** Returns the pip window bounds. */ + fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect { + val windowRegion = wmHelper.getWindowRegion(this) + require(!windowRegion.isEmpty) { "Unable to find a PIP window in the current state" } + return windowRegion.bounds + } + + /** Taps the pip window and dismisses it by clicking on the X button. */ + open fun closePipWindow(wmHelper: WindowManagerStateHelper) { + val windowRect = getWindowRect(wmHelper) + uiDevice.click(windowRect.centerX(), windowRect.centerY()) + // search and interact with the dismiss button + val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss") + uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT) + val dismissPipObject = + uiDevice.findObject(dismissSelector) ?: error("PIP window dismiss button not found") + val dismissButtonBounds = dismissPipObject.visibleBounds + uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY()) + + // Wait for animation to complete. + wmHelper.StateSyncBuilder().withPipGone().withHomeActivityVisible().waitForAndVerify() + } + + /** Close the pip window by pressing the expand button */ + fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) { + val windowRect = getWindowRect(wmHelper) + uiDevice.click(windowRect.centerX(), windowRect.centerY()) + // search and interact with the expand button + val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button") + uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT) + val expandPipObject = + uiDevice.findObject(expandSelector) ?: error("PIP window expand button not found") + val expandButtonBounds = expandPipObject.visibleBounds + uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY()) + wmHelper.StateSyncBuilder().withPipGone().withFullScreenApp(this).waitForAndVerify() + } + + /** Double click on the PIP window to expand it */ + fun doubleClickPipWindow(wmHelper: WindowManagerStateHelper) { + val windowRect = getWindowRect(wmHelper) + Log.d(TAG, "First click") + uiDevice.click(windowRect.centerX(), windowRect.centerY()) + Log.d(TAG, "Second click") + uiDevice.click(windowRect.centerX(), windowRect.centerY()) + Log.d(TAG, "Wait for app transition to end") + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + waitForPipWindowToExpandFrom(wmHelper, Region.from(windowRect)) + } + + private fun waitForPipWindowToExpandFrom( + wmHelper: WindowManagerStateHelper, + windowRect: Region + ) { + wmHelper + .StateSyncBuilder() + .add("pipWindowExpanded") { + val pipAppWindow = + it.wmState.visibleWindows.firstOrNull { window -> + this.windowMatchesAnyOf(window) + } + ?: return@add false + val pipRegion = pipAppWindow.frameRegion + return@add pipRegion.coversMoreThan(windowRect) + } + .waitForAndVerify() + } + + private fun waitForPipWindowToMinimizeFrom( + wmHelper: WindowManagerStateHelper, + windowRect: Region + ) { + wmHelper + .StateSyncBuilder() + .add("pipWindowMinimized") { + val pipAppWindow = + it.wmState.visibleWindows.firstOrNull { window -> + this.windowMatchesAnyOf(window) + } + ?: return@add false + val pipRegion = pipAppWindow.frameRegion + return@add windowRect.coversMoreThan(pipRegion) + } + .waitForAndVerify() + } + + /** + * Waits until the PIP window snaps horizontally to the provided bounds. + * + * @param finalBounds the bounds to wait for PIP window to snap to + */ + fun waitForPipToSnapTo(wmHelper: WindowManagerStateHelper, finalBounds: android.graphics.Rect) { + wmHelper + .StateSyncBuilder() + .add("pipWindowSnapped") { + val pipAppWindow = + it.wmState.visibleWindows.firstOrNull { window -> + this.windowMatchesAnyOf(window) + } + ?: return@add false + val pipRegionBounds = pipAppWindow.frameRegion.bounds + return@add pipRegionBounds.left == finalBounds.left && + pipRegionBounds.right == finalBounds.right + } + .add(ConditionsFactory.isWMStateComplete()) + .waitForAndVerify() + } + + companion object { + private const val TAG = "PipAppHelper" + private const val ENTER_PIP_BUTTON_ID = "enter_pip" + private const val WITH_CUSTOM_ACTIONS_BUTTON_ID = "with_custom_actions" + private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start" + private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual" + private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter" + // minimum number of steps to take, when animating gestures, needs to be 2 + // so that there is at least a single intermediate layer that flicker tests can check + private const val MIN_STEPS_TO_ANIMATE = 2 + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt index 7bab981ce231..cac3530399de 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt @@ -17,18 +17,16 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.support.test.launcherhelper.ILauncherStrategy -import android.support.test.launcherhelper.LauncherStrategyFactory +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.toFlickerComponent import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.parser.toFlickerComponent -class SeamlessRotationAppHelper @JvmOverloads constructor( +class SeamlessRotationAppHelper +@JvmOverloads +constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.SEAMLESS_ACTIVITY_LAUNCHER_NAME, - component: FlickerComponentName = - ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy -) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
\ No newline at end of file + launcherName: String = ActivityOptions.SeamlessRotation.LABEL, + component: ComponentNameMatcher = + ActivityOptions.SeamlessRotation.COMPONENT.toFlickerComponent() +) : StandardAppHelper(instr, launcherName, component) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt index bd2e5756b4a9..8366a7a1fe41 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt @@ -17,18 +17,16 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.support.test.launcherhelper.ILauncherStrategy -import android.support.test.launcherhelper.LauncherStrategyFactory +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.toFlickerComponent import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.parser.toFlickerComponent -class ShowWhenLockedAppHelper @JvmOverloads constructor( +class ShowWhenLockedAppHelper +@JvmOverloads +constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME, - component: FlickerComponentName = - ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy -) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
\ No newline at end of file + launcherName: String = ActivityOptions.ShowWhenLockedActivity.LABEL, + component: ComponentNameMatcher = + ActivityOptions.ShowWhenLockedActivity.COMPONENT.toFlickerComponent() +) : StandardAppHelper(instr, launcherName, component) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt index f6a8817e5b08..89c6c35af47d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt @@ -17,18 +17,15 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.support.test.launcherhelper.ILauncherStrategy -import android.support.test.launcherhelper.LauncherStrategyFactory +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.traces.parsers.toFlickerComponent import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.parser.toFlickerComponent -class SimpleAppHelper @JvmOverloads constructor( +class SimpleAppHelper +@JvmOverloads +constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.SIMPLE_ACTIVITY_LAUNCHER_NAME, - component: FlickerComponentName = - ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy -) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
\ No newline at end of file + launcherName: String = ActivityOptions.SimpleActivity.LABEL, + component: ComponentNameMatcher = ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent() +) : StandardAppHelper(instr, launcherName, component) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt index a135e0af067b..895725c1efef 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt @@ -17,50 +17,43 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation -import android.support.test.launcherhelper.ILauncherStrategy -import android.support.test.launcherhelper.LauncherStrategyFactory -import android.view.Display +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.helpers.FIND_TIMEOUT +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.common.WindowManagerConditionsFactory -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -class TwoActivitiesAppHelper @JvmOverloads constructor( +class TwoActivitiesAppHelper +@JvmOverloads +constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.BUTTON_ACTIVITY_LAUNCHER_NAME, - component: FlickerComponentName = - ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent(), - launcherStrategy: ILauncherStrategy = LauncherStrategyFactory - .getInstance(instr) - .launcherStrategy -) : StandardAppHelper(instr, launcherName, component, launcherStrategy) { + launcherName: String = ActivityOptions.LaunchNewActivity.LABEL, + component: ComponentNameMatcher = + ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent() +) : StandardAppHelper(instr, launcherName, component) { private val secondActivityComponent = - ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() + ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent() fun openSecondActivity(device: UiDevice, wmHelper: WindowManagerStateHelper) { val launchActivityButton = By.res(getPackage(), LAUNCH_SECOND_ACTIVITY) val button = device.wait(Until.findObject(launchActivityButton), FIND_TIMEOUT) - require(button != null) { + requireNotNull(button) { "Button not found, this usually happens when the device " + - "was left in an unknown state (e.g. in split screen)" + "was left in an unknown state (e.g. in split screen)" } button.click() device.wait(Until.gone(launchActivityButton), FIND_TIMEOUT) - wmHelper.waitFor( - WindowManagerStateHelper.isAppFullScreen(secondActivityComponent), - WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY), - WindowManagerConditionsFactory.hasLayersAnimating().negate() - ) + wmHelper.StateSyncBuilder().withFullScreenApp(secondActivityComponent).waitForAndVerify() } companion object { private const val LAUNCH_SECOND_ACTIVITY = "launch_second_activity" } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt deleted file mode 100644 index dd5f33fb8669..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (C) 2020 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.android.server.wm.flicker.ime - -import android.app.Instrumentation -import android.platform.test.annotations.Presubmit -import android.view.Surface -import android.view.WindowManagerPolicyConstants -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test IME window closing back to app window transitions. - * - * This test doesn't work on 90 degrees. According to the InputMethodService documentation: - * - * Don't show if this is not explicitly requested by the user and the input method - * is fullscreen. That would be too disruptive. - * - * More details on b/190352379 - * - * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToAppTest` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - eachRun { - testApp.launchViaIntent(wmHelper) - testApp.openIME(device, wmHelper) - } - } - teardown { - eachRun { - testApp.exit(wmHelper) - } - } - transitions { - testApp.closeIME(device, wmHelper) - } - } - } - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Presubmit - @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry() - } - } - - @Presubmit - @Test - fun imeAppWindowIsAlwaysVisible() { - testSpec.assertWm { - this.isAppWindowOnTop(testApp.component) - } - } - - @Presubmit - @Test - fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() - - @Presubmit - @Test - fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() - - @Presubmit - @Test - fun entireScreenCovered() = testSpec.entireScreenCovered() - - @Presubmit - @Test - fun imeLayerVisibleStart() { - testSpec.assertLayersStart { - this.isVisible(FlickerComponentName.IME) - } - } - - @Presubmit - @Test - fun imeLayerInvisibleEnd() { - testSpec.assertLayersEnd { - this.isInvisible(FlickerComponentName.IME) - } - } - - @Presubmit - @Test - fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible() - - @Presubmit - @Test - fun imeAppLayerIsAlwaysVisible() { - testSpec.assertLayers { - this.isVisible(testApp.component) - } - } - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - @FlakyTest(bugId = 206753786) - @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - - @Presubmit - @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() { - testSpec.assertLayers { - this.visibleLayersShownMoreThanOneConsecutiveEntry() - } - } - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 3, - // b/190352379 (IME doesn't show on app launch in 90 degrees) - supportedRotations = listOf(Surface.ROTATION_0), - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY) - ) - } - } -} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt deleted file mode 100644 index 5606965a6d40..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2020 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.android.server.wm.flicker.ime - -import android.app.Instrumentation -import android.platform.test.annotations.Presubmit -import android.view.Surface -import android.view.WindowManagerPolicyConstants -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test IME window closing back to app window transitions. - * - * This test doesn't work on 90 degrees. According to the InputMethodService documentation: - * - * Don't show if this is not explicitly requested by the user and the input method - * is fullscreen. That would be too disruptive. - * - * More details on b/190352379 - * - * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToHomeTest` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - eachRun { - testApp.launchViaIntent(wmHelper) - testApp.openIME(device, wmHelper) - } - } - teardown { - eachRun { - testApp.exit(wmHelper) - } - } - transitions { - device.pressHome() - wmHelper.waitForHomeActivityVisible() - wmHelper.waitImeGone() - } - } - } - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Presubmit - @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry() - } - } - - @Presubmit - @Test - fun imeAppWindowBecomesInvisible() { - testSpec.assertWm { - this.isAppWindowOnTop(testApp.component) - .then() - .isAppWindowNotOnTop(testApp.component) - } - } - - @Presubmit - @Test - fun entireScreenCovered() = testSpec.entireScreenCovered() - - @Presubmit - @Test - fun imeLayerVisibleStart() { - testSpec.assertLayersStart { - this.isVisible(FlickerComponentName.IME) - } - } - - @Presubmit - @Test - fun imeLayerInvisibleEnd() { - testSpec.assertLayersEnd { - this.isInvisible(FlickerComponentName.IME) - } - } - - @Presubmit - @Test - fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible() - - @Presubmit - @Test - fun imeAppLayerBecomesInvisible() { - testSpec.assertLayers { - this.isVisible(testApp.component) - .then() - .isInvisible(testApp.component) - } - } - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - @FlakyTest(bugId = 206753786) - @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - - @Presubmit - @Test - fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() - - @Presubmit - @Test - fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() - - @Presubmit - @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() { - testSpec.assertLayers { - this.visibleLayersShownMoreThanOneConsecutiveEntry(listOf( - FlickerComponentName.IME, - FlickerComponentName.SPLASH_SCREEN)) - } - } - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 3, - // b/190352379 (IME doesn't show on app launch in 90 degrees) - supportedRotations = listOf(Surface.ROTATION_0), - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY) - ) - } - } -} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt deleted file mode 100644 index 6257484be9bd..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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.android.server.wm.flicker.ime - -import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit -import android.view.Surface -import android.view.WindowManagerPolicyConstants -import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.ImeEditorPopupDialogAppHelper -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.flicker.traces.region.RegionSubject -import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group4 -class CloseImeEditorPopupDialogTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation, testSpec.startRotation) - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - eachRun { - imeTestApp.launchViaIntent(wmHelper) - imeTestApp.openIME(device, wmHelper) - } - } - transitions { - imeTestApp.dismissDialog(wmHelper) - instrumentation.uiAutomation.syncInputTransactions() - } - teardown { - eachRun { - device.pressHome() - wmHelper.waitForHomeActivityVisible() - imeTestApp.exit() - } - } - } - } - - @Postsubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Postsubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Postsubmit - @Test - fun imeWindowBecameInvisible() = testSpec.imeWindowBecomesInvisible() - - @Postsubmit - @Test - fun imeLayerAndImeSnapshotVisibleOnScreen() { - testSpec.assertLayers { - this.isVisible(FlickerComponentName.IME) - .then() - .isVisible(FlickerComponentName.IME_SNAPSHOT) - .then() - .isInvisible(FlickerComponentName.IME_SNAPSHOT) - .isInvisible(FlickerComponentName.IME) - } - } - - @Postsubmit - @Test - fun imeSnapshotAssociatedOnAppVisibleRegion() { - testSpec.assertLayers { - this.invoke("imeSnapshotAssociatedOnAppVisibleRegion") { - val imeSnapshotLayers = it.subjects.filter { - subject -> subject.name.contains( - FlickerComponentName.IME_SNAPSHOT.toLayerName()) && subject.isVisible - } - if (imeSnapshotLayers.isNotEmpty()) { - val visibleAreas = imeSnapshotLayers.mapNotNull { imeSnapshotLayer -> - imeSnapshotLayer.layer?.visibleRegion }.toTypedArray() - val imeVisibleRegion = RegionSubject.assertThat(visibleAreas, this, timestamp) - val appVisibleRegion = it.visibleRegion(imeTestApp.component) - if (imeVisibleRegion.region.isNotEmpty) { - imeVisibleRegion.coversAtMost(appVisibleRegion.region) - } - } - } - } - } - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 2, - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ), - supportedRotations = listOf(Surface.ROTATION_0) - ) - } - } -}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt new file mode 100644 index 000000000000..7e0632d216ea --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt @@ -0,0 +1,110 @@ +/* + * 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.android.server.wm.flicker.ime + +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.common.flicker.subject.region.RegionSubject +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest +import com.android.server.wm.flicker.helpers.ImeEditorPopupDialogAppHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class CloseImeOnDismissPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) { + private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation) + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + imeTestApp.launchViaIntent(wmHelper) + imeTestApp.openIME(wmHelper) + } + transitions { + imeTestApp.dismissDialog(wmHelper) + wmHelper.StateSyncBuilder().withImeGone().waitForAndVerify() + } + teardown { + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + imeTestApp.exit(wmHelper) + } + } + + @Presubmit @Test fun imeWindowBecameInvisible() = flicker.imeWindowBecomesInvisible() + + @Presubmit + @Test + fun imeLayerAndImeSnapshotVisibleOnScreen() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.IME) + .then() + .isVisible(ComponentNameMatcher.IME_SNAPSHOT, isOptional = true) + .then() + .isInvisible(ComponentNameMatcher.IME_SNAPSHOT, isOptional = true) + .isInvisible(ComponentNameMatcher.IME) + } + } + + @Presubmit + @Test + fun imeSnapshotAssociatedOnAppVisibleRegion() { + flicker.assertLayers { + this.invoke("imeSnapshotAssociatedOnAppVisibleRegion") { + val imeSnapshotLayers = + it.subjects.filter { subject -> + subject.name.contains(ComponentNameMatcher.IME_SNAPSHOT.toLayerName()) && + subject.isVisible + } + if (imeSnapshotLayers.isNotEmpty()) { + val visibleAreas = + imeSnapshotLayers + .mapNotNull { imeSnapshotLayer -> imeSnapshotLayer.layer.visibleRegion } + .toTypedArray() + val imeVisibleRegion = RegionSubject(visibleAreas, timestamp) + val appVisibleRegion = it.visibleRegion(imeTestApp) + if (imeVisibleRegion.region.isNotEmpty) { + imeVisibleRegion.coversAtMost(appVisibleRegion.region) + } + } + } + } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_0) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt index edd52b76cd5c..c355e2708657 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTestCfArm.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2023 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. @@ -16,34 +16,27 @@ package com.android.server.wm.flicker.ime -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import org.junit.Assume -import org.junit.Before - +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized -/** - * Test IME windows switching with 2-Buttons or gestural navigation. - * To run this test: `atest FlickerTests:SwitchImeWindowsFromGestureNavTest` - */ -@RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group4 -@FlakyTest(bugId = 228012334) -class SwitchImeWindowsFromGestureNavTest_ShellTransit(testSpec: FlickerTestParameter) - : SwitchImeWindowsFromGestureNavTest(testSpec) { - @Before - override fun before() { - Assume.assumeTrue(isShellTransitionsEnabled) +class CloseImeOnDismissPopupDialogTestCfArm(flicker: FlickerTest) : + CloseImeOnDismissPopupDialogTest(flicker) { + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_0) + ) + } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt new file mode 100644 index 000000000000..537238b1d626 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2020 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.android.server.wm.flicker.ime + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest +import com.android.server.wm.flicker.helpers.ImeAppHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME window closing to home transitions. To run this test: `atest + * FlickerTests:CloseImeWindowToHomeTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class CloseImeOnGoHomeTest(flicker: FlickerTest) : BaseTest(flicker) { + private val testApp = ImeAppHelper(instrumentation) + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + testApp.launchViaIntent(wmHelper) + testApp.openIME(wmHelper) + } + transitions { + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().withImeGone().waitForAndVerify() + } + teardown { testApp.exit(wmHelper) } + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + flicker.assertWm { + this.visibleWindowsShownMoreThanOneConsecutiveEntry( + listOf( + ComponentNameMatcher.IME, + ComponentNameMatcher.SPLASH_SCREEN, + ComponentNameMatcher.SNAPSHOT + ) + ) + } + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + flicker.assertLayers { + this.visibleLayersShownMoreThanOneConsecutiveEntry( + listOf(ComponentNameMatcher.IME, ComponentNameMatcher.SPLASH_SCREEN) + ) + } + } + + @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible() + + @Presubmit @Test fun imeWindowBecomesInvisible() = flicker.imeWindowBecomesInvisible() + + @Presubmit + @Test + fun imeAppWindowBecomesInvisible() { + flicker.assertWm { this.isAppWindowVisible(testApp).then().isAppWindowInvisible(testApp) } + } + + @Presubmit + @Test + fun imeAppLayerBecomesInvisible() { + flicker.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) } + } + + @Presubmit + @Test + @PlatinumTest(focusArea = "ime") + override fun cujCompleted() { + super.cujCompleted() + imeLayerBecomesInvisible() + imeAppWindowBecomesInvisible() + imeWindowBecomesInvisible() + imeLayerBecomesInvisible() + runAndIgnoreAssumptionViolation { navBarLayerPositionAtStartAndEnd() } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_0) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt new file mode 100644 index 000000000000..0fe52df5bb25 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTestCfArm.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.ime + +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class CloseImeOnGoHomeTestCfArm(flicker: FlickerTest) : CloseImeOnGoHomeTest(flicker) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt new file mode 100644 index 000000000000..cbe03dcd932a --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2020 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.android.server.wm.flicker.ime + +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest +import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME window closing back to app window transitions. + * + * This test doesn't work on 90 degrees. According to the InputMethodService documentation: + * ``` + * Don't show if this is not explicitly requested by the user and the input method + * is fullscreen. That would be too disruptive. + * ``` + * + * More details on b/190352379 + * + * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToHomeTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class CloseImeShownOnAppStartOnGoHomeTest(flicker: FlickerTest) : BaseTest(flicker) { + private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation) + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + testApp.launchViaIntent(wmHelper) + } + teardown { testApp.exit(wmHelper) } + transitions { + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().withImeGone().waitForAndVerify() + } + } + + @Presubmit + @Test + fun imeAppWindowBecomesInvisible() { + flicker.assertWm { this.isAppWindowOnTop(testApp).then().isAppWindowNotOnTop(testApp) } + } + + @Presubmit + @Test + fun imeLayerVisibleStart() { + flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) } + } + + @Presubmit + @Test + fun imeLayerInvisibleEnd() { + flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.IME) } + } + + @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible() + + @Presubmit + @Test + fun imeAppLayerBecomesInvisible() { + flicker.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + // b/190352379 (IME doesn't show on app launch in 90 degrees) + supportedRotations = listOf(Rotation.ROTATION_0) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt new file mode 100644 index 000000000000..5aacb3011e79 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTestCfArm.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.ime + +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class CloseImeShownOnAppStartOnGoHomeTestCfArm(flicker: FlickerTest) : + CloseImeShownOnAppStartOnGoHomeTest(flicker) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt new file mode 100644 index 000000000000..82c390b77d59 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2020 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.android.server.wm.flicker.ime + +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest +import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME window closing back to app window transitions. + * + * This test doesn't work on 90 degrees. According to the InputMethodService documentation: + * ``` + * Don't show if this is not explicitly requested by the user and the input method + * is fullscreen. That would be too disruptive. + * ``` + * + * More details on b/190352379 + * + * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToAppTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class CloseImeShownOnAppStartToAppOnPressBackTest(flicker: FlickerTest) : BaseTest(flicker) { + private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation) + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { testApp.launchViaIntent(wmHelper) } + teardown { testApp.exit(wmHelper) } + transitions { testApp.closeIME(wmHelper) } + } + + @Presubmit + @Test + fun imeAppWindowIsAlwaysVisible() { + flicker.assertWm { this.isAppWindowOnTop(testApp) } + } + + @Presubmit + @Test + fun imeLayerVisibleStart() { + flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) } + } + + @Presubmit + @Test + fun imeLayerInvisibleEnd() { + flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.IME) } + } + + @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible() + + @Presubmit + @Test + fun imeAppLayerIsAlwaysVisible() { + flicker.assertLayers { this.isVisible(testApp) } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + // b/190352379 (IME doesn't show on app launch in 90 degrees) + supportedRotations = listOf(Rotation.ROTATION_0) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt new file mode 100644 index 000000000000..eb81aed35011 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTestCfArm.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.ime + +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class CloseImeShownOnAppStartToAppOnPressBackTestCfArm(flicker: FlickerTest) : + CloseImeShownOnAppStartToAppOnPressBackTest(flicker) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt new file mode 100644 index 000000000000..8d8075927076 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2020 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.android.server.wm.flicker.ime + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest +import com.android.server.wm.flicker.helpers.ImeAppHelper +import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd +import org.junit.Assume +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME window closing back to app window transitions. To run this test: `atest + * FlickerTests:CloseImeWindowToAppTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class CloseImeToAppOnPressBackTest(flicker: FlickerTest) : BaseTest(flicker) { + private val testApp = ImeAppHelper(instrumentation) + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + testApp.launchViaIntent(wmHelper) + testApp.openIME(wmHelper) + } + teardown { testApp.exit(wmHelper) } + transitions { testApp.closeIME(wmHelper) } + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + flicker.assertWm { + this.visibleWindowsShownMoreThanOneConsecutiveEntry( + listOf( + ComponentNameMatcher.IME, + ComponentNameMatcher.SPLASH_SCREEN, + ComponentNameMatcher.SNAPSHOT + ) + ) + } + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarLayerPositionAtStartAndEnd() { + Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(flicker.scenario.isLandscapeOrSeascapeAtStart) + flicker.navBarLayerPositionAtStartAndEnd() + } + + @Presubmit + @Test + fun navBarLayerPositionAtStartAndEndLandscapeOrSeascapeAtStart() { + Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart) + flicker.navBarLayerPositionAtStartAndEnd() + } + + @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible() + + @Presubmit + @Test + fun imeAppLayerIsAlwaysVisible() { + flicker.assertLayers { this.isVisible(testApp) } + } + + @Presubmit + @Test + fun imeAppWindowIsAlwaysVisible() { + flicker.assertWm { this.isAppWindowOnTop(testApp) } + } + + @Presubmit + @Test + @PlatinumTest(focusArea = "ime") + override fun cujCompleted() { + super.cujCompleted() + imeLayerBecomesInvisible() + imeAppLayerIsAlwaysVisible() + imeAppWindowIsAlwaysVisible() + runAndIgnoreAssumptionViolation { navBarLayerPositionAtStartAndEnd() } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt new file mode 100644 index 000000000000..db1440b0c5b8 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTestCfArm.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.ime + +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class CloseImeToAppOnPressBackTestCfArm(flicker: FlickerTest) : + CloseImeToAppOnPressBackTest(flicker) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt new file mode 100644 index 000000000000..15262959332e --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2021 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.android.server.wm.flicker.ime + +import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest +import com.android.server.wm.flicker.helpers.ImeAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Unlike {@link OpenImeWindowTest} testing IME window opening transitions, this test also verify + * there is no flickering when back to the simple activity without requesting IME to show. + * + * To run this test: `atest FlickerTests:OpenImeWindowAndCloseTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class CloseImeToHomeOnFinishActivityTest(flicker: FlickerTest) : BaseTest(flicker) { + private val simpleApp = SimpleAppHelper(instrumentation) + private val testApp = ImeAppHelper(instrumentation) + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + simpleApp.launchViaIntent(wmHelper) + testApp.launchViaIntent(wmHelper) + testApp.openIME(wmHelper) + } + transitions { testApp.finishActivity(wmHelper) } + teardown { simpleApp.exit(wmHelper) } + } + + @Presubmit @Test fun imeWindowBecomesInvisible() = flicker.imeWindowBecomesInvisible() + + @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible() + + @FlakyTest(bugId = 246284124) + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + + @Presubmit + @Test + @PlatinumTest(focusArea = "ime") + override fun cujCompleted() { + runAndIgnoreAssumptionViolation { entireScreenCovered() } + runAndIgnoreAssumptionViolation { statusBarLayerIsVisibleAtStartAndEnd() } + runAndIgnoreAssumptionViolation { statusBarLayerPositionAtStartAndEnd() } + runAndIgnoreAssumptionViolation { statusBarWindowIsAlwaysVisible() } + runAndIgnoreAssumptionViolation { visibleWindowsShownMoreThanOneConsecutiveEntry() } + runAndIgnoreAssumptionViolation { taskBarLayerIsVisibleAtStartAndEnd() } + runAndIgnoreAssumptionViolation { taskBarWindowIsAlwaysVisible() } + runAndIgnoreAssumptionViolation { navBarLayerIsVisibleAtStartAndEnd() } + runAndIgnoreAssumptionViolation { navBarWindowIsAlwaysVisible() } + runAndIgnoreAssumptionViolation { navBarWindowIsVisibleAtStartAndEnd() } + imeLayerBecomesInvisible() + imeWindowBecomesInvisible() + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_0) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt new file mode 100644 index 000000000000..405ab6bcd9d6 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTestCfArm.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.ime + +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class CloseImeToHomeOnFinishActivityTestCfArm(flicker: FlickerTest) : + CloseImeToHomeOnFinishActivityTest(flicker) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt deleted file mode 100644 index e7a1c50821b7..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2020 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.android.server.wm.flicker.ime - -import android.app.Instrumentation -import android.platform.test.annotations.Presubmit -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.Assume.assumeFalse -import org.junit.Assume.assumeTrue -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test IME window closing back to app window transitions. - * To run this test: `atest FlickerTests:CloseImeWindowToAppTest` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = ImeAppHelper(instrumentation) - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - test { - testApp.launchViaIntent(wmHelper) - } - eachRun { - testApp.openIME(device, wmHelper) - } - } - teardown { - test { - testApp.exit(wmHelper) - } - } - transitions { - testApp.closeIME(device, wmHelper) - } - } - } - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Presubmit - @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf( - FlickerComponentName.IME, - FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT)) - } - } - - @Presubmit - @Test - fun imeAppWindowIsAlwaysVisible() { - testSpec.assertWm { - this.isAppWindowOnTop(testApp.component) - } - } - - @Presubmit - @Test - fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() - - @Presubmit - @Test - fun statusBarLayerIsVisible() = testSpec.navBarLayerIsVisible() - - @Presubmit - @Test - fun entireScreenCovered() = testSpec.entireScreenCovered() - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() { - assumeFalse(testSpec.isLandscapeOrSeascapeAtStart) - testSpec.navBarLayerRotatesAndScales() - } - - @FlakyTest - @Test - fun navBarLayerRotatesAndScales_Flaky() { - assumeTrue(testSpec.isLandscapeOrSeascapeAtStart) - testSpec.navBarLayerRotatesAndScales() - } - - @FlakyTest(bugId = 206753786) - @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - - @Presubmit - @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() { - testSpec.assertLayers { - this.visibleLayersShownMoreThanOneConsecutiveEntry() - } - } - - @Presubmit - @Test - fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible() - - @Presubmit - @Test - fun imeAppLayerIsAlwaysVisible() { - testSpec.assertLayers { - this.isVisible(testApp.component) - } - } - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 3) - } - } -}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt deleted file mode 100644 index b454f0155b3e..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2020 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.android.server.wm.flicker.ime - -import android.app.Instrumentation -import android.platform.test.annotations.Presubmit -import android.view.Surface -import android.view.WindowManagerPolicyConstants -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized -/** - * Test IME window closing to home transitions. - * To run this test: `atest FlickerTests:CloseImeWindowToHomeTest` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = ImeAppHelper(instrumentation) - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - eachRun { - testApp.launchViaIntent(wmHelper) - testApp.openIME(device, wmHelper) - } - } - transitions { - device.pressHome() - wmHelper.waitForHomeActivityVisible() - wmHelper.waitImeGone() - } - teardown { - eachRun { - device.pressHome() - wmHelper.waitForHomeActivityVisible() - } - test { - testApp.exit() - } - } - } - } - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Presubmit - @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry(listOf( - FlickerComponentName.IME, - FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT)) - } - } - - @Presubmit - @Test - fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible() - - @Presubmit - @Test - fun imeAppWindowBecomesInvisible() { - testSpec.assertWm { - this.isAppWindowVisible(testApp.component) - .then() - .isAppWindowInvisible(testApp.component) - } - } - - @Presubmit - @Test - fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() - - @Presubmit - @Test - fun statusBarLayerIsVisible() = testSpec.navBarLayerIsVisible() - - @Presubmit - @Test - fun entireScreenCovered() = testSpec.entireScreenCovered() - - @Presubmit - @Test - fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible() - - @Presubmit - @Test - fun imeAppLayerBecomesInvisible() { - testSpec.assertLayers { - this.isVisible(testApp.component) - .then() - .isInvisible(testApp.component) - } - } - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - @FlakyTest(bugId = 206753786) - @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - - @Presubmit - @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() { - testSpec.assertLayers { - this.visibleLayersShownMoreThanOneConsecutiveEntry(listOf( - FlickerComponentName.IME, - FlickerComponentName.SPLASH_SCREEN)) - } - } - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 3, - supportedRotations = listOf(Surface.ROTATION_0), - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ) - ) - } - } -} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt index ba78e25580ec..8e3371986056 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt @@ -15,55 +15,50 @@ */ @file:JvmName("CommonAssertions") + package com.android.server.wm.flicker.ime -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.traces.common.FlickerComponentName +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.legacy.FlickerTest -fun FlickerTestParameter.imeLayerBecomesVisible() { +fun FlickerTest.imeLayerBecomesVisible() { assertLayers { - this.isInvisible(FlickerComponentName.IME) - .then() - .isVisible(FlickerComponentName.IME) + this.isInvisible(ComponentNameMatcher.IME).then().isVisible(ComponentNameMatcher.IME) } } -fun FlickerTestParameter.imeLayerBecomesInvisible() { +fun FlickerTest.imeLayerBecomesInvisible() { assertLayers { - this.isVisible(FlickerComponentName.IME) - .then() - .isInvisible(FlickerComponentName.IME) + this.isVisible(ComponentNameMatcher.IME).then().isInvisible(ComponentNameMatcher.IME) } } -fun FlickerTestParameter.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) { +fun FlickerTest.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) { if (rotatesScreen) { assertWm { - this.isNonAppWindowVisible(FlickerComponentName.IME) + this.isNonAppWindowVisible(ComponentNameMatcher.IME) .then() - .isNonAppWindowInvisible(FlickerComponentName.IME) + .isNonAppWindowInvisible(ComponentNameMatcher.IME) .then() - .isNonAppWindowVisible(FlickerComponentName.IME) + .isNonAppWindowVisible(ComponentNameMatcher.IME) } } else { - assertWm { - this.isNonAppWindowVisible(FlickerComponentName.IME) - } + assertWm { this.isNonAppWindowVisible(ComponentNameMatcher.IME) } } } -fun FlickerTestParameter.imeWindowBecomesVisible() { +fun FlickerTest.imeWindowBecomesVisible() { assertWm { - this.isNonAppWindowInvisible(FlickerComponentName.IME) + this.isNonAppWindowInvisible(ComponentNameMatcher.IME) .then() - .isNonAppWindowVisible(FlickerComponentName.IME) + .isNonAppWindowVisible(ComponentNameMatcher.IME) } } -fun FlickerTestParameter.imeWindowBecomesInvisible() { +fun FlickerTest.imeWindowBecomesInvisible() { assertWm { - this.isNonAppWindowVisible(FlickerComponentName.IME) + this.isNonAppWindowVisible(ComponentNameMatcher.IME) .then() - .isNonAppWindowInvisible(FlickerComponentName.IME) + .isNonAppWindowInvisible(ComponentNameMatcher.IME) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt deleted file mode 100644 index 2f8f9441a7b9..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2021 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.android.server.wm.flicker.ime - -import android.view.WindowInsets.Type.ime -import android.view.WindowInsets.Type.navigationBars -import android.view.WindowInsets.Type.statusBars - -import android.app.Instrumentation -import android.platform.test.annotations.Presubmit -import android.view.Surface -import android.view.WindowManagerPolicyConstants -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue - -/** - * Test IME snapshot mechanism won't apply when transitioning from non-IME focused dialog activity. - * To run this test: `atest FlickerTests:LaunchAppShowImeAndDialogThemeAppTest` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -class LaunchAppShowImeAndDialogThemeAppTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - eachRun { - testApp.launchViaIntent(wmHelper) - wmHelper.waitImeShown() - testApp.startDialogThemedActivity(wmHelper) - // Verify IME insets isn't visible on dialog since it's non-IME focusable window - assertFalse(testApp.getInsetsVisibleFromDialog(ime())) - assertTrue(testApp.getInsetsVisibleFromDialog(statusBars())) - assertTrue(testApp.getInsetsVisibleFromDialog(navigationBars())) - } - } - teardown { - eachRun { - testApp.exit() - } - } - transitions { - testApp.dismissDialog(wmHelper) - } - } - } - - /** - * Checks that [FlickerComponentName.IME] layer becomes visible during the transition - */ - @FlakyTest(bugId = 215884488) - @Test - fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible() - - /** - * Checks that [FlickerComponentName.IME] layer is visible at the end of the transition - */ - @FlakyTest(bugId = 227142436) - @Test - fun imeLayerExistsEnd() { - testSpec.assertLayersEnd { - this.isVisible(FlickerComponentName.IME) - } - } - - /** - * Checks that [FlickerComponentName.IME_SNAPSHOT] layer is invisible always. - */ - @Presubmit - @Test - fun imeSnapshotNotVisible() { - testSpec.assertLayers { - this.isInvisible(FlickerComponentName.IME_SNAPSHOT) - } - } - - companion object { - /** - * Creates the test configurations. - * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. - */ - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 3, - supportedRotations = listOf(Surface.ROTATION_0), - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ) - ) - } - } -} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt deleted file mode 100644 index b897ca2a9c15..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2021 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.android.server.wm.flicker.ime - -import android.app.Instrumentation -import android.platform.test.annotations.Presubmit -import android.view.Surface -import android.view.WindowManagerPolicyConstants -import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Launch an app that automatically displays the IME - * - * To run this test: `atest FlickerTests:LaunchAppShowImeOnStartTest` - * - * Actions: - * Make sure no apps are running on the device - * Launch an app [testApp] that automatically displays IME and wait animation to complete - * - * To run only the presubmit assertions add: `-- - * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest - * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` - * - * To run only the postsubmit assertions add: `-- - * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest - * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` - * - * To run only the flaky assertions add: `-- - * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` - * - * Notes: - * 1. Some default assertions (e.g., nav bar, status bar and screen covered) - * are inherited [CloseAppTransition] - * 2. Part of the test setup occurs automatically via - * [com.android.server.wm.flicker.TransitionRunnerWithRules], - * including configuring navigation mode, initial orientation and ensuring no - * apps are running before setup - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) - private val initializeApp = ImeStateInitializeHelper(instrumentation) - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - eachRun { - initializeApp.launchViaIntent() - this.setRotation(testSpec.startRotation) - } - } - teardown { - eachRun { - initializeApp.exit() - testApp.exit() - } - } - transitions { - testApp.launchViaIntent(wmHelper) - wmHelper.waitImeShown() - } - } - } - - /** - * Checks that [FlickerComponentName.IME] window becomes visible during the transition - */ - @Presubmit - @Test - fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible() - - /** - * Checks that [FlickerComponentName.IME] layer becomes visible during the transition - */ - @Presubmit - @Test - fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible() - - /** - * Checks that [FlickerComponentName.IME] layer is invisible at the start of the transition - */ - @Presubmit - @Test - fun imeLayerNotExistsStart() { - testSpec.assertLayersStart { - this.isInvisible(FlickerComponentName.IME) - } - } - - /** - * Checks that [FlickerComponentName.IME] layer is visible at the end of the transition - */ - @Presubmit - @Test - fun imeLayerExistsEnd() { - testSpec.assertLayersEnd { - this.isVisible(FlickerComponentName.IME) - } - } - - companion object { - /** - * Creates the test configurations. - * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. - */ - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 3, - supportedRotations = listOf(Surface.ROTATION_0), - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ) - ) - } - } -}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt deleted file mode 100644 index 972918e28fa7..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2021 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.android.server.wm.flicker.ime - -import android.app.Instrumentation -import android.platform.test.annotations.Presubmit -import android.view.Surface -import android.view.WindowManagerPolicyConstants -import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.* -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.flicker.helpers.SimpleAppHelper -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Unlike {@link OpenImeWindowTest} testing IME window opening transitions, this test also verify - * there is no flickering when back to the simple activity without requesting IME to show. - * - * To run this test: `atest FlickerTests:OpenImeWindowAndCloseTest` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class OpenImeWindowAndCloseTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val simpleApp = SimpleAppHelper(instrumentation) - private val testApp = ImeAppHelper(instrumentation) - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - eachRun { - simpleApp.launchViaIntent(wmHelper) - testApp.launchViaIntent(wmHelper) - testApp.openIME(device, wmHelper) - } - } - transitions { - testApp.finishActivity(device, wmHelper) - } - teardown { - test { - simpleApp.exit() - } - } - } - } - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Presubmit - @Test - fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible() - - @Presubmit - @Test - fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() - - @Presubmit - @Test - fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() - - @Presubmit - @Test - fun entireScreenCovered() = testSpec.entireScreenCovered() - - @Presubmit - @Test - fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible() - - @Presubmit - @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() { - testSpec.assertLayers { - this.visibleLayersShownMoreThanOneConsecutiveEntry() - } - } - - @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry() - } - } - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 3, - supportedRotations = listOf(Surface.ROTATION_0), - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ) - ) - } - } -}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt deleted file mode 100644 index 78aea1f1fb17..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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.android.server.wm.flicker.ime - -import android.app.Instrumentation -import android.platform.test.annotations.Presubmit -import android.platform.test.annotations.RequiresDevice -import android.view.Surface -import android.view.WindowManagerPolicyConstants -import androidx.test.filters.FlakyTest -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper -import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test IME window layer will become visible when switching from the fixed orientation activity. - * To run this test: `atest FlickerTests:OpenImeWindowFromFixedOrientationAppTest` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class OpenImeWindowFromFixedOrientationAppTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val fixedOrientationApp = FixedOrientationAppHelper(instrumentation) - private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - eachRun { - fixedOrientationApp.launchViaIntent(wmHelper) - this.setRotation(Surface.ROTATION_90) - } - } - transitions { - imeTestApp.launchViaIntent(wmHelper) - } - teardown { - test { - fixedOrientationApp.exit(wmHelper) - } - } - } - } - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Presubmit - @Test - fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible() - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - @FlakyTest(bugId = 206753786) - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - - @Presubmit - @Test - fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible() - - companion object { - /** - * Creates the test configurations. - * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. - */ - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 3, - supportedRotations = listOf(Surface.ROTATION_0), - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ) - ) - } - } -}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt deleted file mode 100644 index 8fcb4b7c03f1..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2020 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.android.server.wm.flicker.ime - -import android.app.Instrumentation -import android.platform.test.annotations.Presubmit -import android.view.Surface -import android.view.WindowManagerPolicyConstants -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.helpers.ImeAppHelper -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test IME window opening transitions. - * To run this test: `atest FlickerTests:OpenImeWindowTest` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = ImeAppHelper(instrumentation) - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - test { - testApp.launchViaIntent(wmHelper) - } - } - transitions { - testApp.openIME(device, wmHelper) - } - teardown { - eachRun { - testApp.closeIME(device, wmHelper) - } - test { - testApp.exit() - } - } - } - } - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Presubmit - @Test - fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible() - - @Presubmit - @Test - fun appWindowAlwaysVisibleOnTop() { - testSpec.assertWm { - this.isAppWindowOnTop(testApp.component) - } - } - - @Presubmit - @Test - fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() - - @Presubmit - @Test - fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() - - @Presubmit - @Test - fun entireScreenCovered() = testSpec.entireScreenCovered() - - @Presubmit - @Test - fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible() - - @Presubmit - @Test - fun layerAlwaysVisible() { - testSpec.assertLayers { - this.isVisible(testApp.component) - } - } - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - @FlakyTest(bugId = 206753786) - @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - - @Presubmit - @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() { - testSpec.assertLayers { - this.visibleLayersShownMoreThanOneConsecutiveEntry() - } - } - - @Presubmit - @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry() - } - } - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 3, - supportedRotations = listOf(Surface.ROTATION_0), - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ) - ) - } - } -} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt new file mode 100644 index 000000000000..19bbf0cb92b0 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.ime + +import android.platform.test.annotations.Presubmit +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.flicker.subject.region.RegionSubject +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.helpers.WindowUtils +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest +import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME window shown on the app with fixing portrait orientation. To run this test: `atest + * FlickerTests:OpenImeWindowToFixedPortraitAppTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenImeWindowToFixedPortraitAppTest(flicker: FlickerTest) : BaseTest(flicker) { + private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation) + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + testApp.launchViaIntent(wmHelper) + testApp.openIME(wmHelper) + // Enable letterbox when the app calls setRequestedOrientation + device.executeShellCommand("cmd window set-ignore-orientation-request true") + } + transitions { testApp.toggleFixPortraitOrientation(wmHelper) } + teardown { + testApp.exit() + device.executeShellCommand("cmd window set-ignore-orientation-request false") + } + } + + @Presubmit + @Test + fun imeLayerVisibleStart() { + flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) } + } + + @Presubmit + @Test + fun imeLayerExistsEnd() { + flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) } + } + + @Presubmit + @Test + fun imeLayerVisibleRegionKeepsTheSame() { + var imeLayerVisibleRegionBeforeTransition: RegionSubject? = null + flicker.assertLayersStart { + imeLayerVisibleRegionBeforeTransition = this.visibleRegion(ComponentNameMatcher.IME) + } + flicker.assertLayersEnd { + this.visibleRegion(ComponentNameMatcher.IME) + .coversExactly(imeLayerVisibleRegionBeforeTransition!!.region) + } + } + + @Presubmit + @Test + fun appWindowWithLetterboxCoversExactlyOnScreen() { + val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) + flicker.assertLayersEnd { + this.visibleRegion(testApp.or(ComponentNameMatcher.LETTERBOX)) + .coversExactly(displayBounds) + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = + listOf( + Rotation.ROTATION_90, + ), + supportedNavigationModes = listOf(NavBar.MODE_3BUTTON, NavBar.MODE_GESTURAL) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt deleted file mode 100644 index 0454ca0a607e..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt +++ /dev/null @@ -1,238 +0,0 @@ -/* - * 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.android.server.wm.flicker.ime - -import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit -import android.platform.test.annotations.Presubmit -import android.platform.test.annotations.RequiresDevice -import android.view.Surface -import android.view.WindowManagerPolicyConstants -import androidx.test.filters.FlakyTest -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.common.WindowManagerConditionsFactory -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import org.junit.Assume.assumeTrue -import org.junit.Assume.assumeFalse -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test IME window layer will be associated with the app task when going to the overview screen. - * To run this test: `atest FlickerTests:OpenImeWindowToOverViewTest` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group4 -class OpenImeWindowToOverViewTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) - private val statusBarInvisible = WindowManagerConditionsFactory.isStatusBarVisible().negate() - private val navBarInvisible = WindowManagerConditionsFactory.isNavBarVisible().negate() - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - eachRun { - imeTestApp.launchViaIntent(wmHelper) - } - } - transitions { - device.pressRecentApps() - waitForRecentsActivityVisible(wmHelper) - waitNavStatusBarVisibility(wmHelper) - } - teardown { - test { - device.pressHome() - imeTestApp.exit(wmHelper) - } - } - } - } - - /** - * The bars (including status bar and navigation bar) are expected to be hidden while - * entering overview in landscape if launcher is set to portrait only. Because - * "showing portrait overview (launcher) in landscape display" is an intermediate state - * depending on the touch-up to decide the intention of gesture, the display may keep in - * landscape if return to app, or change to portrait if the gesture is to swipe-to-home. - * - * So instead of showing landscape bars with portrait launcher at the same time - * (especially return-to-home that launcher workspace becomes visible), hide the bars until - * leave overview to have cleaner appearance. - * - * b/227189877 - */ - private fun waitNavStatusBarVisibility(wmHelper: WindowManagerStateHelper) { - when { - testSpec.isLandscapeOrSeascapeAtStart && !testSpec.isGesturalNavigation -> - wmHelper.waitFor(statusBarInvisible) - testSpec.isLandscapeOrSeascapeAtStart -> - wmHelper.waitFor(statusBarInvisible, navBarInvisible) - } - } - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Presubmit - @Test - fun imeWindowIsAlwaysVisible() { - testSpec.imeWindowIsAlwaysVisible() - } - - @Presubmit - @Test - fun navBarLayerIsVisible3Button() { - assumeFalse(testSpec.isGesturalNavigation) - testSpec.navBarLayerIsVisible() - } - - /** - * Bars are expected to be hidden while entering overview in landscape (b/227189877) - */ - @Presubmit - @Test - fun navBarLayerIsVisibleInPortraitGestural() { - assumeFalse(testSpec.isLandscapeOrSeascapeAtStart) - assumeTrue(testSpec.isGesturalNavigation) - testSpec.navBarLayerIsVisible() - } - - /** - * In the legacy transitions, the nav bar is not marked as invisible. - * In the new transitions this is fixed and the nav bar shows as invisible - */ - @Postsubmit - @Test - fun navBarLayerIsInvisibleInLandscapeGestural() { - assumeTrue(testSpec.isLandscapeOrSeascapeAtStart) - assumeTrue(testSpec.isGesturalNavigation) - assumeTrue(isShellTransitionsEnabled) - testSpec.assertLayersStart { - this.isVisible(FlickerComponentName.NAV_BAR) - } - testSpec.assertLayersEnd { - this.isInvisible(FlickerComponentName.NAV_BAR) - } - } - - @Postsubmit - @Test - fun statusBarLayerIsVisibleInPortrait() { - assumeFalse(testSpec.isLandscapeOrSeascapeAtStart) - testSpec.statusBarLayerIsVisible() - } - - @Presubmit - @Test - fun statusBarLayerIsInvisibleInLandscape() { - assumeTrue(testSpec.isLandscapeOrSeascapeAtStart) - testSpec.assertLayersStart { - this.isVisible(FlickerComponentName.STATUS_BAR) - } - testSpec.assertLayersEnd { - this.isInvisible(FlickerComponentName.STATUS_BAR) - } - } - - @FlakyTest(bugId = 228011606) - @Test - fun imeLayerIsVisibleAndAssociatedWithAppWidow() { - testSpec.assertLayersStart { - isVisible(FlickerComponentName.IME).visibleRegion(FlickerComponentName.IME) - .coversAtMost(isVisible(imeTestApp.component) - .visibleRegion(imeTestApp.component).region) - } - testSpec.assertLayers { - this.invoke("imeLayerIsVisibleAndAlignAppWidow") { - val imeVisibleRegion = it.visibleRegion(FlickerComponentName.IME) - val appVisibleRegion = it.visibleRegion(imeTestApp.component) - if (imeVisibleRegion.region.isNotEmpty) { - it.isVisible(FlickerComponentName.IME) - imeVisibleRegion.coversAtMost(appVisibleRegion.region) - } - } - } - } - - private fun waitForRecentsActivityVisible( - wmHelper: WindowManagerStateHelper - ) { - val waitMsg = "state of Recents activity to be visible" - require( - wmHelper.waitFor(waitMsg) { - it.wmState.homeActivity?.let { act -> - it.wmState.isActivityVisible(act.name) - } == true || - it.wmState.recentsActivity?.let { act -> - it.wmState.isActivityVisible(act.name) - } == true - } - ) { "Recents activity should be visible" } - wmHelper.waitForAppTransitionIdle() - // Ensure WindowManagerService wait until all animations have completed - instrumentation.uiAutomation.syncInputTransactions() - } - - companion object { - /** - * Creates the test configurations. - * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. - */ - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 1, - supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90), - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ) - ) - } - } -}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt deleted file mode 100644 index e7a33543a86b..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2020 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.android.server.wm.flicker.ime - -import android.app.Instrumentation -import android.platform.test.annotations.Presubmit -import android.view.Display -import android.view.Surface -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.LAUNCHER_COMPONENT -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.flicker.helpers.reopenAppFromOverview -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.ConditionList -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.common.WindowManagerConditionsFactory -import org.junit.Assume.assumeFalse -import org.junit.Assume.assumeTrue -import org.junit.Before -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test IME window opening transitions. - * To run this test: `atest FlickerTests:ReOpenImeWindowTest` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -open class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) - - private val waitConditionSetup = ConditionList(listOf( - WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY), - WindowManagerConditionsFactory.hasLayersAnimating().negate(), - WindowManagerConditionsFactory.isHomeActivityVisible() - )) - - @Before - open fun before() { - assumeFalse(isShellTransitionsEnabled) - } - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - test { - testApp.launchViaIntent(wmHelper) - testApp.openIME(device, wmHelper) - } - eachRun { - device.pressRecentApps() - wmHelper.waitFor(waitConditionSetup) - this.setRotation(testSpec.startRotation) - } - } - transitions { - device.reopenAppFromOverview(wmHelper) - wmHelper.waitImeShown() - } - teardown { - test { - testApp.exit() - } - } - } - } - - @Presubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Presubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - @Presubmit - @Test - fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - val component = FlickerComponentName("", "RecentTaskScreenshotSurface") - testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry( - ignoreWindows = listOf(FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT, - component) - ) - } - } - - @Presubmit - @Test - fun launcherWindowBecomesInvisible() { - testSpec.assertWm { - this.isAppWindowVisible(LAUNCHER_COMPONENT) - .then() - .isAppWindowInvisible(LAUNCHER_COMPONENT) - } - } - - @Presubmit - @Test - fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled) - - @Presubmit - @Test - fun imeAppWindowVisibilityLegacy() { - assumeFalse(isShellTransitionsEnabled) - // the app starts visible in live tile, and stays visible for the duration of entering - // and exiting overview. However, legacy transitions seem to have a bug which causes - // everything to restart during the test, so expect the app to disappear and come back. - // Since we log 1x per frame, sometimes the activity visibility and the app visibility - // are updated together, sometimes not, thus ignore activity check at the start - testSpec.assertWm { - this.isAppWindowVisible(testApp.component) - .then() - .isAppWindowInvisible(testApp.component) - .then() - .isAppWindowVisible(testApp.component) - } - } - - @FlakyTest(bugId = 204570898) - @Test - fun imeAppWindowVisibility() { - assumeTrue(isShellTransitionsEnabled) - // the app starts visible in live tile, and stays visible for the duration of entering - // and exiting overview. Since we log 1x per frame, sometimes the activity visibility - // and the app visibility are updated together, sometimes not, thus ignore activity - // check at the start - testSpec.assertWm { - this.isAppWindowVisible(testApp.component) - } - } - - @Presubmit - @Test - // During testing the launcher is always in portrait mode - fun entireScreenCovered() = testSpec.entireScreenCovered() - - @Presubmit - @Test - fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() - - @Presubmit - @Test - fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() - - @Presubmit - @Test - fun imeLayerIsBecomesVisibleLegacy() { - assumeFalse(isShellTransitionsEnabled) - testSpec.assertLayers { - this.isVisible(FlickerComponentName.IME) - .then() - .isInvisible(FlickerComponentName.IME) - .then() - .isVisible(FlickerComponentName.IME) - } - } - - @FlakyTest(bugId = 204570898) - @Test - fun imeLayerIsBecomesVisible() { - assumeTrue(isShellTransitionsEnabled) - testSpec.assertLayers { - this.isVisible(FlickerComponentName.IME) - } - } - - @Presubmit - @Test - fun appLayerReplacesLauncher() { - testSpec.assertLayers { - this.isVisible(LAUNCHER_COMPONENT) - .then() - .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isVisible(testApp.component) - } - } - - @Presubmit - @Test - fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - @FlakyTest(bugId = 206753786) - @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - - @Presubmit - @Test - fun visibleLayersShownMoreThanOneConsecutiveEntry() { - // depends on how much of the animation transactions are sent to SF at once - // sometimes this layer appears for 2-3 frames, sometimes for only 1 - val recentTaskComponent = FlickerComponentName("", "RecentTaskScreenshotSurface") - testSpec.assertLayers { - this.visibleLayersShownMoreThanOneConsecutiveEntry( - listOf(FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT, recentTaskComponent) - ) - } - } - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 3, - supportedRotations = listOf(Surface.ROTATION_0) - ) - } - } -} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppCfArmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppCfArmTest.kt new file mode 100644 index 000000000000..03f21f95e61e --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppCfArmTest.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.ime + +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class ShowImeOnAppStartWhenLaunchingAppCfArmTest(flicker: FlickerTest) : + ShowImeOnAppStartWhenLaunchingAppTest(flicker) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt new file mode 100644 index 000000000000..496165ab5b09 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt @@ -0,0 +1,143 @@ +/* + * 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.android.server.wm.flicker.ime + +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.platform.test.annotations.Postsubmit +import android.tools.common.Timestamp +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.common.flicker.subject.exceptions.ExceptionMessageBuilder +import android.tools.common.flicker.subject.exceptions.InvalidPropertyException +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest +import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper +import com.android.server.wm.flicker.helpers.setRotation +import com.android.server.wm.flicker.snapshotStartingWindowLayerCoversExactlyOnApp +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME window layer will become visible when switching from the fixed orientation activity + * (e.g. Launcher activity). To run this test: `atest + * FlickerTests:ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker: FlickerTest) : + BaseTest(flicker) { + private val imeTestApp = + ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation) + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + + // Launch the activity with expecting IME will be shown. + imeTestApp.launchViaIntent(wmHelper) + + // Swiping out the IME activity to home. + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + transitions { + // Bring the existing IME activity to the front in landscape mode device rotation. + setRotation(Rotation.ROTATION_90) + imeTestApp.launchViaIntent(wmHelper) + } + teardown { imeTestApp.exit(wmHelper) } + } + + @Presubmit @Test fun imeWindowBecomesVisible() = flicker.imeWindowBecomesVisible() + + @Presubmit @Test fun imeLayerBecomesVisible() = flicker.imeLayerBecomesVisible() + + @Presubmit + @Test + fun snapshotStartingWindowLayerCoversExactlyOnApp() { + flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp) + } + + @Postsubmit + @Test + fun imeLayerAlphaOneAfterSnapshotStartingWindowRemoval() { + // Check if the snapshot appeared during the trace + var imeSnapshotRemovedTimestamp: Timestamp? = null + + val layerTrace = flicker.reader.readLayersTrace() + val layerTraceEntries = layerTrace?.entries?.toList() ?: emptyList() + + layerTraceEntries.zipWithNext { prev, next -> + val prevSnapshotLayerVisible = + ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(prev.visibleLayers) + val nextSnapshotLayerVisible = + ComponentNameMatcher.SNAPSHOT.layerMatchesAnyOf(next.visibleLayers) + + if (imeSnapshotRemovedTimestamp == null && + (prevSnapshotLayerVisible && !nextSnapshotLayerVisible)) { + imeSnapshotRemovedTimestamp = next.timestamp + } + } + + // if so, make an assertion + imeSnapshotRemovedTimestamp?.let { timestamp -> + val stateAfterSnapshot = layerTrace?.getEntryAt(timestamp) + ?: error("State not found for $timestamp") + + val imeLayers = ComponentNameMatcher.IME + .filterLayers(stateAfterSnapshot.visibleLayers.toList()) + + require(imeLayers.isNotEmpty()) { "IME layer not found" } + if (imeLayers.any { it.color.a != 1.0f }) { + val errorMsgBuilder = ExceptionMessageBuilder() + .setTimestamp(timestamp) + .forInvalidProperty("IME layer alpha") + .setExpected("is 1.0") + .setActual("not 1.0") + .addExtraDescription("Filter", + ComponentNameMatcher.IME.toLayerIdentifier()) + throw InvalidPropertyException(errorMsgBuilder) + } + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_90) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt new file mode 100644 index 000000000000..3aca2a07f7cd --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.ime + +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTestCfArm(flicker: FlickerTest) : + ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest(flicker) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt new file mode 100644 index 000000000000..f64ad53bf2f7 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2020 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.android.server.wm.flicker.ime + +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.helpers.reopenAppFromOverview +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest +import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper +import com.android.server.wm.flicker.helpers.setRotation +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME window opening transitions. To run this test: `atest FlickerTests:ReOpenImeWindowTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class ShowImeOnAppStartWhenLaunchingAppFromOverviewTest(flicker: FlickerTest) : + BaseTest(flicker) { + private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation) + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.workspace.switchToOverview().dismissAllTasks() + testApp.launchViaIntent(wmHelper) + testApp.openIME(wmHelper) + this.setRotation(flicker.scenario.startRotation) + device.pressRecentApps() + wmHelper.StateSyncBuilder().withRecentsActivityVisible().waitForAndVerify() + } + transitions { + device.reopenAppFromOverview(wmHelper) + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).withImeShown().waitForAndVerify() + } + teardown { testApp.exit(wmHelper) } + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + // depends on how much of the animation transactions are sent to SF at once + // sometimes this layer appears for 2-3 frames, sometimes for only 1 + val recentTaskComponent = ComponentNameMatcher("", "RecentTaskScreenshotSurface") + flicker.assertLayers { + this.visibleLayersShownMoreThanOneConsecutiveEntry( + listOf( + ComponentNameMatcher.SPLASH_SCREEN, + ComponentNameMatcher.SNAPSHOT, + recentTaskComponent + ) + ) + } + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + val component = ComponentNameMatcher("", "RecentTaskScreenshotSurface") + flicker.assertWm { + this.visibleWindowsShownMoreThanOneConsecutiveEntry( + ignoreWindows = + listOf( + ComponentNameMatcher.SPLASH_SCREEN, + ComponentNameMatcher.SNAPSHOT, + component + ) + ) + } + } + + @Presubmit + @Test + fun launcherWindowBecomesInvisible() { + flicker.assertWm { + this.isAppWindowVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isAppWindowInvisible(ComponentNameMatcher.LAUNCHER) + } + } + + @Presubmit @Test fun imeWindowIsAlwaysVisible() = flicker.imeWindowIsAlwaysVisible() + + @Presubmit + @Test + fun imeAppWindowIsAlwaysVisible() { + // the app starts visible in live tile, and stays visible for the duration of entering + // and exiting overview. Since we log 1x per frame, sometimes the activity visibility + // and the app visibility are updated together, sometimes not, thus ignore activity + // check at the start + flicker.assertWm { this.isAppWindowVisible(testApp) } + } + + @Presubmit + @Test + fun imeLayerBecomesVisible() { + flicker.assertLayers { this.isVisible(ComponentNameMatcher.IME) } + } + + @Presubmit + @Test + fun appLayerReplacesLauncher() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(testApp) + } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_0) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt new file mode 100644 index 000000000000..e1aa4182c331 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.ime + +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class ShowImeOnAppStartWhenLaunchingAppFromOverviewTestCfArm(flicker: FlickerTest) : + ShowImeOnAppStartWhenLaunchingAppFromOverviewTest(flicker) { + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_0) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt new file mode 100644 index 000000000000..11bc7b9f29c0 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2021 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.android.server.wm.flicker.ime + +import android.platform.test.annotations.Presubmit +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest +import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.setRotation +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME windows switching with 2-Buttons or gestural navigation. To run this test: `atest + * FlickerTests:SwitchImeWindowsFromGestureNavTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Presubmit +open class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker: FlickerTest) : + BaseTest(flicker) { + private val testApp = SimpleAppHelper(instrumentation) + private val imeTestApp = + ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation) + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + tapl.setIgnoreTaskbarVisibility(true) + this.setRotation(flicker.scenario.startRotation) + testApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() + + imeTestApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withFullScreenApp(imeTestApp).waitForAndVerify() + + imeTestApp.openIME(wmHelper) + } + teardown { + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + testApp.exit(wmHelper) + imeTestApp.exit(wmHelper) + } + transitions { + // [Step1]: Swipe right from testApp task to imeTestApp + createTag(TAG_IME_VISIBLE) + // Expect taskBar invisible when switching to imeTestApp on the large screen device. + tapl.launchedAppState.quickSwitchToPreviousApp() + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() + createTag(TAG_IME_INVISIBLE) + } + transitions { + // [Step2]: Swipe left to back to testApp task + // Expect taskBar visible when switching to testApp on the large screen device. + tapl.launchedAppState.quickSwitchToPreviousAppSwipeLeft() + wmHelper.StateSyncBuilder().withFullScreenApp(imeTestApp).waitForAndVerify() + } + } + /** {@inheritDoc} */ + @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered() + + @Presubmit + @Test + fun imeAppWindowVisibility() { + flicker.assertWm { + isAppWindowVisible(imeTestApp) + .then() + .isAppSnapshotStartingWindowVisibleFor(testApp, isOptional = true) + .then() + .isAppWindowVisible(testApp) + .then() + .isAppSnapshotStartingWindowVisibleFor(imeTestApp, isOptional = true) + .then() + .isAppWindowVisible(imeTestApp) + } + } + + @Presubmit + @Test + open fun imeLayerIsVisibleWhenSwitchingToImeApp() { + flicker.assertLayersStart { isVisible(ComponentNameMatcher.IME) } + flicker.assertLayersTag(TAG_IME_VISIBLE) { isVisible(ComponentNameMatcher.IME) } + flicker.assertLayersEnd { isVisible(ComponentNameMatcher.IME) } + } + + @Presubmit + @Test + fun imeLayerIsInvisibleWhenSwitchingToTestApp() { + flicker.assertLayersTag(TAG_IME_INVISIBLE) { isInvisible(ComponentNameMatcher.IME) } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL), + supportedRotations = listOf(Rotation.ROTATION_0) + ) + } + + private const val TAG_IME_VISIBLE = "imeVisible" + private const val TAG_IME_INVISIBLE = "imeInVisible" + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt new file mode 100644 index 000000000000..0f57467c0449 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.ime + +import android.platform.test.annotations.Presubmit +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@Presubmit +open class ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTestCfArm(flicker: FlickerTest) : + ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest(flicker) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt new file mode 100644 index 000000000000..46967db55086 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2021 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.android.server.wm.flicker.ime + +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest +import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper +import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper +import com.android.server.wm.flicker.helpers.setRotation +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Launch an app that automatically displays the IME + * + * To run this test: `atest FlickerTests:LaunchAppShowImeOnStartTest` + * + * Actions: + * ``` + * Make sure no apps are running on the device + * Launch an app [testApp] that automatically displays IME and wait animation to complete + * ``` + * + * To run only the presubmit assertions add: `-- + * + * ``` + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` + * ``` + * + * To run only the postsubmit assertions add: `-- + * + * ``` + * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest + * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` + * ``` + * + * To run only the flaky assertions add: `-- + * + * ``` + * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` + * ``` + * + * Notes: + * ``` + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [CloseAppTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup + * ``` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class ShowImeOnAppStartWhenLaunchingAppTest(flicker: FlickerTest) : BaseTest(flicker) { + private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation) + private val initializeApp = ImeStateInitializeHelper(instrumentation) + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + initializeApp.launchViaIntent(wmHelper) + this.setRotation(flicker.scenario.startRotation) + } + teardown { + initializeApp.exit(wmHelper) + testApp.exit(wmHelper) + } + transitions { + testApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify() + } + } + + /** Checks that [ComponentNameMatcher.IME] window becomes visible during the transition */ + @Presubmit @Test fun imeWindowBecomesVisible() = flicker.imeWindowBecomesVisible() + + /** Checks that [ComponentNameMatcher.IME] layer becomes visible during the transition */ + @Presubmit @Test fun imeLayerBecomesVisible() = flicker.imeLayerBecomesVisible() + + /** Checks that [ComponentNameMatcher.IME] layer is invisible at the start of the transition */ + @Presubmit + @Test + fun imeLayerNotExistsStart() { + flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.IME) } + } + + /** Checks that [ComponentNameMatcher.IME] layer is visible at the end of the transition */ + @Presubmit + @Test + fun imeLayerExistsEnd() { + flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_0) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt new file mode 100644 index 000000000000..827110dd4572 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2020 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.android.server.wm.flicker.ime + +import android.platform.test.annotations.PlatinumTest +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest +import com.android.server.wm.flicker.helpers.ImeAppHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** Test IME window opening transitions. To run this test: `atest FlickerTests:OpenImeWindowTest` */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class ShowImeWhenFocusingOnInputFieldTest(flicker: FlickerTest) : BaseTest(flicker) { + private val testApp = ImeAppHelper(instrumentation) + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { testApp.launchViaIntent(wmHelper) } + transitions { testApp.openIME(wmHelper) } + teardown { + testApp.closeIME(wmHelper) + testApp.exit(wmHelper) + } + } + + @Presubmit + @Test + @PlatinumTest(focusArea = "ime") + override fun cujCompleted() { + super.cujCompleted() + imeWindowBecomesVisible() + appWindowAlwaysVisibleOnTop() + layerAlwaysVisible() + } + + @Presubmit @Test fun imeWindowBecomesVisible() = flicker.imeWindowBecomesVisible() + + @Presubmit + @Test + fun appWindowAlwaysVisibleOnTop() { + flicker.assertWm { this.isAppWindowOnTop(testApp) } + } + + @Presubmit @Test fun imeLayerBecomesVisible() = flicker.imeLayerBecomesVisible() + + @Presubmit + @Test + fun layerAlwaysVisible() { + flicker.assertLayers { this.isVisible(testApp) } + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_0) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt new file mode 100644 index 000000000000..f5b22949e6b4 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTestCfArm.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.ime + +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class ShowImeWhenFocusingOnInputFieldTestCfArm(flicker: FlickerTest) : + ShowImeWhenFocusingOnInputFieldTest(flicker) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt new file mode 100644 index 000000000000..277b91558416 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2021 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.android.server.wm.flicker.ime + +import android.platform.test.annotations.Presubmit +import android.tools.common.Rotation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.view.WindowInsets.Type.ime +import android.view.WindowInsets.Type.navigationBars +import android.view.WindowInsets.Type.statusBars +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest +import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME snapshot mechanism won't apply when transitioning from non-IME focused dialog activity. + * To run this test: `atest FlickerTests:LaunchAppShowImeAndDialogThemeAppTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class ShowImeWhileDismissingThemedPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) { + private val testApp = ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation) + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + testApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify() + testApp.startDialogThemedActivity(wmHelper) + // Verify IME insets isn't visible on dialog since it's non-IME focusable window + assertFalse(testApp.getInsetsVisibleFromDialog(ime())) + assertTrue(testApp.getInsetsVisibleFromDialog(statusBars())) + assertTrue(testApp.getInsetsVisibleFromDialog(navigationBars())) + } + teardown { testApp.exit(wmHelper) } + transitions { testApp.dismissDialog(wmHelper) } + } + + /** Checks that [ComponentNameMatcher.IME] layer becomes visible during the transition */ + @Presubmit @Test fun imeWindowIsAlwaysVisible() = flicker.imeWindowIsAlwaysVisible() + + /** Checks that [ComponentNameMatcher.IME] layer is visible at the end of the transition */ + @Presubmit + @Test + fun imeLayerExistsEnd() { + flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) } + } + + /** Checks that [ComponentNameMatcher.IME_SNAPSHOT] layer is invisible always. */ + @Presubmit + @Test + fun imeSnapshotNotVisible() { + flicker.assertLayers { this.isInvisible(ComponentNameMatcher.IME_SNAPSHOT) } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_0) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt new file mode 100644 index 000000000000..8891d26c9e54 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTestCfArm.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.ime + +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class ShowImeWhileDismissingThemedPopupDialogTestCfArm(flicker: FlickerTest) : + ShowImeWhileDismissingThemedPopupDialogTest(flicker) { + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedRotations = listOf(Rotation.ROTATION_0) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt new file mode 100644 index 000000000000..9275d6a2f17f --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt @@ -0,0 +1,217 @@ +/* + * 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.android.server.wm.flicker.ime + +import android.platform.test.annotations.Presubmit +import android.tools.common.traces.ConditionsFactory +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest +import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper +import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd +import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd +import org.junit.Assume +import org.junit.FixMethodOrder +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME window layer will be associated with the app task when going to the overview screen. To + * run this test: `atest FlickerTests:OpenImeWindowToOverViewTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class ShowImeWhileEnteringOverviewTest(flicker: FlickerTest) : BaseTest(flicker) { + private val imeTestApp = + ImeShownOnAppStartHelper(instrumentation, flicker.scenario.startRotation) + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { imeTestApp.launchViaIntent(wmHelper) } + transitions { + device.pressRecentApps() + val builder = wmHelper.StateSyncBuilder().withRecentsActivityVisible() + waitNavStatusBarVisibility(builder) + builder.waitForAndVerify() + } + teardown { + device.pressHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + imeTestApp.exit(wmHelper) + } + } + + /** + * The bars (including [ComponentNameMatcher.STATUS_BAR] and [ComponentNameMatcher.NAV_BAR]) are + * expected to be hidden while entering overview in landscape if launcher is set to portrait + * only. Because "showing portrait overview (launcher) in landscape display" is an intermediate + * state depending on the touch-up to decide the intention of gesture, the display may keep in + * landscape if return to app, or change to portrait if the gesture is to swipe-to-home. + * + * So instead of showing landscape bars with portrait launcher at the same time (especially + * return-to-home that launcher workspace becomes visible), hide the bars until leave overview + * to have cleaner appearance. + * + * b/227189877 + */ + private fun waitNavStatusBarVisibility(stateSync: WindowManagerStateHelper.StateSyncBuilder) { + when { + flicker.scenario.isLandscapeOrSeascapeAtStart && !flicker.scenario.isTablet -> + stateSync.add(ConditionsFactory.isStatusBarVisible().negate()) + else -> stateSync.withNavOrTaskBarVisible().withStatusBarVisible() + } + } + + @Presubmit + @Test + fun imeWindowIsAlwaysVisible() { + flicker.imeWindowIsAlwaysVisible() + } + + @Presubmit + @Test + fun navBarLayerIsVisibleAtStartAndEnd3Button() { + Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeFalse(flicker.scenario.isGesturalNavigation) + flicker.navBarLayerIsVisibleAtStartAndEnd() + } + + /** + * In the legacy transitions, the nav bar is not marked as invisible. In the new transitions + * this is fixed and the nav bar shows as invisible + */ + @Presubmit + @Test + fun navBarLayerIsInvisibleInLandscapeGestural() { + Assume.assumeFalse(flicker.scenario.isTablet) + Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart) + Assume.assumeTrue(flicker.scenario.isGesturalNavigation) + flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) } + flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.NAV_BAR) } + } + + /** + * In the legacy transitions, the nav bar is not marked as invisible. In the new transitions + * this is fixed and the nav bar shows as invisible + */ + @Presubmit + @Test + fun statusBarLayerIsInvisibleInLandscapePhone() { + Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart) + Assume.assumeTrue(flicker.scenario.isGesturalNavigation) + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) } + flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) } + } + + /** + * In the legacy transitions, the nav bar is not marked as invisible. In the new transitions + * this is fixed and the nav bar shows as invisible + */ + @Presubmit + @Test + fun statusBarLayerIsInvisibleInLandscapeTablet() { + Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart) + Assume.assumeTrue(flicker.scenario.isGesturalNavigation) + Assume.assumeTrue(flicker.scenario.isTablet) + flicker.statusBarLayerIsVisibleAtStartAndEnd() + } + + /** {@inheritDoc} */ + @Test + @Ignore("Visibility changes depending on orientation and navigation mode") + override fun navBarLayerIsVisibleAtStartAndEnd() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Visibility changes depending on orientation and navigation mode") + override fun navBarLayerPositionAtStartAndEnd() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Visibility changes depending on orientation and navigation mode") + override fun statusBarLayerPositionAtStartAndEnd() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Visibility changes depending on orientation and navigation mode") + override fun statusBarLayerIsVisibleAtStartAndEnd() {} + + @Presubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + @Presubmit + @Test + fun statusBarLayerIsVisibleInPortrait() { + Assume.assumeFalse(flicker.scenario.isLandscapeOrSeascapeAtStart) + flicker.statusBarLayerIsVisibleAtStartAndEnd() + } + + @Presubmit + @Test + fun statusBarLayerIsInvisibleInLandscape() { + Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart) + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) } + flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) } + } + + @Presubmit + @Test + fun imeLayerIsVisibleAndAssociatedWithAppWidow() { + flicker.assertLayersStart { + isVisible(ComponentNameMatcher.IME) + .visibleRegion(ComponentNameMatcher.IME) + .coversAtMost(isVisible(imeTestApp).visibleRegion(imeTestApp).region) + } + flicker.assertLayers { + this.invoke("imeLayerIsVisibleAndAlignAppWidow") { + val imeVisibleRegion = it.visibleRegion(ComponentNameMatcher.IME) + val appVisibleRegion = it.visibleRegion(imeTestApp) + if (imeVisibleRegion.region.isNotEmpty) { + it.isVisible(ComponentNameMatcher.IME) + imeVisibleRegion.coversAtMost(appVisibleRegion.region) + } + } + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt new file mode 100644 index 000000000000..fc3971351db7 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTestCfArm.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.ime + +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class ShowImeWhileEnteringOverviewTestCfArm(flicker: FlickerTest) : + ShowImeWhileEnteringOverviewTest(flicker) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt deleted file mode 100644 index 4b268a871fa0..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (C) 2021 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.android.server.wm.flicker.ime - -import android.app.Instrumentation -import android.platform.test.annotations.Presubmit -import android.view.Display -import android.view.Surface -import android.view.WindowManagerPolicyConstants -import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper -import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.common.WindowManagerConditionsFactory -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper -import org.junit.Assume -import org.junit.Before - -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test IME windows switching with 2-Buttons or gestural navigation. - * To run this test: `atest FlickerTests:SwitchImeWindowsFromGestureNavTest` - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group4 -@Presubmit -open class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = SimpleAppHelper(instrumentation) - private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) - - @Before - open fun before() { - Assume.assumeFalse(isShellTransitionsEnabled) - } - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - eachRun { - this.setRotation(testSpec.startRotation) - testApp.launchViaIntent(wmHelper) - val testAppVisible = wmHelper.waitFor( - WindowManagerStateHelper.isAppFullScreen(testApp.component), - WindowManagerConditionsFactory.isAppTransitionIdle( - Display.DEFAULT_DISPLAY)) - require(testAppVisible) { - "Expected ${testApp.component.toWindowName()} to be visible" - } - - imeTestApp.launchViaIntent(wmHelper) - val imeAppVisible = wmHelper.waitFor( - WindowManagerStateHelper.isAppFullScreen(imeTestApp.component), - WindowManagerConditionsFactory.isAppTransitionIdle( - Display.DEFAULT_DISPLAY)) - require(imeAppVisible) { - "Expected ${imeTestApp.component.toWindowName()} to be visible" - } - - imeTestApp.openIME(device, wmHelper) - } - } - teardown { - eachRun { - device.pressHome() - wmHelper.waitForHomeActivityVisible() - testApp.exit() - imeTestApp.exit() - } - } - transitions { - // [Step1]: Swipe right from imeTestApp to testApp task - createTag(TAG_IME_VISIBLE) - val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) - device.swipe(0, displayBounds.bounds.height, - displayBounds.bounds.width, displayBounds.bounds.height, 50) - - wmHelper.waitForFullScreenApp(testApp.component) - wmHelper.waitForAppTransitionIdle() - createTag(TAG_IME_INVISIBLE) - } - transitions { - // [Step2]: Swipe left to back to imeTestApp task - val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) - device.swipe(displayBounds.bounds.width, displayBounds.bounds.height, - 0, displayBounds.bounds.height, 50) - wmHelper.waitForFullScreenApp(imeTestApp.component) - } - } - } - - @Test - fun imeAppWindowVisibility() { - testSpec.assertWm { - isAppWindowVisible(imeTestApp.component) - .then() - .isAppSnapshotStartingWindowVisibleFor(testApp.component, isOptional = true) - .then() - .isAppWindowVisible(testApp.component) - .then() - .isAppSnapshotStartingWindowVisibleFor(imeTestApp.component, isOptional = true) - .then() - .isAppWindowVisible(imeTestApp.component) - } - } - - @Test - fun navBarLayerIsVisibleAroundSwitching() { - testSpec.assertLayersStart { - isVisible(FlickerComponentName.NAV_BAR) - } - testSpec.assertLayersEnd { - isVisible(FlickerComponentName.NAV_BAR) - } - } - - @Test - fun statusBarLayerIsVisibleAroundSwitching() { - testSpec.assertLayersStart { - isVisible(FlickerComponentName.STATUS_BAR) - } - testSpec.assertLayersEnd { - isVisible(FlickerComponentName.STATUS_BAR) - } - } - - @Test - fun imeLayerIsVisibleWhenSwitchingToImeApp() { - testSpec.assertLayersStart { - isVisible(FlickerComponentName.IME) - } - testSpec.assertLayersTag(TAG_IME_VISIBLE) { - isVisible(FlickerComponentName.IME) - } - testSpec.assertLayersEnd { - isVisible(FlickerComponentName.IME) - } - } - - @Test - fun imeLayerIsInvisibleWhenSwitchingToTestApp() { - testSpec.assertLayersTag(TAG_IME_INVISIBLE) { - isInvisible(FlickerComponentName.IME) - } - } - - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 3, - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ), - supportedRotations = listOf(Surface.ROTATION_0) - ) - } - - private const val TAG_IME_VISIBLE = "imeVisible" - private const val TAG_IME_INVISIBLE = "imeInVisible" - } -} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt index cc808a0ce871..e6594c969373 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt @@ -16,24 +16,17 @@ package com.android.server.wm.flicker.launch -import android.app.Instrumentation import android.platform.test.annotations.Presubmit -import android.view.Display +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.LAUNCHER_COMPONENT -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.WindowManagerConditionsFactory -import com.android.server.wm.traces.parser.toFlickerComponent -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -46,69 +39,54 @@ import org.junit.runners.Parameterized * To run this test: `atest FlickerTests:ActivitiesTransitionTest` * * Actions: + * ``` * Launch an app * Launch a secondary activity within the app * Close the secondary activity back to the initial one + * ``` * * Notes: + * ``` * 1. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group4 -class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() +open class ActivitiesTransitionTest(flicker: FlickerTest) : BaseTest(flicker) { private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation) - /** - * Entry point for the test runner. It will use this method to initialize and cache - * flicker executions - */ - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - test { - testApp.launchViaIntent(wmHelper) - wmHelper.waitForFullScreenApp(testApp.component) - } - } - teardown { - test { - testApp.exit(wmHelper) - } - } - transitions { - testApp.openSecondActivity(device, wmHelper) - device.pressBack() - val firstActivityVisible = wmHelper.waitFor( - WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY), - WindowManagerStateHelper.isAppFullScreen(testApp.component)) - require(firstActivityVisible) { "Expected ${testApp.component} to be visible" } - } + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotation(flicker.scenario.startRotation.value) + testApp.launchViaIntent(wmHelper) + } + teardown { testApp.exit(wmHelper) } + transitions { + testApp.openSecondActivity(device, wmHelper) + tapl.pressBack() + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() } } /** - * Checks that the [ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME] activity is visible at - * the start of the transition, that - * [ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME] becomes visible during the - * transition, and that [ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME] is again visible - * at the end + * Checks that the [ActivityOptions.LaunchNewActivity] activity is visible at the start of the + * transition, that [ActivityOptions.SimpleActivity] becomes visible during the transition, and + * that [ActivityOptions.LaunchNewActivity] is again visible at the end */ @Presubmit @Test fun finishSubActivity() { - val buttonActivityComponent = ActivityOptions - .BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent() - val imeAutoFocusActivityComponent = ActivityOptions - .SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() - testSpec.assertWm { + val buttonActivityComponent = + ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent() + val imeAutoFocusActivityComponent = + ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent() + flicker.assertWm { this.isAppWindowOnTop(buttonActivityComponent) .then() .isAppWindowOnTop(imeAutoFocusActivityComponent) @@ -118,46 +96,36 @@ class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) { } /** - * Checks that all parts of the screen are covered during the transition - */ - @Presubmit - @Test - fun entireScreenCovered() = testSpec.entireScreenCovered() - - /** - * Checks that the [LAUNCHER_COMPONENT] window is not on top. The launcher cannot be + * Checks that the [ComponentNameMatcher.LAUNCHER] window is not on top. The launcher cannot be * asserted with `isAppWindowVisible` because it contains 2 windows with the exact same name, * and both are never simultaneously visible */ @Presubmit @Test fun launcherWindowNotOnTop() { - testSpec.assertWm { - this.isAppWindowNotOnTop(LAUNCHER_COMPONENT) - } + flicker.assertWm { this.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) } } /** - * Checks that the [LAUNCHER_COMPONENT] layer is never visible during the transition + * Checks that the [ComponentNameMatcher.LAUNCHER] layer is never visible during the transition */ @Presubmit @Test fun launcherLayerNotVisible() { - testSpec.assertLayers { this.isInvisible(LAUNCHER_COMPONENT) } + flicker.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) } } companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 3) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTestCfArm.kt new file mode 100644 index 000000000000..8b89a8b4c40d --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTestCfArm.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.launch + +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class ActivitiesTransitionTestCfArm(flicker: FlickerTest) : ActivitiesTransitionTest(flicker) { + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt new file mode 100644 index 000000000000..549183f407e2 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt @@ -0,0 +1,73 @@ +/* + * 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.android.server.wm.flicker.launch + +import android.tools.device.apphelpers.CameraAppHelper +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test launching an app after cold opening camera + * + * To run this test: `atest FlickerTests:OpenAppAfterCameraTest` + * + * Notes: Some default assertions are inherited [OpenAppTransition] + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class OpenAppAfterCameraTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) { + private val cameraApp = CameraAppHelper(instrumentation) + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { + tapl.setExpectedRotationCheckEnabled(false) + // 1. Open camera - cold -> close it first + cameraApp.exit(wmHelper) + cameraApp.launchViaIntent(wmHelper) + // Can't use TAPL due to Recents not showing in 3 Button Nav in full screen mode + device.pressHome() + tapl.getWorkspace() + } + teardown { testApp.exit(wmHelper) } + transitions { testApp.launchViaIntent(wmHelper) } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt new file mode 100644 index 000000000000..ac05c7687311 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.launch + +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenAppAfterCameraTestCfArm(flicker: FlickerTest) : OpenAppAfterCameraTest(flicker) { + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt new file mode 100644 index 000000000000..3a80c6649833 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt @@ -0,0 +1,219 @@ +/* + * 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.android.server.wm.flicker.launch + +import android.platform.test.annotations.FlakyTest +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule +import androidx.test.filters.RequiresDevice +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test cold launching an app from launcher + * + * To run this test: `atest FlickerTests:OpenAppColdFromIcon` + * + * Actions: + * ``` + * Make sure no apps are running on the device + * Launch an app [testApp] by clicking it's icon on all apps and wait animation to complete + * ``` + * + * Notes: + * ``` + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [OpenAppTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup + * ``` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) { + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { + if (flicker.scenario.isTablet) { + tapl.setExpectedRotation(flicker.scenario.startRotation.value) + } else { + tapl.setExpectedRotation(Rotation.ROTATION_0.value) + } + RemoveAllTasksButHomeRule.removeAllTasksButHome() + } + transitions { + tapl + .goHome() + .switchToAllApps() + .getAppIcon(testApp.launcherName) + .launch(testApp.`package`) + } + teardown { testApp.exit(wmHelper) } + } + + @FlakyTest(bugId = 240916028) + @Test + override fun focusChanges() { + super.focusChanges() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun appWindowReplacesLauncherAsTopWindow() { + super.appWindowReplacesLauncherAsTopWindow() + } + @FlakyTest(bugId = 240916028) + @Test + override fun appWindowAsTopWindowAtEnd() { + super.appWindowAsTopWindowAtEnd() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun appWindowBecomesTopWindow() { + super.appWindowBecomesTopWindow() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun appWindowBecomesVisible() { + super.appWindowBecomesVisible() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun appWindowIsTopWindowAtEnd() { + super.appWindowIsTopWindowAtEnd() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun appLayerBecomesVisible() { + super.appLayerBecomesVisible() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun appLayerReplacesLauncher() { + super.appLayerReplacesLauncher() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun cujCompleted() { + super.cujCompleted() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun entireScreenCovered() { + super.entireScreenCovered() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() { + super.navBarLayerIsVisibleAtStartAndEnd() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun navBarLayerPositionAtStartAndEnd() { + super.navBarLayerPositionAtStartAndEnd() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun navBarWindowIsAlwaysVisible() { + super.navBarWindowIsAlwaysVisible() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun navBarWindowIsVisibleAtStartAndEnd() { + super.navBarWindowIsVisibleAtStartAndEnd() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() { + super.statusBarLayerIsVisibleAtStartAndEnd() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun statusBarLayerPositionAtStartAndEnd() { + super.statusBarLayerPositionAtStartAndEnd() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun statusBarWindowIsAlwaysVisible() { + super.statusBarWindowIsAlwaysVisible() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() { + super.taskBarLayerIsVisibleAtStartAndEnd() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun taskBarWindowIsAlwaysVisible() { + super.taskBarWindowIsAlwaysVisible() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + + @FlakyTest(bugId = 240916028) + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt new file mode 100644 index 000000000000..d33a2724ca44 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.launch + +import android.platform.test.annotations.FlakyTest +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** Some assertions will fail because of b/264415996 */ +@FlickerServiceCompatible +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenAppColdFromIconCfArm(flicker: FlickerTest) : OpenAppColdFromIcon(flicker) { + @Test + @FlakyTest + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt index 2f6b8f008119..26f88d23cda0 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt @@ -16,16 +16,16 @@ package com.android.server.wm.flicker.launch -import androidx.test.filters.FlakyTest +import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit -import android.platform.test.annotations.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome +import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -38,97 +38,57 @@ import org.junit.runners.Parameterized * To run this test: `atest FlickerTests:OpenAppColdTest` * * Actions: + * ``` * Make sure no apps are running on the device * Launch an app [testApp] and wait animation to complete + * ``` * * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [OpenAppTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice +@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -open class OpenAppColdTest(testSpec: FlickerTestParameter) - : OpenAppFromLauncherTransition(testSpec) { - /** - * Defines the transition used to run the test - */ +open class OpenAppColdTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) { + /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { - eachRun { - removeAllTasksButHome() - this.setRotation(testSpec.startRotation) - } - } - teardown { - eachRun { - testApp.exit(wmHelper) - } - } - transitions { - testApp.launchViaIntent(wmHelper) - wmHelper.waitForFullScreenApp(testApp.component) + removeAllTasksButHome() + this.setRotation(flicker.scenario.startRotation) } + teardown { testApp.exit(wmHelper) } + transitions { testApp.launchViaIntent(wmHelper) } } /** {@inheritDoc} */ - @FlakyTest(bugId = 206753786) - @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - - /** {@inheritDoc} */ - @FlakyTest - @Test - override fun navBarLayerRotatesAndScales() { - super.navBarLayerRotatesAndScales() - } - - /** {@inheritDoc} */ - @Presubmit - @Test - override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher() + @Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher() - /** {@inheritDoc} */ - @Presubmit - @Test - override fun appWindowReplacesLauncherAsTopWindow() = - super.appWindowReplacesLauncherAsTopWindow() - - /** {@inheritDoc} */ - @Presubmit - @Test - override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - - /** {@inheritDoc} */ - @Presubmit - @Test - override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() - - /** {@inheritDoc} */ - @Presubmit + @Postsubmit @Test - override fun entireScreenCovered() = super.entireScreenCovered() + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests() + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt new file mode 100644 index 000000000000..d9a99dadbd3d --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.launch + +import android.platform.test.annotations.FlakyTest +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@FlickerServiceCompatible +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenAppColdTestCfArm(flicker: FlickerTest) : OpenAppColdTest(flicker) { + @FlakyTest(bugId = 273696733) + @Test + override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher() + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt index c6e92adce8c7..3d5a8e309b10 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt @@ -17,55 +17,56 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Presubmit -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.LAUNCHER_COMPONENT +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.legacy.FlickerTest import com.android.server.wm.flicker.replacesLayer -import com.android.server.wm.traces.common.FlickerComponentName import org.junit.Test -/** - * Base class for app launch tests - */ -abstract class OpenAppFromLauncherTransition(testSpec: FlickerTestParameter) - : OpenAppTransition(testSpec) { +/** Base class for app launch tests */ +abstract class OpenAppFromLauncherTransition(flicker: FlickerTest) : OpenAppTransition(flicker) { - /** - * Checks that the focus changes from the launcher to [testApp] - */ + /** Checks that the focus changes from the [ComponentNameMatcher.LAUNCHER] to [testApp] */ @Presubmit @Test open fun focusChanges() { - testSpec.assertEventLog { - this.focusChanges("NexusLauncherActivity", testApp.`package`) - } + flicker.assertEventLog { this.focusChanges("NexusLauncherActivity", testApp.`package`) } } /** - * Checks that [LAUNCHER_COMPONENT] layer is visible at the start of the transition, and - * is replaced by [testApp], which remains visible until the end + * Checks that [ComponentNameMatcher.LAUNCHER] layer is visible at the start of the transition, + * and is replaced by [testApp], which remains visible until the end */ open fun appLayerReplacesLauncher() { - testSpec.replacesLayer(LAUNCHER_COMPONENT, testApp.component, - ignoreEntriesWithRotationLayer = true, ignoreSnapshot = true, - ignoreSplashscreen = true) + flicker.replacesLayer( + ComponentNameMatcher.LAUNCHER, + testApp, + ignoreEntriesWithRotationLayer = true, + ignoreSnapshot = true, + ignoreSplashscreen = true + ) } /** - * Checks that [LAUNCHER_COMPONENT] window is visible at the start of the transition, and - * is replaced by a snapshot or splash screen (optional), and finally, is replaced by - * [testApp], which remains visible until the end + * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the + * transition, and is replaced by a [ComponentNameMatcher.SNAPSHOT] or + * [ComponentNameMatcher.SPLASH_SCREEN], or [testApp], which remains visible until the end */ @Presubmit @Test open fun appWindowReplacesLauncherAsTopWindow() { - testSpec.assertWm { - this.isAppWindowOnTop(LAUNCHER_COMPONENT) - .then() - .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isAppWindowOnTop(FlickerComponentName.SPLASH_SCREEN, isOptional = true) - .then() - .isAppWindowOnTop(testApp.component) + flicker.assertWm { + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) + .then() + .isAppWindowOnTop( + testApp.or(ComponentNameMatcher.SNAPSHOT).or(ComponentNameMatcher.SPLASH_SCREEN) + ) } } -}
\ No newline at end of file + + /** Checks that [testApp] window is the top window at the en dof the trace */ + @Presubmit + @Test + open fun appWindowAsTopWindowAtEnd() { + flicker.assertWmEnd { this.isAppWindowOnTop(testApp) } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt index a8cbc5dc922c..b21777b30b21 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt @@ -17,14 +17,16 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Postsubmit -import android.platform.test.annotations.RequiresDevice -import androidx.test.filters.FlakyTest -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import android.platform.test.rule.SettingOverrideRule +import android.provider.Settings +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import org.junit.ClassRule import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -41,10 +43,9 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 @Postsubmit -open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter) - : OpenAppFromNotificationCold(testSpec) { +open class OpenAppFromLockNotificationCold(flicker: FlickerTest) : + OpenAppFromNotificationCold(flicker) { override val openingNotificationsFromLockScreen = true @@ -52,51 +53,83 @@ open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter) get() = { // Needs to run at start of transition, // so before the transition defined in super.transition - transitions { - device.wakeUp() - } + transitions { device.wakeUp() } super.transition(this) // Needs to run at the end of the setup, so after the setup defined in super.transition setup { - eachRun { - device.sleep() - wmHelper.waitFor("noAppWindowsOnTop") { - it.wmState.topVisibleAppWindow.isEmpty() - } - } + device.sleep() + wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify() } } /** {@inheritDoc} */ - @FlakyTest(bugId = 229735718) + @Test @Ignore("Display is off at the start") override fun navBarLayerPositionAtStartAndEnd() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Display is off at the start") + override fun statusBarLayerPositionAtStartAndEnd() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Display is off at the start") + override fun taskBarLayerIsVisibleAtStartAndEnd() {} + + /** {@inheritDoc} */ + @Test @Ignore("Display is off at the start") override fun taskBarWindowIsAlwaysVisible() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Display is off at the start") + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end") + override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ @Test - override fun entireScreenCovered() = super.entireScreenCovered() + @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end") + override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() /** {@inheritDoc} */ - @FlakyTest(bugId = 203538234) @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() + @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end") + override fun navBarWindowIsAlwaysVisible() {} /** {@inheritDoc} */ - @FlakyTest(bugId = 203538234) + @Postsubmit @Test - override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow() + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return com.android.server.wm.flicker.FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 3) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() } + + /** + * Ensures that posted notifications will be visible on the lockscreen and not suppressed + * due to being marked as seen. + */ + @ClassRule + @JvmField + val disableUnseenNotifFilterRule = + SettingOverrideRule( + Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, + /* value = */ "0", + ) } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt index cd8dea012db5..ec92ca65f80a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt @@ -16,16 +16,20 @@ package com.android.server.wm.flicker.launch -import android.platform.test.annotations.Postsubmit -import android.platform.test.annotations.RequiresDevice -import androidx.test.filters.FlakyTest -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.traces.common.FlickerComponentName +import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.Presubmit +import android.platform.test.rule.SettingOverrideRule +import android.provider.Settings +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.statusBarLayerPositionAtEnd +import org.junit.ClassRule import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -42,10 +46,7 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -@Postsubmit -open class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) - : OpenAppFromNotificationWarm(testSpec) { +class OpenAppFromLockNotificationWarm(flicker: FlickerTest) : OpenAppFromNotificationWarm(flicker) { override val openingNotificationsFromLockScreen = true @@ -53,76 +54,113 @@ open class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) get() = { // Needs to run at start of transition, // so before the transition defined in super.transition - transitions { - device.wakeUp() - } + transitions { device.wakeUp() } super.transition(this) // Needs to run at the end of the setup, so after the setup defined in super.transition setup { - eachRun { - device.sleep() - wmHelper.waitFor("noAppWindowsOnTop") { - it.wmState.topVisibleAppWindow.isEmpty() - } - } + device.sleep() + wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify() } } /** - * Checks that we start of with no top windows and then [testApp] becomes the first and - * only top window of the transition, with snapshot or splash screen windows optionally showing - * first. + * Checks that we start of with no top windows and then [testApp] becomes the first and only top + * window of the transition, with snapshot or splash screen windows optionally showing first. */ @Test - @Postsubmit - open fun appWindowBecomesFirstAndOnlyTopWindow() { - testSpec.assertWm { + @Presubmit + fun appWindowBecomesFirstAndOnlyTopWindow() { + flicker.assertWm { this.hasNoVisibleAppWindow() - .then() - .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isAppWindowOnTop(FlickerComponentName.SPLASH_SCREEN, isOptional = true) - .then() - .isAppWindowOnTop(testApp.component) + .then() + .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true) + .then() + .isAppWindowOnTop(testApp) } } - /** - * Checks that the screen is locked. - */ + /** Checks that the screen is locked at the start of the transition */ @Test - @Postsubmit + @Presubmit fun screenLockedStart() { - testSpec.assertLayersStart { - isEmpty() - } + flicker.assertWmStart { isKeyguardShowing() } } /** {@inheritDoc} */ - @FlakyTest(bugId = 229735718) @Test - override fun entireScreenCovered() = super.entireScreenCovered() + @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end") + override fun navBarLayerPositionAtStartAndEnd() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end") + override fun taskBarWindowIsAlwaysVisible() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end") + override fun statusBarLayerPositionAtStartAndEnd() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end") + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + /** + * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the + * transition + */ + @Presubmit @Test fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd() + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end") + override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end") + override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end") + override fun navBarWindowIsAlwaysVisible() {} /** {@inheritDoc} */ - @FlakyTest(bugId = 203538234) + @FlakyTest(bugId = 246284526) @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return com.android.server.wm.flicker.FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 3) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() } + + /** + * Ensures that posted notifications will be visible on the lockscreen and not suppressed + * due to being marked as seen. + */ + @ClassRule + @JvmField + val disableUnseenNotifFilterRule = + SettingOverrideRule( + Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, + /* value= */ "0", + ) } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt index bc637f8f9a63..009d61797fe0 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt @@ -16,17 +16,17 @@ package com.android.server.wm.flicker.launch +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit -import android.platform.test.annotations.RequiresDevice -import androidx.test.filters.FlakyTest -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import android.platform.test.annotations.Presubmit +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.helpers.wakeUpAndGoToHomeScreen +import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -34,8 +34,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test cold launching an app from a notification from the lock screen when there is an app - * overlaid on the lock screen. + * Test cold launching an app from a notification from the lock screen when there is an app overlaid + * on the lock screen. * * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWithLockOverlayApp` */ @@ -43,12 +43,10 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 @Postsubmit -class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParameter) - : OpenAppFromLockNotificationCold(testSpec) { - private val showWhenLockedApp: ShowWhenLockedAppHelper = - ShowWhenLockedAppHelper(instrumentation) +class OpenAppFromLockNotificationWithLockOverlayApp(flicker: FlickerTest) : + OpenAppFromLockNotificationCold(flicker) { + private val showWhenLockedApp = ShowWhenLockedAppHelper(instrumentation) // Although we are technically still locked here, the overlay app means we should open the // notification shade as if we were unlocked. @@ -59,68 +57,80 @@ class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParamet super.transition(this) setup { - eachRun { - device.wakeUpAndGoToHomeScreen() - - // Launch an activity that is shown when the device is locked - showWhenLockedApp.launchViaIntent(wmHelper) - wmHelper.waitForFullScreenApp(showWhenLockedApp.component) - - device.sleep() - wmHelper.waitFor("noAppWindowsOnTop") { - it.wmState.topVisibleAppWindow.isEmpty() - } - } - } + device.wakeUpAndGoToHomeScreen() + + // Launch an activity that is shown when the device is locked + showWhenLockedApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withFullScreenApp(showWhenLockedApp).waitForAndVerify() - teardown { - test { - showWhenLockedApp.exit(wmHelper) - } + device.sleep() + wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify() } + + teardown { showWhenLockedApp.exit(wmHelper) } } @Test - @Postsubmit + @FlakyTest(bugId = 227143265) fun showWhenLockedAppWindowBecomesVisible() { - testSpec.assertWm { + flicker.assertWm { this.hasNoVisibleAppWindow() - .then() - .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isAppWindowOnTop(showWhenLockedApp.component) + .then() + .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isAppWindowOnTop(showWhenLockedApp) } } @Test - @Postsubmit + @FlakyTest(bugId = 227143265) fun showWhenLockedAppLayerBecomesVisible() { - testSpec.assertLayers { - this.isInvisible(showWhenLockedApp.component) - .then() - .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isVisible(showWhenLockedApp.component) + flicker.assertLayers { + this.isInvisible(showWhenLockedApp) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(showWhenLockedApp) } } /** {@inheritDoc} */ - @FlakyTest(bugId = 229735718) + @Presubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible() + + /** {@inheritDoc} */ + @FlakyTest(bugId = 227143265) + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @FlakyTest(bugId = 209599395) + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() + + @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered() + + @FlakyTest(bugId = 278227468) @Test - override fun entireScreenCovered() = super.entireScreenCovered() + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 3) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt index fe80162b5b81..eae9ca10c711 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt @@ -16,54 +16,36 @@ package com.android.server.wm.flicker.launch +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit -import androidx.test.filters.FlakyTest -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.navBarLayerPositionEnd -import com.android.server.wm.traces.common.FlickerComponentName +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import com.android.server.wm.flicker.navBarLayerPositionAtEnd +import com.android.server.wm.flicker.statusBarLayerPositionAtEnd +import org.junit.Assume +import org.junit.Ignore import org.junit.Test -/** - * Base class for app launch tests from lock screen - */ -abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter) - : OpenAppTransition(testSpec) { +/** Base class for app launch tests from lock screen */ +abstract class OpenAppFromLockTransition(flicker: FlickerTest) : OpenAppTransition(flicker) { - /** - * Defines the transition used to run the test - */ - override val transition: FlickerBuilder.() -> Unit - get() = { - super.transition(this) - setup { - eachRun { - device.sleep() - wmHelper.waitFor("noAppWindowsOnTop") { - it.wmState.topVisibleAppWindow.isEmpty() - } - } - } - teardown { - eachRun { - testApp.exit(wmHelper) - } - } - transitions { - testApp.launchViaIntent(wmHelper) - wmHelper.waitForFullScreenApp(testApp.component) - } + /** Defines the transition used to run the test */ + override val transition: FlickerBuilder.() -> Unit = { + super.transition(this) + setup { + device.sleep() + wmHelper.StateSyncBuilder().withoutTopVisibleAppWindows().waitForAndVerify() } + teardown { testApp.exit(wmHelper) } + transitions { testApp.launchViaIntent(wmHelper) } + } - /** - * Check that we go from no focus to focus on the [testApp] - */ + /** Check that we go from no focus to focus on the [testApp] */ @Presubmit @Test open fun focusChanges() { - testSpec.assertEventLog { - this.focusChanges("", testApp.`package`) - } + flicker.assertEventLog { this.focusChanges("", testApp.`package`) } } /** @@ -73,27 +55,22 @@ abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter) @FlakyTest(bugId = 203538234) @Test open fun appWindowBecomesFirstAndOnlyTopWindow() { - testSpec.assertWm { + flicker.assertWm { this.hasNoVisibleAppWindow() - .then() - .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isAppWindowOnTop(FlickerComponentName.SPLASH_SCREEN, isOptional = true) - .then() - .isAppWindowOnTop(testApp.component) + .then() + .isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true) + .then() + .isAppWindowOnTop(testApp) } } - /** - * Checks that the screen is locked at the start of the transition ([colorFadComponent]) - * layer is visible - */ + /** Checks that the screen is locked at the start of the transition */ @Presubmit @Test fun screenLockedStart() { - testSpec.assertLayersStart { - isEmpty() - } + flicker.assertLayersStart { isEmpty() } } /** {@inheritDoc} */ @@ -101,26 +78,50 @@ abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter) @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible() - /** - * Checks the position of the navigation bar at the start and end of the transition - * - * Differently from the normal usage of this assertion, check only the final state of the - * transition because the display is off at the start and the NavBar is never visible - */ + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end") + override fun navBarLayerPositionAtStartAndEnd() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end") + override fun statusBarLayerPositionAtStartAndEnd() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end") + override fun taskBarLayerIsVisibleAtStartAndEnd() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end") + override fun taskBarWindowIsAlwaysVisible() {} + + /** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */ @Presubmit @Test - override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerPositionEnd() + open fun navBarLayerPositionAtEnd() { + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.navBarLayerPositionAtEnd() + } + + /** Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the end of the transition */ + @Presubmit @Test fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd() + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end") + override fun statusBarLayerIsVisibleAtStartAndEnd() {} /** - * Checks that the status bar layer is visible at the end of the trace + * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the end of the trace * * It is not possible to check at the start because the screen is off */ @Presubmit @Test - override fun statusBarLayerIsVisible() { - testSpec.assertLayersEnd { - this.isVisible(FlickerComponentName.STATUS_BAR) - } + fun statusBarLayerIsVisibleAtEnd() { + flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt index 5022dd8f9bff..7bcb91070ecf 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt @@ -17,13 +17,16 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Postsubmit -import android.platform.test.annotations.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import android.platform.test.annotations.Presubmit +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.statusBarLayerPositionAtEnd import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -40,45 +43,71 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 @Postsubmit -open class OpenAppFromNotificationCold(testSpec: FlickerTestParameter) - : OpenAppFromNotificationWarm(testSpec) { +open class OpenAppFromNotificationCold(flicker: FlickerTest) : + OpenAppFromNotificationWarm(flicker) { + /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { - eachRun { - // Close the app that posted the notification to trigger a cold start next time - // it is open - can't just kill it because that would remove the notification. - taplInstrumentation.goHome() - taplInstrumentation.workspace.switchToOverview() - taplInstrumentation.overview.dismissAllTasks() - } + // Close the app that posted the notification to trigger a cold start next time + // it is open - can't just kill it because that would remove the notification. + tapl.setExpectedRotationCheckEnabled(false) + tapl.goHome() + tapl.workspace.switchToOverview() + tapl.overview.dismissAllTasks() } } + @Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart() + + @Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart() + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end") + override fun navBarLayerPositionAtStartAndEnd() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end") + override fun statusBarLayerPositionAtStartAndEnd() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end") + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + /** + * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the + * transition + */ + @Presubmit @Test open fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd() + + /** {@inheritDoc} */ @Test - @Postsubmit - override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart() + @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end") + override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() + /** {@inheritDoc} */ @Test - @Postsubmit - override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart() + @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end") + override fun navBarWindowIsAlwaysVisible() {} companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return com.android.server.wm.flicker.FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 3) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt index 7ffa51320487..8b4a613305c0 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest_ShellTransit.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2023 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. @@ -14,36 +14,34 @@ * limitations under the License. */ -package com.android.server.wm.flicker.ime +package com.android.server.wm.flicker.launch import android.platform.test.annotations.Postsubmit -import androidx.test.filters.FlakyTest -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.annotation.Group2 -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import org.junit.Assume -import org.junit.Before +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized -/** - * Test IME window opening transitions. - * To run this test: `atest FlickerTests:ReOpenImeWindowTest` - */ -@RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group2 -@FlakyTest(bugId = 221854428) -class ReOpenImeWindowTest_ShellTransit(private val testSpec: FlickerTestParameter) - : ReOpenImeWindowTest(testSpec) { - @Before - override fun before() { - Assume.assumeTrue(isShellTransitionsEnabled) +@Postsubmit +class OpenAppFromNotificationColdCfArm(flicker: FlickerTest) : + OpenAppFromNotificationCold(flicker) { + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt index 812f6859c7b0..425e674dec3a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt @@ -17,24 +17,28 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Postsubmit -import android.platform.test.annotations.RequiresDevice +import android.platform.test.annotations.Presubmit +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.helpers.wakeUpAndGoToHomeScreen import android.view.WindowInsets import android.view.WindowManager -import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until -import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.NotificationAppHelper -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen +import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd +import com.android.server.wm.flicker.navBarLayerPositionAtEnd +import com.android.server.wm.flicker.navBarWindowIsVisibleAtEnd +import com.android.server.wm.flicker.taskBarLayerIsVisibleAtEnd +import com.android.server.wm.flicker.taskBarWindowIsVisibleAtEnd import org.junit.Assume import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -51,30 +55,22 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -@Postsubmit -open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) - : OpenAppTransition(testSpec) { - protected val taplInstrumentation = LauncherInstrumentation() - +open class OpenAppFromNotificationWarm(flicker: FlickerTest) : OpenAppTransition(flicker) { override val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation) open val openingNotificationsFromLockScreen = false + /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { setup { - test { - device.wakeUpAndGoToHomeScreen() - this.setRotation(testSpec.startRotation) - } - eachRun { - testApp.launchViaIntent(wmHelper) - wmHelper.waitForFullScreenApp(testApp.component) - testApp.postNotification(device, wmHelper) - device.pressHome() - wmHelper.waitForAppTransitionIdle() - } + device.wakeUpAndGoToHomeScreen() + this.setRotation(flicker.scenario.startRotation) + testApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() + testApp.postNotification(wmHelper) + device.pressHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() } transitions { @@ -82,11 +78,14 @@ open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) var endY = 3 * device.displayHeight / 4 var steps = 25 if (openingNotificationsFromLockScreen) { - val wm = instrumentation.context.getSystemService(WindowManager::class.java) + val wm: WindowManager = + instrumentation.context.getSystemService(WindowManager::class.java) + ?: error("Unable to connect to WindowManager service") val metricInsets = wm.currentWindowMetrics.windowInsets - val insets = metricInsets.getInsetsIgnoringVisibility( - WindowInsets.Type.statusBars() - or WindowInsets.Type.displayCutout()) + val insets = + metricInsets.getInsetsIgnoringVisibility( + WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout() + ) startY = insets.top + 100 endY = device.displayHeight / 2 @@ -100,98 +99,109 @@ open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) instrumentation.uiAutomation.syncInputTransactions() // Launch the activity by clicking the notification - val notification = device.wait(Until.findObject( - By.text("Flicker Test Notification")), 2000L) + val notification = + device.wait(Until.findObject(By.text("Flicker Test Notification")), 2000L) notification?.click() ?: error("Notification not found") instrumentation.uiAutomation.syncInputTransactions() // Wait for the app to launch - wmHelper.waitForFullScreenApp(testApp.component) + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() } - teardown { - test { - testApp.exit(wmHelper) - } - } + teardown { testApp.exit(wmHelper) } } - @Test - @Postsubmit - override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + @Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart() + @Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart() + + @Presubmit @Test - @Postsubmit - override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() + open fun notificationAppWindowVisibleAtEnd() { + flicker.assertWmEnd { this.isAppWindowVisible(testApp) } + } + @Presubmit @Test - @Postsubmit - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + open fun notificationAppWindowOnTopAtEnd() { + flicker.assertWmEnd { this.isAppWindowOnTop(testApp) } + } + @Presubmit @Test - @Postsubmit - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() + open fun notificationAppLayerVisibleAtEnd() { + flicker.assertLayersEnd { this.isVisible(testApp) } + } + /** + * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible at the end of the + * transition + * + * Note: Large screen only + */ + @Presubmit @Test - @Postsubmit - override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart() + open fun taskBarWindowIsVisibleAtEnd() { + Assume.assumeTrue(flicker.scenario.isTablet) + flicker.taskBarWindowIsVisibleAtEnd() + } + /** + * Checks that the [ComponentNameMatcher.TASK_BAR] layer is visible at the end of the transition + * + * Note: Large screen only + */ + @Presubmit @Test - @Postsubmit - override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart() + open fun taskBarLayerIsVisibleAtEnd() { + Assume.assumeTrue(flicker.scenario.isTablet) + flicker.taskBarLayerIsVisibleAtEnd() + } + /** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */ + @Presubmit @Test - @Postsubmit - fun notificationAppWindowVisibleAtEnd() { - testSpec.assertWmEnd { - this.isAppWindowVisible(testApp.component) - } + open fun navBarLayerPositionAtEnd() { + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.navBarLayerPositionAtEnd() } + /** {@inheritDoc} */ + @Presubmit @Test - @Postsubmit - fun notificationAppWindowOnTopAtEnd() { - testSpec.assertWmEnd { - this.isAppWindowOnTop(testApp.component) - } + open fun navBarLayerIsVisibleAtEnd() { + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.navBarLayerIsVisibleAtEnd() } + @Presubmit @Test - @Postsubmit - fun notificationAppLayerVisibleAtEnd() { - testSpec.assertLayersEnd { - this.isVisible(testApp.component) - } + open fun navBarWindowIsVisibleAtEnd() { + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.navBarWindowIsVisibleAtEnd() } /** {@inheritDoc} */ - @Postsubmit @Test - override fun appWindowBecomesTopWindow() { - Assume.assumeFalse(isShellTransitionsEnabled) - super.appWindowBecomesTopWindow() - } + @Ignore("Display is off at the start") + override fun taskBarLayerIsVisibleAtStartAndEnd() {} - @FlakyTest(bugId = 229738092) + /** {@inheritDoc} */ @Test - fun appWindowBecomesTopWindow_ShellTransit() { - Assume.assumeTrue(isShellTransitionsEnabled) - super.appWindowBecomesTopWindow() - } + @Postsubmit + override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible() companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 3) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt new file mode 100644 index 000000000000..43d28fa60e51 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.launch + +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenAppFromNotificationWarmCfArm(flicker: FlickerTest) : + OpenAppFromNotificationWarm(flicker) { + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt index 2226fd1d2155..8e1b059b2bc7 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt @@ -16,21 +16,16 @@ package com.android.server.wm.flicker.launch -import androidx.test.filters.FlakyTest +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit -import android.platform.test.annotations.RequiresDevice -import android.view.Display -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.LAUNCHER_COMPONENT -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.helpers.reopenAppFromOverview +import android.tools.common.Rotation +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.traces.common.WindowManagerConditionsFactory -import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -43,86 +38,59 @@ import org.junit.runners.Parameterized * To run this test: `atest FlickerTests:OpenAppFromOverviewTest` * * Actions: + * ``` * Launch [testApp] * Press recents * Relaunch an app [testApp] by selecting it in the overview screen, and wait animation to * complete (only this action is traced) + * ``` * * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [OpenAppTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice +@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) - : OpenAppFromLauncherTransition(testSpec) { +open class OpenAppFromOverviewTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) { - /** - * Defines the transition used to run the test - */ + /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { - test { - testApp.launchViaIntent(wmHelper) - } - eachRun { - device.pressHome() - wmHelper.waitForAppTransitionIdle() - device.pressRecentApps() - wmHelper.waitFor( - WindowManagerConditionsFactory - .isAppTransitionIdle(Display.DEFAULT_DISPLAY), - WindowManagerConditionsFactory.isActivityVisible(LAUNCHER_COMPONENT), - WindowManagerConditionsFactory.hasLayersAnimating().negate() - ) - this.setRotation(testSpec.startRotation) + tapl.setExpectedRotationCheckEnabled(false) + testApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + // By default, launcher doesn't rotate on phones, but rotates on tablets + if (flicker.scenario.isTablet) { + tapl.setExpectedRotation(flicker.scenario.startRotation.value) + } else { + tapl.setExpectedRotation(Rotation.ROTATION_0.value) } + tapl.workspace.switchToOverview() + wmHelper.StateSyncBuilder().withRecentsActivityVisible().waitForAndVerify() + this.setRotation(flicker.scenario.startRotation) } transitions { - device.reopenAppFromOverview(wmHelper) - wmHelper.waitFor( - WindowManagerConditionsFactory.hasLayersAnimating().negate(), - WindowManagerConditionsFactory.isWMStateComplete(), - WindowManagerConditionsFactory.isLayerVisible(LAUNCHER_COMPONENT).negate(), - WindowManagerConditionsFactory.isActivityVisible(LAUNCHER_COMPONENT).negate() - ) - wmHelper.waitForFullScreenApp(testApp.component) + tapl.overview.currentTask.open() + wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() } } /** {@inheritDoc} */ - @FlakyTest(bugId = 206753786) - @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - - /** {@inheritDoc} */ - @Presubmit - @Test - override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher() - - /** {@inheritDoc} */ @FlakyTest @Test - override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - - /** {@inheritDoc} */ - @Presubmit - @Test - override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - - /** {@inheritDoc} */ - @Presubmit - @Test - override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() /** {@inheritDoc} */ @Presubmit @@ -134,53 +102,17 @@ open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible_warmStart() - /** {@inheritDoc} */ - @FlakyTest(bugId = 229735718) - @Test - override fun entireScreenCovered() = super.entireScreenCovered() - - /** {@inheritDoc} */ - @Presubmit - @Test - override fun appWindowReplacesLauncherAsTopWindow() { - Assume.assumeFalse(isShellTransitionsEnabled) - super.appWindowReplacesLauncherAsTopWindow() - } - - @FlakyTest(bugId = 229738092) - @Test - fun appWindowReplacesLauncherAsTopWindow_ShellTransit() { - Assume.assumeTrue(isShellTransitionsEnabled) - super.appWindowReplacesLauncherAsTopWindow() - } - - /** {@inheritDoc} */ - @Presubmit - @Test - override fun appWindowBecomesTopWindow() { - Assume.assumeFalse(isShellTransitionsEnabled) - super.appWindowBecomesTopWindow() - } - - @FlakyTest(bugId = 229738092) - @Test - fun appWindowBecomesTopWindow_ShellTransit() { - Assume.assumeTrue(isShellTransitionsEnabled) - super.appWindowBecomesTopWindow() - } - companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 3) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt new file mode 100644 index 000000000000..ff24190a7aef --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.launch + +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** Some assertions will fail because of b/264415996 */ +@FlickerServiceCompatible +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class OpenAppFromOverviewTestCfArm(flicker: FlickerTest) : OpenAppFromOverviewTest(flicker) { + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt index 55eb3c3d1a0a..1383ae39f760 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt @@ -16,21 +16,20 @@ package com.android.server.wm.flicker.launch -import android.platform.test.annotations.Postsubmit -import androidx.test.filters.FlakyTest +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit -import android.platform.test.annotations.RequiresDevice -import android.view.Surface -import android.view.WindowManagerPolicyConstants -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.NonResizeableAppHelper -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.navBarLayerPositionEnd -import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.Assume import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -44,139 +43,182 @@ import org.junit.runners.Parameterized * To run this test: `atest FlickerTests:OpenAppNonResizeableTest` * * Actions: + * ``` * Lock the device. * Launch an app on top of the lock screen [testApp] and wait animation to complete + * ``` * * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [OpenAppTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice +@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) - : OpenAppFromLockTransition(testSpec) { +open class OpenAppNonResizeableTest(flicker: FlickerTest) : OpenAppFromLockTransition(flicker) { override val testApp = NonResizeableAppHelper(instrumentation) - private val colorFadComponent = FlickerComponentName("", "ColorFade BLAST#") /** - * Checks that the nav bar layer starts invisible, becomes visible during unlocking animation - * and remains visible at the end + * Checks that the [ComponentNameMatcher.NAV_BAR] layer starts invisible, becomes visible during + * unlocking animation and remains visible at the end */ - @FlakyTest(bugId = 227083463) + @Presubmit @Test fun navBarLayerVisibilityChanges() { - testSpec.assertLayers { - this.isInvisible(FlickerComponentName.NAV_BAR) + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.assertLayers { + this.isInvisible(ComponentNameMatcher.NAV_BAR) .then() - .isVisible(FlickerComponentName.NAV_BAR) + .isVisible(ComponentNameMatcher.NAV_BAR) } } - /** - * Checks if [testApp] is visible at the end of the transition - */ + /** Checks if [testApp] is visible at the end of the transition */ @Presubmit @Test fun appWindowBecomesVisibleAtEnd() { - testSpec.assertWmEnd { - this.isAppWindowVisible(testApp.component) - } + flicker.assertWmEnd { this.isAppWindowVisible(testApp) } } /** - * Checks that the nav bar starts the transition invisible, then becomes visible during - * the unlocking animation and remains visible at the end of the transition + * Checks that the [ComponentNameMatcher.NAV_BAR] starts the transition invisible, then becomes + * visible during the unlocking animation and remains visible at the end of the transition */ @Presubmit @Test fun navBarWindowsVisibilityChanges() { - testSpec.assertWm { - this.isNonAppWindowInvisible(FlickerComponentName.NAV_BAR) + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.assertWm { + this.isNonAppWindowInvisible(ComponentNameMatcher.NAV_BAR) .then() - .isAboveAppWindowVisible(FlickerComponentName.NAV_BAR) + .isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) } } /** - * Checks that the status bar layer is visible at the end of the trace + * Checks that the [ComponentNameMatcher.TASK_BAR] starts the transition invisible, then becomes + * visible during the unlocking animation and remains visible at the end of the transition + */ + @Presubmit + @Test + fun taskBarLayerIsVisibleAtEnd() { + Assume.assumeTrue(flicker.scenario.isTablet) + flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) } + } + + /** + * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the end of the trace * * It is not possible to check at the start because the screen is off */ @Presubmit @Test - override fun statusBarLayerIsVisible() { - testSpec.assertLayersEnd { - this.isVisible(FlickerComponentName.STATUS_BAR) - } + override fun statusBarLayerIsVisibleAtStartAndEnd() { + flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) } } /** {@inheritDoc} */ - @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end") + override fun taskBarLayerIsVisibleAtStartAndEnd() {} /** {@inheritDoc} */ - @FlakyTest(bugId = 206753786) - @Test - fun statusBarLayerPositionAtEnd() { - testSpec.assertLayersEnd { - val display = this.entry.displays.minByOrNull { it.id } - ?: error("There is no display!") - this.visibleRegion(FlickerComponentName.STATUS_BAR) - .coversExactly(WindowUtils.getStatusBarPosition(display)) - } - } + @Test + @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end") + override fun navBarLayerIsVisibleAtStartAndEnd() {} - /** - * Checks the position of the navigation bar at the start and end of the transition - * - * Differently from the normal usage of this assertion, check only the final state of the - * transition because the display is off at the start and the NavBar is never visible - */ - @Postsubmit + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end") + override fun taskBarWindowIsAlwaysVisible() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end") + override fun navBarWindowIsAlwaysVisible() {} + + /** {@inheritDoc} */ @Test - override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerPositionEnd() + @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end") + override fun navBarWindowIsVisibleAtStartAndEnd() = super.navBarWindowIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end") + override fun statusBarWindowIsAlwaysVisible() {} + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun appWindowBecomesFirstAndOnlyTopWindow() = + super.appWindowBecomesFirstAndOnlyTopWindow() + + /** {@inheritDoc} */ + @Presubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible() + + /** Checks the [ComponentNameMatcher.NAV_BAR] is visible at the end of the transition */ + @Presubmit + @Test + fun navBarLayerIsVisibleAtEnd() { + Assume.assumeFalse(flicker.scenario.isTablet) + flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) } + } /** {@inheritDoc} */ @FlakyTest @Test override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() + super.visibleLayersShownMoreThanOneConsecutiveEntry() /** {@inheritDoc} */ - @FlakyTest + @Presubmit + @Test + override fun appLayerBecomesVisible() { + Assume.assumeFalse(flicker.scenario.isTablet) + super.appLayerBecomesVisible() + } + + /** {@inheritDoc} */ + @FlakyTest(bugId = 227143265) @Test - override fun entireScreenCovered() = super.entireScreenCovered() + fun appLayerBecomesVisibleTablet() { + Assume.assumeTrue(flicker.scenario.isTablet) + super.appLayerBecomesVisible() + } - @FlakyTest(bugId = 218470989) + @Presubmit @Test override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + @FlakyTest(bugId = 251217585) + @Test + override fun focusChanges() { + super.focusChanges() + } companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 3, - supportedNavigationModes = - listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY), - supportedRotations = listOf(Surface.ROTATION_0) - ) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL), + supportedRotations = listOf(Rotation.ROTATION_0) + ) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt index 20e6d0222854..87a14c69e3ac 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt @@ -16,202 +16,95 @@ package com.android.server.wm.flicker.launch -import android.app.Instrumentation import android.platform.test.annotations.Presubmit -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.dsl.FlickerBuilder +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.helpers.wakeUpAndGoToHomeScreen +import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.StandardAppHelper import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.FlickerComponentName import org.junit.Test -/** - * Base class for app launch tests - */ -abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { - protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() +/** Base class for app launch tests */ +abstract class OpenAppTransition(flicker: FlickerTest) : BaseTest(flicker) { protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation) - /** - * Defines the transition used to run the test - */ - protected open val transition: FlickerBuilder.() -> Unit = { + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { setup { - test { - device.wakeUpAndGoToHomeScreen() - this.setRotation(testSpec.startRotation) - } - } - teardown { - test { - testApp.exit(wmHelper) - } - } - } - - /** - * Entry point for the test runner. It will use this method to initialize and cache - * flicker executions - */ - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - transition() + tapl.setExpectedRotation(flicker.scenario.startRotation.value) + device.wakeUpAndGoToHomeScreen() + this.setRotation(flicker.scenario.startRotation) } + teardown { testApp.exit(wmHelper) } } /** - * Checks that the navigation bar window is visible during the whole transition - */ - open fun navBarWindowIsVisible() { - testSpec.navBarWindowIsVisible() - } - - /** - * Checks that the navigation bar layer is visible at the start and end of the trace - */ - open fun navBarLayerIsVisible() { - testSpec.navBarLayerIsVisible() - } - - /** - * Checks the position of the navigation bar at the start and end of the transition + * Checks that the [testApp] layer doesn't exist or is invisible at the start of the transition, + * but is created and/or becomes visible during the transition. */ @Presubmit @Test - open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - /** - * Checks that the status bar window is visible during the whole transition - */ - @Presubmit - @Test - open fun statusBarWindowIsVisible() { - testSpec.statusBarWindowIsVisible() + open fun appLayerBecomesVisible() { + appLayerBecomesVisible_coldStart() } - /** - * Checks that the status bar layer is visible at the start and end of the trace - */ - @Presubmit - @Test - open fun statusBarLayerIsVisible() { - testSpec.statusBarLayerIsVisible() - } - - /** - * Checks the position of the status bar at the start and end of the transition - */ - @Presubmit - @Test - open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - - /** - * Checks that all windows that are visible on the trace, are visible for at least 2 - * consecutive entries. - */ - @Presubmit - @Test - open fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry() - } - } - - /** - * Checks that all layers that are visible on the trace, are visible for at least 2 - * consecutive entries. - */ - @Presubmit - @Test - open fun visibleLayersShownMoreThanOneConsecutiveEntry() { - testSpec.assertLayers { - this.visibleLayersShownMoreThanOneConsecutiveEntry() - } - } - - /** - * Checks that all parts of the screen are covered during the transition - */ - @Presubmit - @Test - open fun entireScreenCovered() = testSpec.entireScreenCovered() - - /** - * Checks that the app layer doesn't exist or is invisible at the start of the transition, but - * is created and/or becomes visible during the transition. - */ - @Presubmit - @Test - open fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart() - protected fun appLayerBecomesVisible_coldStart() { - testSpec.assertLayers { - this.notContains(testApp.component) - .then() - .isInvisible(testApp.component, isOptional = true) - .then() - .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isVisible(FlickerComponentName.SPLASH_SCREEN, isOptional = true) - .then() - .isVisible(testApp.component) + flicker.assertLayers { + this.notContains(testApp) + .then() + .isInvisible(testApp, isOptional = true) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true) + .then() + .isVisible(testApp) } } protected fun appLayerBecomesVisible_warmStart() { - testSpec.assertLayers { - this.isInvisible(testApp.component) - .then() - .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isVisible(FlickerComponentName.SPLASH_SCREEN, isOptional = true) - .then() - .isVisible(testApp.component) + flicker.assertLayers { + this.isInvisible(testApp) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true) + .then() + .isVisible(testApp) } } /** - * Checks that the app window doesn't exist at the start of the transition, that it is + * Checks that the [testApp] window doesn't exist at the start of the transition, that it is * created (invisible - optional) and becomes visible during the transition * - * The `isAppWindowInvisible` step is optional because we log once per frame, upon logging, - * the window may be visible or not depending on what was processed until that moment. + * The `isAppWindowInvisible` step is optional because we log once per frame, upon logging, the + * window may be visible or not depending on what was processed until that moment. */ - @Presubmit - @Test - open fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart() + @Presubmit @Test open fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart() protected fun appWindowBecomesVisible_coldStart() { - testSpec.assertWm { - this.notContains(testApp.component) - .then() - .isAppWindowInvisible(testApp.component, isOptional = true) - .then() - .isAppWindowVisible(testApp.component) + flicker.assertWm { + this.notContains(testApp) + .then() + .isAppWindowInvisible(testApp, isOptional = true) + .then() + .isAppWindowVisible(testApp) } } protected fun appWindowBecomesVisible_warmStart() { - testSpec.assertWm { - this.isAppWindowInvisible(testApp.component) - .then() - .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isAppWindowVisible(FlickerComponentName.SPLASH_SCREEN, isOptional = true) - .then() - .isAppWindowVisible(testApp.component) + flicker.assertWm { + this.isAppWindowInvisible(testApp) + .then() + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true) + .then() + .isAppWindowVisible(testApp) } } @@ -222,14 +115,22 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { @Presubmit @Test open fun appWindowBecomesTopWindow() { - testSpec.assertWm { - this.isAppWindowNotOnTop(testApp.component) - .then() - .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isAppWindowOnTop(FlickerComponentName.SPLASH_SCREEN, isOptional = true) - .then() - .isAppWindowOnTop(testApp.component) + flicker.assertWm { + this.isAppWindowNotOnTop(testApp) + .then() + .isAppWindowOnTop( + testApp.or(ComponentNameMatcher.SNAPSHOT).or(ComponentNameMatcher.SPLASH_SCREEN) + ) } } + + /** + * Checks that [testApp] window is not on top at the start of the transition, and then becomes + * the top visible window until the end of the transition. + */ + @Presubmit + @Test + open fun appWindowIsTopWindowAtEnd() { + flicker.assertWmEnd { this.isAppWindowOnTop(testApp) } + } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt index 97528c0471cc..3385830ee77f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt @@ -16,14 +16,14 @@ package com.android.server.wm.flicker.launch +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit -import android.platform.test.annotations.RequiresDevice -import androidx.test.filters.FlakyTest -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.setRotation import org.junit.FixMethodOrder import org.junit.Test @@ -37,88 +37,47 @@ import org.junit.runners.Parameterized * To run this test: `atest FlickerTests:OpenAppWarmTest` * * Actions: + * ``` * Launch [testApp] * Press home * Relaunch an app [testApp] and wait animation to complete (only this action is traced) + * ``` * * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [OpenAppTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice +@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -open class OpenAppWarmTest(testSpec: FlickerTestParameter) - : OpenAppFromLauncherTransition(testSpec) { - /** - * Defines the transition used to run the test - */ +open class OpenAppWarmTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) { + /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { - test { - testApp.launchViaIntent(wmHelper) - } - eachRun { - device.pressHome() - wmHelper.waitForHomeActivityVisible() - this.setRotation(testSpec.startRotation) - } - } - teardown { - eachRun { - testApp.exit(wmHelper) - } - } - transitions { + tapl.setExpectedRotationCheckEnabled(false) testApp.launchViaIntent(wmHelper) - wmHelper.waitForFullScreenApp(testApp.component) + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + this.setRotation(flicker.scenario.startRotation) } + teardown { testApp.exit(wmHelper) } + transitions { testApp.launchViaIntent(wmHelper) } } /** {@inheritDoc} */ @FlakyTest(bugId = 206753786) @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - - /** {@inheritDoc} */ - @Presubmit - @Test - override fun appWindowReplacesLauncherAsTopWindow() = - super.appWindowReplacesLauncherAsTopWindow() - - /** {@inheritDoc} */ - @Presubmit - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - - /** {@inheritDoc} */ - @FlakyTest - @Test - override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() - - /** {@inheritDoc} */ - @Presubmit - @Test - override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher() - - /** {@inheritDoc} */ - @Presubmit - @Test - override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() - - /** {@inheritDoc} */ - @Presubmit - @Test - override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() /** {@inheritDoc} */ @Presubmit @@ -130,22 +89,17 @@ open class OpenAppWarmTest(testSpec: FlickerTestParameter) @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible_warmStart() - @FlakyTest(bugId = 229735718) - @Test - override fun entireScreenCovered() = super.entireScreenCovered() - companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests() + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt new file mode 100644 index 000000000000..d8b38b30cf13 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.launch + +import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@FlickerServiceCompatible +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenAppWarmTestCfArm(flicker: FlickerTest) : OpenAppWarmTest(flicker) { + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt new file mode 100644 index 000000000000..ae9ca8007dc8 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt @@ -0,0 +1,167 @@ +/* + * 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.android.server.wm.flicker.launch + +import android.os.SystemClock +import android.platform.test.annotations.Postsubmit +import android.tools.device.apphelpers.CameraAppHelper +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule +import android.view.KeyEvent +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.helpers.setRotation +import org.junit.FixMethodOrder +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test cold launching camera from launcher by double pressing power button + * + * To run this test: `atest FlickerTests:OpenCameraOnDoubleClickPowerButton` + * + * Actions: + * ``` + * Make sure no apps are running on the device + * Launch an app [testApp] and wait animation to complete + * ``` + * + * Notes: + * ``` + * 1. Some default assertions (e.g., nav bar, status bar and screen covered) + * are inherited [OpenAppTransition] + * 2. Part of the test setup occurs automatically via + * [com.android.server.wm.flicker.TransitionRunnerWithRules], + * including configuring navigation mode, initial orientation and ensuring no + * apps are running before setup + * ``` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenCameraOnDoubleClickPowerButton(flicker: FlickerTest) : + OpenAppFromLauncherTransition(flicker) { + private val cameraApp = CameraAppHelper(instrumentation) + override val testApp: StandardAppHelper + get() = cameraApp + + override val transition: FlickerBuilder.() -> Unit + get() = { + setup { + RemoveAllTasksButHomeRule.removeAllTasksButHome() + this.setRotation(flicker.scenario.startRotation) + } + transitions { + device.pressKeyCode(KeyEvent.KEYCODE_POWER) + SystemClock.sleep(100) + device.pressKeyCode(KeyEvent.KEYCODE_POWER) + wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(cameraApp).waitForAndVerify() + } + teardown { RemoveAllTasksButHomeRule.removeAllTasksButHome() } + } + + @Postsubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible() + + @Postsubmit @Test override fun appWindowAsTopWindowAtEnd() = super.appWindowAsTopWindowAtEnd() + + @Postsubmit @Test override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow() + + @Postsubmit @Test override fun appWindowBecomesVisible() = super.appWindowBecomesVisible() + + @Postsubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher() + + @Postsubmit @Test override fun appWindowIsTopWindowAtEnd() = super.appWindowIsTopWindowAtEnd() + + @Postsubmit + @Test + override fun appWindowReplacesLauncherAsTopWindow() = + super.appWindowReplacesLauncherAsTopWindow() + + @Postsubmit @Test override fun focusChanges() = super.focusChanges() + + @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered() + + @Ignore("Not applicable to this CUJ. App is full screen at the end") + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() + + @Postsubmit + @Test + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() + + @Postsubmit + @Test + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() + + @Ignore("Status bar visibility depends on whether the permission dialog is displayed or not") + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + @Ignore("Status bar visibility depends on whether the permission dialog is displayed or not") + @Test + override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd() + + @Ignore("Status bar visibility depends on whether the permission dialog is displayed or not") + @Test + override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible() + + @Ignore("Not applicable to this CUJ. App is full screen at the end") + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + @Ignore("Not applicable to this CUJ. App is full screen at the end") + @Test + override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible() + + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + @Postsubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + @Ignore("Not applicable to this CUJ. App is full screen at the end") + @Test + override fun navBarWindowIsVisibleAtStartAndEnd() { + super.navBarWindowIsVisibleAtStartAndEnd() + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt new file mode 100644 index 000000000000..b48611edd738 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt @@ -0,0 +1,128 @@ +/* + * 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.android.server.wm.flicker.launch + +import android.app.Instrumentation +import android.os.Bundle +import android.os.Handler +import android.platform.test.annotations.Presubmit +import android.tools.common.traces.ConditionsFactory +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerBuilderProvider +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule +import android.tools.device.helpers.wakeUpAndGoToHomeScreen +import androidx.test.filters.RequiresDevice +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.R +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.setRotation +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test the [android.app.ActivityOptions.makeCustomTaskAnimation]. + * + * To run this test: `atest FlickerTests:OverrideTaskTransitionTest` + * + * Actions: + * ``` + * Launches SimpleActivity with a special animation. + * ``` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OverrideTaskTransitionTest(val flicker: FlickerTest) { + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val testApp = SimpleAppHelper(instrumentation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { + device.wakeUpAndGoToHomeScreen() + RemoveAllTasksButHomeRule.removeAllTasksButHome() + setRotation(flicker.scenario.startRotation) + } + transitions { + instrumentation.context.startActivity( + testApp.openAppIntent, + createCustomTaskAnimation() + ) + wmHelper + .StateSyncBuilder() + .add(ConditionsFactory.isWMStateComplete()) + .withAppTransitionIdle() + .withWindowSurfaceAppeared(testApp) + .waitForAndVerify() + } + teardown { testApp.exit() } + } + } + + @Presubmit + @Test + fun testSimpleActivityIsShownDirectly() { + flicker.assertLayers { + // Before the app launches, only the launcher is visible. + isVisible(ComponentNameMatcher.LAUNCHER) + .isInvisible(testApp) + .then() + // Animation starts, but the app may not be drawn yet which means the Splash + // may be visible. + .isSplashScreenVisibleFor(testApp, isOptional = true) + .then() + // App shows up with the custom animation starting at alpha=1. + .isVisible(testApp) + .then() + // App custom animation continues to alpha=0 (invisible). + .isInvisible(testApp) + .then() + // App custom animation ends with it being visible. + .isVisible(testApp) + } + } + + private fun createCustomTaskAnimation(): Bundle { + return android.app.ActivityOptions.makeCustomTaskAnimation( + instrumentation.context, + R.anim.show_hide_show_3000ms, + 0, + Handler.getMain(), + null, + null + ) + .toBundle() + } + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt index 2f546b56f145..d0fd73207c42 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt @@ -18,29 +18,24 @@ package com.android.server.wm.flicker.launch import android.app.Instrumentation import android.app.WallpaperManager -import android.platform.test.annotations.Postsubmit +import android.content.res.Resources +import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.Presubmit +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.common.traces.component.ComponentNameMatcher.Companion.SPLASH_SCREEN +import android.tools.common.traces.component.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER +import android.tools.common.traces.component.ComponentSplashScreenMatcher +import android.tools.common.traces.component.IComponentMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.helpers.WindowUtils +import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.filters.RequiresDevice -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.LAUNCHER_COMPONENT -import com.android.server.wm.flicker.annotation.Group4 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.entireScreenCovered +import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.NewTasksAppHelper -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.flicker.testapp.ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME -import com.android.server.wm.flicker.testapp.ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.common.FlickerComponentName.Companion.SPLASH_SCREEN -import com.android.server.wm.traces.common.FlickerComponentName.Companion.WALLPAPER_BBQ_WRAPPER -import com.android.server.wm.traces.parser.toFlickerComponent +import com.android.server.wm.flicker.helpers.SimpleAppHelper import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -50,204 +45,177 @@ import org.junit.runners.Parameterized /** * Test the back and forward transition between 2 activities. * - * To run this test: `atest FlickerTests:ActivitiesTransitionTest` + * To run this test: `atest FlickerTests:TaskTransitionTest` * * Actions: + * ``` * Launch the NewTaskLauncherApp [mTestApp] * Open a new task (SimpleActivity) from the NewTaskLauncherApp [mTestApp] * Go back to the NewTaskLauncherApp [mTestApp] + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group4 -class TaskTransitionTest(val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val mTestApp: NewTasksAppHelper = NewTasksAppHelper(instrumentation) - private val mWallpaper by lazy { - getWallpaperPackage(InstrumentationRegistry.getInstrumentation()) - ?: error("Unable to obtain wallpaper") - } - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - eachRun { - mTestApp.launchViaIntent(wmHelper) - wmHelper.waitForFullScreenApp(mTestApp.component) - } - } - teardown { - test { - mTestApp.exit() - } - } - transitions { - mTestApp.openNewTask(device, wmHelper) - device.pressBack() - wmHelper.waitForAppTransitionIdle() - wmHelper.waitForFullScreenApp(mTestApp.component) - } +class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) { + private val launchNewTaskApp = NewTasksAppHelper(instrumentation) + private val simpleApp = SimpleAppHelper(instrumentation) + private val wallpaper by lazy { getWallpaperPackage(instrumentation) } + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { launchNewTaskApp.launchViaIntent(wmHelper) } + teardown { launchNewTaskApp.exit(wmHelper) } + transitions { + launchNewTaskApp.openNewTask(device, wmHelper) + tapl.pressBack() + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() } } /** - * Checks that the wallpaper window is never visible when performing task transitions. - * A solid color background should be shown instead. + * Checks that the [wallpaper] window is never visible when performing task transitions. A solid + * color background should be shown instead. */ - @Postsubmit + @FlakyTest(bugId = 253617416) @Test fun wallpaperWindowIsNeverVisible() { - testSpec.assertWm { - this.isNonAppWindowInvisible(mWallpaper) - } + flicker.assertWm { this.isNonAppWindowInvisible(wallpaper) } } /** - * Checks that the wallpaper layer is never visible when performing task transitions. - * A solid color background should be shown instead. + * Checks that the [wallpaper] layer is never visible when performing task transitions. A solid + * color background should be shown instead. */ - @Postsubmit + @Presubmit @Test fun wallpaperLayerIsNeverVisible() { - testSpec.assertLayers { - this.isInvisible(mWallpaper) + flicker.assertLayers { + this.isInvisible(wallpaper) this.isInvisible(WALLPAPER_BBQ_WRAPPER) } } /** - * Check that the launcher window is never visible when performing task transitions. - * A solid color background should be shown above it. + * Check that the [ComponentNameMatcher.LAUNCHER] window is never visible when performing task + * transitions. A solid color background should be shown above it. */ - @Postsubmit + @Presubmit @Test fun launcherWindowIsNeverVisible() { - testSpec.assertWm { - this.isAppWindowInvisible(LAUNCHER_COMPONENT) - } + flicker.assertWm { this.isAppWindowInvisible(ComponentNameMatcher.LAUNCHER) } } /** - * Checks that the launcher layer is never visible when performing task transitions. - * A solid color background should be shown above it. + * Checks that the [ComponentNameMatcher.LAUNCHER] layer is never visible when performing task + * transitions. A solid color background should be shown above it. */ - @Postsubmit + @Presubmit @Test fun launcherLayerIsNeverVisible() { - testSpec.assertLayers { - this.isInvisible(LAUNCHER_COMPONENT) - } + flicker.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) } } - /** - * Checks that a color background is visible while the task transition is occurring. - */ - @Postsubmit + /** Checks that a color background is visible while the task transition is occurring. */ + @FlakyTest(bugId = 265007895) @Test - fun colorLayerIsVisibleDuringTransition() { - val bgColorLayer = FlickerComponentName("", "colorBackgroundLayer") - val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) - - testSpec.assertLayers { + fun transitionHasColorBackground() { + val backgroundColorLayer = ComponentNameMatcher("", "Animation Background") + val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) + flicker.assertLayers { this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") { - it.visibleRegion(LAUNCH_NEW_TASK_ACTIVITY).coversExactly(displayBounds) + it.visibleRegion(launchNewTaskApp.componentMatcher).coversExactly(displayBounds) } - .isInvisible(bgColorLayer) + .isInvisible(backgroundColorLayer) + .hasNoColor(backgroundColorLayer) .then() // Transitioning - .isVisible(bgColorLayer) + .isVisible(backgroundColorLayer) + .hasColor(backgroundColorLayer) .then() // Fully transitioned to simple SIMPLE_ACTIVITY + .invoke( + "SIMPLE_ACTIVITY's splashscreen coversExactly displayBounds", + isOptional = true + ) { + it.visibleRegion(ComponentSplashScreenMatcher(simpleApp.componentMatcher)) + .coversExactly(displayBounds) + } .invoke("SIMPLE_ACTIVITY coversExactly displayBounds") { - it.visibleRegion(SIMPLE_ACTIVITY).coversExactly(displayBounds) + it.visibleRegion(simpleApp.componentMatcher).coversExactly(displayBounds) } - .isInvisible(bgColorLayer) + .isInvisible(backgroundColorLayer) + .hasNoColor(backgroundColorLayer) .then() // Transitioning back - .isVisible(bgColorLayer) + .isVisible(backgroundColorLayer) + .hasColor(backgroundColorLayer) .then() // Fully transitioned back to LAUNCH_NEW_TASK_ACTIVITY + .invoke( + "LAUNCH_NEW_TASK_ACTIVITY's splashscreen coversExactly displayBounds", + isOptional = true + ) { + it.visibleRegion( + ComponentSplashScreenMatcher(launchNewTaskApp.componentMatcher) + ) + .coversExactly(displayBounds) + } .invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") { - it.visibleRegion(LAUNCH_NEW_TASK_ACTIVITY).coversExactly(displayBounds) + it.visibleRegion(launchNewTaskApp.componentMatcher).coversExactly(displayBounds) } - .isInvisible(bgColorLayer) + .isInvisible(backgroundColorLayer) + .hasNoColor(backgroundColorLayer) } } /** - * Checks that we start with the LaunchNewTask activity on top and then open up - * the SimpleActivity and then go back to the LaunchNewTask activity. + * Checks that we start with the LaunchNewTask activity on top and then open up the + * SimpleActivity and then go back to the LaunchNewTask activity. */ - @Postsubmit + @Presubmit @Test fun newTaskOpensOnTopAndThenCloses() { - testSpec.assertWm { - this.isAppWindowOnTop(LAUNCH_NEW_TASK_ACTIVITY) - .then() - .isAppWindowOnTop(SPLASH_SCREEN, isOptional = true) - .then() - .isAppWindowOnTop(SIMPLE_ACTIVITY) - .then() - .isAppWindowOnTop(SPLASH_SCREEN, isOptional = true) - .then() - .isAppWindowOnTop(LAUNCH_NEW_TASK_ACTIVITY) + flicker.assertWm { + this.isAppWindowOnTop(launchNewTaskApp.componentMatcher) + .then() + .isAppWindowOnTop(SPLASH_SCREEN, isOptional = true) + .then() + .isAppWindowOnTop(simpleApp.componentMatcher) + .then() + .isAppWindowOnTop(SPLASH_SCREEN, isOptional = true) + .then() + .isAppWindowOnTop(launchNewTaskApp.componentMatcher) } } - /** - * Checks that all parts of the screen are covered at the start and end of the transition - */ - @Postsubmit - @Test - fun entireScreenCovered() = testSpec.entireScreenCovered() - - /** - * Checks that the navbar window is visible throughout the transition - */ - @Postsubmit - @Test - fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible() - - /** - * Checks that the navbar layer is visible throughout the transition - */ - @Postsubmit - @Test - fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible() - - /** - * Checks that the status bar window is visible throughout the transition - */ - @Postsubmit - @Test - fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible() - - /** - * Checks that the status bar layer is visible throughout the transition - */ - @Postsubmit - @Test - fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible() - companion object { - private val LAUNCH_NEW_TASK_ACTIVITY = - LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent() - private val SIMPLE_ACTIVITY = SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent() - - private fun getWallpaperPackage(instrumentation: Instrumentation): FlickerComponentName? { + private fun getWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher { val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext) return wallpaperManager.wallpaperInfo?.component?.toFlickerComponent() + ?: getStaticWallpaperPackage(instrumentation) + } + + private fun getStaticWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher { + val resourceId = + Resources.getSystem() + .getIdentifier("image_wallpaper_component", "string", "android") + // frameworks/base/core/res/res/values/config.xml returns package plus class name, + // but wallpaper layer has only class name + val rawComponentMatcher = + ComponentNameMatcher.unflattenFromString( + instrumentation.targetContext.resources.getString(resourceId) + ) + + return ComponentNameMatcher(rawComponentMatcher.className) } @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 3) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt index c89e6a44ab6c..a8b80ad1d16c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt @@ -16,33 +16,21 @@ package com.android.server.wm.flicker.quickswitch -import android.app.Instrumentation +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit -import android.platform.test.annotations.RequiresDevice -import android.view.Surface -import android.view.WindowManagerPolicyConstants -import androidx.test.platform.app.InstrumentationRegistry -import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.LAUNCHER_COMPONENT -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import android.tools.common.NavBar +import android.tools.common.datatypes.Rect +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.NonResizeableAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.FlickerComponentName -import org.junit.Assume -import org.junit.Before import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -54,59 +42,43 @@ import org.junit.runners.Parameterized * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest` * * Actions: + * ``` * Launch an app [testApp1] * Launch another app [testApp2] * Swipe right from the bottom of the screen to quick switch back to the first app [testApp1] - * + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val taplInstrumentation = LauncherInstrumentation() - +open class QuickSwitchBetweenTwoAppsBackTest(flicker: FlickerTest) : BaseTest(flicker) { private val testApp1 = SimpleAppHelper(instrumentation) private val testApp2 = NonResizeableAppHelper(instrumentation) - private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) - - @Before - open fun before() { - Assume.assumeFalse(isShellTransitionsEnabled) - } - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - test { - taplInstrumentation.setExpectedRotation(testSpec.startRotation) - } - - eachRun { - testApp1.launchViaIntent(wmHelper) - wmHelper.waitForFullScreenApp(testApp1.component) - - testApp2.launchViaIntent(wmHelper) - wmHelper.waitForFullScreenApp(testApp2.component) - } - } - transitions { - taplInstrumentation.launchedAppState.quickSwitchToPreviousApp() - wmHelper.waitForFullScreenApp(testApp1.component) - wmHelper.waitForAppTransitionIdle() - wmHelper.waitForNavBarStatusBarVisible() - } + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotation(flicker.scenario.startRotation.value) + tapl.setIgnoreTaskbarVisibility(true) + testApp1.launchViaIntent(wmHelper) + testApp2.launchViaIntent(wmHelper) + startDisplayBounds = + wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found") + } + transitions { + tapl.launchedAppState.quickSwitchToPreviousApp() + wmHelper + .StateSyncBuilder() + .withFullScreenApp(testApp1) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() + } - teardown { - test { - testApp1.exit() - testApp2.exit() - } - } + teardown { + testApp1.exit(wmHelper) + testApp2.exit(wmHelper) } } @@ -116,45 +88,35 @@ open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestPa */ @Presubmit @Test - fun startsWithApp2WindowsCoverFullScreen() { - testSpec.assertWmStart { - this.frameRegion(testApp2.component).coversExactly(startDisplayBounds) - } + open fun startsWithApp2WindowsCoverFullScreen() { + flicker.assertWmStart { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) } } /** * Checks that the transition starts with [testApp2]'s layers filling/covering exactly the * entirety of the display. */ - @Presubmit + @FlakyTest(bugId = 250520840) @Test - fun startsWithApp2LayersCoverFullScreen() { - testSpec.assertLayersStart { - this.visibleRegion(testApp2.component).coversExactly(startDisplayBounds) - } + open fun startsWithApp2LayersCoverFullScreen() { + flicker.assertLayersStart { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) } } - /** - * Checks that the transition starts with [testApp2] being the top window. - */ + /** Checks that the transition starts with [testApp2] being the top window. */ @Presubmit @Test - fun startsWithApp2WindowBeingOnTop() { - testSpec.assertWmStart { - this.isAppWindowOnTop(testApp2.component) - } + open fun startsWithApp2WindowBeingOnTop() { + flicker.assertWmStart { this.isAppWindowOnTop(testApp2) } } /** - * Checks that [testApp1] windows fill the entire screen (i.e. is "fullscreen") at the end of the - * transition once we have fully quick switched from [testApp2] back to the [testApp1]. + * Checks that [testApp1] windows fill the entire screen (i.e. is "fullscreen") at the end of + * the transition once we have fully quick switched from [testApp2] back to the [testApp1]. */ @Presubmit @Test - fun endsWithApp1WindowsCoveringFullScreen() { - testSpec.assertWmEnd { - this.frameRegion(testApp1.component).coversExactly(startDisplayBounds) - } + open fun endsWithApp1WindowsCoveringFullScreen() { + flicker.assertWmEnd { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) } } /** @@ -164,21 +126,17 @@ open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestPa @Presubmit @Test fun endsWithApp1LayersCoveringFullScreen() { - testSpec.assertLayersEnd { - this.visibleRegion(testApp1.component).coversExactly(startDisplayBounds) - } + flicker.assertLayersEnd { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) } } /** - * Checks that [testApp1] is the top window at the end of the transition once we have fully quick - * switched from [testApp2] back to the [testApp1]. + * Checks that [testApp1] is the top window at the end of the transition once we have fully + * quick switched from [testApp2] back to the [testApp1]. */ @Presubmit @Test - fun endsWithApp1BeingOnTop() { - testSpec.assertWmEnd { - this.isAppWindowOnTop(testApp1.component) - } + open fun endsWithApp1BeingOnTop() { + flicker.assertWmEnd { this.isAppWindowOnTop(testApp1) } } /** @@ -187,13 +145,13 @@ open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestPa */ @Presubmit @Test - fun app1WindowBecomesAndStaysVisible() { - testSpec.assertWm { - this.isAppWindowInvisible(testApp1.component) - .then() - .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isAppWindowVisible(testApp1.component) + open fun app1WindowBecomesAndStaysVisible() { + flicker.assertWm { + this.isAppWindowInvisible(testApp1) + .then() + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(testApp1) } } @@ -203,12 +161,8 @@ open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestPa */ @Presubmit @Test - fun app1LayerBecomesAndStaysVisible() { - testSpec.assertLayers { - this.isInvisible(testApp1.component) - .then() - .isVisible(testApp1.component) - } + open fun app1LayerBecomesAndStaysVisible() { + flicker.assertLayers { this.isInvisible(testApp1).then().isVisible(testApp1) } } /** @@ -217,12 +171,8 @@ open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestPa */ @Presubmit @Test - fun app2WindowBecomesAndStaysInvisible() { - testSpec.assertWm { - this.isAppWindowVisible(testApp2.component) - .then() - .isAppWindowInvisible(testApp2.component) - } + open fun app2WindowBecomesAndStaysInvisible() { + flicker.assertWm { this.isAppWindowVisible(testApp2).then().isAppWindowInvisible(testApp2) } } /** @@ -231,12 +181,8 @@ open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestPa */ @Presubmit @Test - fun app2LayerBecomesAndStaysInvisible() { - testSpec.assertLayers { - this.isVisible(testApp2.component) - .then() - .isInvisible(testApp2.component) - } + open fun app2LayerBecomesAndStaysInvisible() { + flicker.assertLayers { this.isVisible(testApp2).then().isInvisible(testApp2) } } /** @@ -246,16 +192,16 @@ open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestPa */ @Presubmit @Test - fun app1WindowIsVisibleOnceApp2WindowIsInvisible() { - testSpec.assertWm { - this.isAppWindowVisible(testApp2.component) - .then() - // TODO: Do we actually want to test this? Seems too implementation specific... - .isAppWindowVisible(LAUNCHER_COMPONENT, isOptional = true) - .then() - .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isAppWindowVisible(testApp1.component) + open fun app1WindowIsVisibleOnceApp2WindowIsInvisible() { + flicker.assertWm { + this.isAppWindowVisible(testApp2) + .then() + // TODO: Do we actually want to test this? Seems too implementation specific... + .isAppWindowVisible(ComponentNameMatcher.LAUNCHER, isOptional = true) + .then() + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(testApp1) } } @@ -266,67 +212,41 @@ open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestPa */ @Presubmit @Test - fun app1LayerIsVisibleOnceApp2LayerIsInvisible() { - testSpec.assertLayers { - this.isVisible(testApp2.component) - .then() - .isVisible(LAUNCHER_COMPONENT, isOptional = true) - .then() - .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isVisible(testApp1.component) + open fun app1LayerIsVisibleOnceApp2LayerIsInvisible() { + flicker.assertLayers { + this.isVisible(testApp2) + .then() + .isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(testApp1) } } - /** - * Checks that the navbar window is visible throughout the entire transition. - */ - @Presubmit + /** {@inheritDoc} */ + @Ignore("Nav bar window becomes invisible during quick switch") @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsVisible() + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() - /** - * Checks that the navbar layer is visible throughout the entire transition. - */ - @Presubmit + @FlakyTest(bugId = 246284708) @Test - fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible() + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() - /** - * Checks that the navbar is always in the right position and covers the expected region. - * - * NOTE: This doesn't check that the navbar is visible or not. - */ - @Presubmit + @FlakyTest(bugId = 250518877) @Test - fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales() - - /** - * Checks that the status bar window is visible throughout the entire transition. - */ - @Presubmit - @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible() - - /** - * Checks that the status bar layer is visible throughout the entire transition. - */ - @Presubmit - @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible() + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() companion object { + private var startDisplayBounds = Rect.EMPTY + @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 3, - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ), - supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90) - ) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) + ) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTestCfArm.kt new file mode 100644 index 000000000000..f970a79abcb8 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTestCfArm.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.quickswitch + +import android.tools.common.NavBar +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class QuickSwitchBetweenTwoAppsBackTestCfArm(flicker: FlickerTest) : + QuickSwitchBetweenTwoAppsBackTest(flicker) { + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt deleted file mode 100644 index b9fef085da29..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2021 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.android.server.wm.flicker.quickswitch - -import android.platform.test.annotations.RequiresDevice -import androidx.test.filters.FlakyTest -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import org.junit.Assume -import org.junit.Before -import org.junit.FixMethodOrder -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test quick switching back to previous app from last opened app - * - * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest` - * - * Actions: - * Launch an app [testApp1] - * Launch another app [testApp2] - * Swipe right from the bottom of the screen to quick switch back to the first app [testApp1] - * - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -@FlakyTest(bugId = 228009808) -open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(testSpec: FlickerTestParameter) - : QuickSwitchBetweenTwoAppsBackTest(testSpec) { - @Before - override fun before() { - Assume.assumeTrue(isShellTransitionsEnabled) - } -} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt index 725d2c3d818c..96cd8ffa3eff 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt @@ -16,33 +16,21 @@ package com.android.server.wm.flicker.quickswitch -import android.app.Instrumentation +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit -import android.platform.test.annotations.RequiresDevice -import android.view.Surface -import android.view.WindowManagerPolicyConstants -import androidx.test.platform.app.InstrumentationRegistry -import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.LAUNCHER_COMPONENT -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import android.tools.common.NavBar +import android.tools.common.datatypes.Rect +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.NonResizeableAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.FlickerComponentName -import com.android.server.wm.traces.common.Rect -import org.junit.Assume -import org.junit.Before import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -54,68 +42,51 @@ import org.junit.runners.Parameterized * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest` * * Actions: + * ``` * Launch an app [testApp1] * Launch another app [testApp2] * Swipe right from the bottom of the screen to quick switch back to the first app [testApp1] * Swipe left from the bottom of the screen to quick switch forward to the second app [testApp2] + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val taplInstrumentation = LauncherInstrumentation() - +open class QuickSwitchBetweenTwoAppsForwardTest(flicker: FlickerTest) : BaseTest(flicker) { private val testApp1 = SimpleAppHelper(instrumentation) private val testApp2 = NonResizeableAppHelper(instrumentation) - @Before - open fun before() { - Assume.assumeFalse(isShellTransitionsEnabled) - } - - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - test { - taplInstrumentation.setExpectedRotation(testSpec.startRotation) - } - - eachRun { - testApp1.launchViaIntent(wmHelper) - wmHelper.waitForFullScreenApp(testApp1.component) - - testApp2.launchViaIntent(wmHelper) - wmHelper.waitForFullScreenApp(testApp2.component) - - startDisplayBounds = wmHelper.currentState.layerState - .displays.firstOrNull { !it.isVirtual } - ?.layerStackSpace - ?: error("Display not found") - - taplInstrumentation.launchedAppState.quickSwitchToPreviousApp() - - wmHelper.waitForFullScreenApp(testApp1.component) - wmHelper.waitForAppTransitionIdle() - } - } - transitions { - taplInstrumentation.launchedAppState.quickSwitchToPreviousAppSwipeLeft() - - wmHelper.waitForFullScreenApp(testApp2.component) - wmHelper.waitForAppTransitionIdle() - wmHelper.waitForNavBarStatusBarVisible() - } + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotation(flicker.scenario.startRotation.value) + + testApp1.launchViaIntent(wmHelper) + testApp2.launchViaIntent(wmHelper) + tapl.launchedAppState.quickSwitchToPreviousApp() + wmHelper + .StateSyncBuilder() + .withFullScreenApp(testApp1) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() + startDisplayBounds = + wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found") + } + transitions { + tapl.launchedAppState.quickSwitchToPreviousAppSwipeLeft() + wmHelper + .StateSyncBuilder() + .withFullScreenApp(testApp2) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() + } - teardown { - test { - testApp1.exit(wmHelper) - testApp2.exit(wmHelper) - } - } + teardown { + testApp1.exit(wmHelper) + testApp2.exit(wmHelper) } } @@ -126,8 +97,8 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes @Presubmit @Test open fun startsWithApp1WindowsCoverFullScreen() { - testSpec.assertWmStart { - this.frameRegion(testApp1.component, FlickerComponentName.LETTERBOX) + flicker.assertWmStart { + this.visibleRegion(testApp1.or(ComponentNameMatcher.LETTERBOX)) .coversExactly(startDisplayBounds) } } @@ -136,23 +107,17 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes * Checks that the transition starts with [testApp1]'s layers filling/covering exactly the * entirety of the display. */ - @Presubmit + @FlakyTest(bugId = 250522691) @Test open fun startsWithApp1LayersCoverFullScreen() { - testSpec.assertLayersStart { - this.visibleRegion(testApp1.component).coversExactly(startDisplayBounds) - } + flicker.assertLayersStart { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) } } - /** - * Checks that the transition starts with [testApp1] being the top window. - */ + /** Checks that the transition starts with [testApp1] being the top window. */ @Presubmit @Test open fun startsWithApp1WindowBeingOnTop() { - testSpec.assertWmStart { - this.isAppWindowOnTop(testApp1.component) - } + flicker.assertWmStart { this.isAppWindowOnTop(testApp1) } } /** @@ -162,9 +127,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes @Presubmit @Test open fun endsWithApp2WindowsCoveringFullScreen() { - testSpec.assertWmEnd { - this.frameRegion(testApp2.component).coversExactly(startDisplayBounds) - } + flicker.assertWmEnd { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) } } /** @@ -174,8 +137,8 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes @Presubmit @Test open fun endsWithApp2LayersCoveringFullScreen() { - testSpec.assertLayersEnd { - this.visibleRegion(testApp2.component, FlickerComponentName.LETTERBOX) + flicker.assertLayersEnd { + this.visibleRegion(testApp2.or(ComponentNameMatcher.LETTERBOX)) .coversExactly(startDisplayBounds) } } @@ -187,9 +150,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes @Presubmit @Test open fun endsWithApp2BeingOnTop() { - testSpec.assertWmEnd { - this.isAppWindowOnTop(testApp2.component) - } + flicker.assertWmEnd { this.isAppWindowOnTop(testApp2) } } /** @@ -199,12 +160,12 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes @Presubmit @Test open fun app2WindowBecomesAndStaysVisible() { - testSpec.assertWm { - this.isAppWindowInvisible(testApp2.component) - .then() - .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isAppWindowVisible(testApp2.component) + flicker.assertWm { + this.isAppWindowInvisible(testApp2) + .then() + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(testApp2) } } @@ -215,11 +176,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes @Presubmit @Test open fun app2LayerBecomesAndStaysVisible() { - testSpec.assertLayers { - this.isInvisible(testApp2.component) - .then() - .isVisible(testApp2.component) - } + flicker.assertLayers { this.isInvisible(testApp2).then().isVisible(testApp2) } } /** @@ -229,11 +186,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes @Presubmit @Test open fun app1WindowBecomesAndStaysInvisible() { - testSpec.assertWm { - this.isAppWindowVisible(testApp1.component) - .then() - .isAppWindowInvisible(testApp1.component) - } + flicker.assertWm { this.isAppWindowVisible(testApp1).then().isAppWindowInvisible(testApp1) } } /** @@ -243,11 +196,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes @Presubmit @Test open fun app1LayerBecomesAndStaysInvisible() { - testSpec.assertLayers { - this.isVisible(testApp1.component) - .then() - .isInvisible(testApp1.component) - } + flicker.assertLayers { this.isVisible(testApp1).then().isInvisible(testApp1) } } /** @@ -258,14 +207,14 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes @Presubmit @Test open fun app2WindowIsVisibleOnceApp1WindowIsInvisible() { - testSpec.assertWm { - this.isAppWindowVisible(testApp1.component) - .then() - .isAppWindowVisible(LAUNCHER_COMPONENT, isOptional = true) - .then() - .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isAppWindowVisible(testApp2.component) + flicker.assertWm { + this.isAppWindowVisible(testApp1) + .then() + .isAppWindowVisible(ComponentNameMatcher.LAUNCHER, isOptional = true) + .then() + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(testApp2) } } @@ -277,78 +226,45 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes @Presubmit @Test open fun app2LayerIsVisibleOnceApp1LayerIsInvisible() { - testSpec.assertLayers { - this.isVisible(testApp1.component) - .then() - .isVisible(LAUNCHER_COMPONENT, isOptional = true) - .then() - .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isVisible(testApp2.component) + flicker.assertLayers { + this.isVisible(testApp1) + .then() + .isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(testApp2) } } - /** - * Checks that the navbar window is visible throughout the entire transition. - */ + /** {@inheritDoc} */ @Presubmit @Test - open fun navBarWindowIsAlwaysVisible() { - testSpec.navBarWindowIsVisible() - } + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() - /** - * Checks that the navbar layer is visible throughout the entire transition. - */ - @Presubmit + /** {@inheritDoc} */ + @Ignore("Nav bar window becomes invisible during quick switch") @Test - open fun navBarLayerAlwaysIsVisible() { - testSpec.navBarLayerIsVisible() - } + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() - /** - * Checks that the navbar is always in the right position and covers the expected region. - * - * NOTE: This doesn't check that the navbar is visible or not. - */ - @Presubmit + @FlakyTest(bugId = 246284708) @Test - open fun navbarIsAlwaysInRightPosition() { - testSpec.navBarLayerRotatesAndScales() - } + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() - /** - * Checks that the status bar window is visible throughout the entire transition. - */ - @Presubmit - @Test - open fun statusBarWindowIsAlwaysVisible() { - testSpec.statusBarWindowIsVisible() - } - - /** - * Checks that the status bar layer is visible throughout the entire transition. - */ - @Presubmit + @FlakyTest(bugId = 250518877) @Test - open fun statusBarLayerIsAlwaysVisible() { - testSpec.statusBarLayerIsVisible() - } + override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() companion object { private var startDisplayBounds = Rect.EMPTY @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 3, - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ), - supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90) - ) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) + ) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestCfArm.kt new file mode 100644 index 000000000000..9f48cdae20f1 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTestCfArm.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.quickswitch + +import android.tools.common.NavBar +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class QuickSwitchBetweenTwoAppsForwardTestCfArm(flicker: FlickerTest) : + QuickSwitchBetweenTwoAppsForwardTest(flicker) { + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt deleted file mode 100644 index 4b8a8c80cd45..000000000000 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2021 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.android.server.wm.flicker.quickswitch - -import android.platform.test.annotations.RequiresDevice -import androidx.test.filters.FlakyTest -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled -import org.junit.Assume -import org.junit.Before -import org.junit.FixMethodOrder -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test quick switching back to previous app from last opened app - * - * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest` - * - * Actions: - * Launch an app [testApp1] - * Launch another app [testApp2] - * Swipe right from the bottom of the screen to quick switch back to the first app [testApp1] - * Swipe left from the bottom of the screen to quick switch forward to the second app [testApp2] - */ -@RequiresDevice -@RunWith(Parameterized::class) -@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -@FlakyTest(bugId = 228009808) -open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(testSpec: FlickerTestParameter) - : QuickSwitchBetweenTwoAppsForwardTest(testSpec) { - @Before - override fun before() { - Assume.assumeTrue(isShellTransitionsEnabled) - } -} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt index cc4a4b2d38aa..7e935f03aeb1 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt @@ -16,30 +16,21 @@ package com.android.server.wm.flicker.quickswitch -import android.app.Instrumentation +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit -import android.platform.test.annotations.RequiresDevice -import android.view.Surface -import android.view.WindowManagerPolicyConstants -import androidx.test.platform.app.InstrumentationRegistry -import com.android.launcher3.tapl.LauncherInstrumentation -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.LAUNCHER_COMPONENT -import com.android.server.wm.flicker.annotation.Group1 -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.entireScreenCovered +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.datatypes.Rect +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -51,52 +42,47 @@ import org.junit.runners.Parameterized * To run this test: `atest FlickerTests:QuickSwitchFromLauncherTest` * * Actions: + * ``` * Launch an app * Navigate home to show launcher * Swipe right from the bottom of the screen to quick switch back to the app - * + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group1 -class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val taplInstrumentation = LauncherInstrumentation() - +open class QuickSwitchFromLauncherTest(flicker: FlickerTest) : BaseTest(flicker) { private val testApp = SimpleAppHelper(instrumentation) - private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - setup { - test { - taplInstrumentation.setExpectedRotation(testSpec.startRotation) - } + tapl.setExpectedRotation(flicker.scenario.startRotation.value) - eachRun { - testApp.launchViaIntent(wmHelper) - device.pressHome() - wmHelper.waitForHomeActivityVisible() - wmHelper.waitForWindowSurfaceDisappeared(testApp.component) - } - } - transitions { - taplInstrumentation.workspace.quickSwitchToPreviousApp() - wmHelper.waitForFullScreenApp(testApp.component) - wmHelper.waitForAppTransitionIdle() - wmHelper.waitForNavBarStatusBarVisible() - } + testApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper + .StateSyncBuilder() + .withHomeActivityVisible() + .withWindowSurfaceDisappeared(testApp) + .waitForAndVerify() - teardown { - eachRun { - testApp.exit() - } - } + startDisplayBounds = + wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found") + } + transitions { + tapl.workspace.quickSwitchToPreviousApp() + wmHelper + .StateSyncBuilder() + .withFullScreenApp(testApp) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() } + teardown { testApp.exit(wmHelper) } } /** @@ -106,9 +92,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun endsWithAppWindowsCoveringFullScreen() { - testSpec.assertWmEnd { - this.frameRegion(testApp.component).coversExactly(startDisplayBounds) - } + flicker.assertWmEnd { this.visibleRegion(testApp).coversExactly(startDisplayBounds) } } /** @@ -118,9 +102,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun endsWithAppLayersCoveringFullScreen() { - testSpec.assertLayersEnd { - this.visibleRegion(testApp.component).coversExactly(startDisplayBounds) - } + flicker.assertLayersEnd { this.visibleRegion(testApp).coversExactly(startDisplayBounds) } } /** @@ -130,55 +112,48 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun endsWithAppBeingOnTop() { - testSpec.assertWmEnd { - this.isAppWindowOnTop(testApp.component) - } + flicker.assertWmEnd { this.isAppWindowOnTop(testApp) } } - /** - * Checks that the transition starts with the home activity being tagged as visible. - */ + /** Checks that the transition starts with the home activity being tagged as visible. */ @Presubmit @Test fun startsWithHomeActivityFlaggedVisible() { - testSpec.assertWmStart { - this.isHomeActivityVisible() - } + flicker.assertWmStart { this.isHomeActivityVisible() } } /** - * Checks that the transition starts with the launcher windows filling/covering exactly the - * entirety of the display. + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] windows + * filling/covering exactly display size */ @Presubmit @Test fun startsWithLauncherWindowsCoverFullScreen() { - testSpec.assertWmStart { - this.frameRegion(LAUNCHER_COMPONENT).coversExactly(startDisplayBounds) + flicker.assertWmStart { + this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds) } } /** - * Checks that the transition starts with the launcher layers filling/covering exactly the - * entirety of the display. + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] layers + * filling/covering exactly the display size. */ @Presubmit @Test fun startsWithLauncherLayersCoverFullScreen() { - testSpec.assertLayersStart { - this.visibleRegion(LAUNCHER_COMPONENT).coversExactly(startDisplayBounds) + flicker.assertLayersStart { + this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds) } } /** - * Checks that the transition starts with the launcher being the top window. + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] being the top + * window. */ @Presubmit @Test fun startsWithLauncherBeingOnTop() { - testSpec.assertWmStart { - this.isAppWindowOnTop(LAUNCHER_COMPONENT) - } + flicker.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) } } /** @@ -188,9 +163,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun endsWithHomeActivityFlaggedInvisible() { - testSpec.assertWmEnd { - this.isHomeActivityInvisible() - } + flicker.assertWmEnd { this.isHomeActivityInvisible() } } /** @@ -200,11 +173,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun appWindowBecomesAndStaysVisible() { - testSpec.assertWm { - this.isAppWindowInvisible(testApp.component) - .then() - .isAppWindowVisible(testApp.component) - } + flicker.assertWm { this.isAppWindowInvisible(testApp).then().isAppWindowVisible(testApp) } } /** @@ -214,130 +183,101 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun appLayerBecomesAndStaysVisible() { - testSpec.assertLayers { - this.isInvisible(testApp.component) - .then() - .isVisible(testApp.component) - } + flicker.assertLayers { this.isInvisible(testApp).then().isVisible(testApp) } } /** - * Checks that the launcher window starts off visible and becomes invisible at some point before - * the end of the transition and then stays invisible until the end of the transition. + * Checks that the [ComponentNameMatcher.LAUNCHER] window starts off visible and becomes + * invisible at some point before the end of the transition and then stays invisible until the + * end of the transition. */ @Presubmit @Test fun launcherWindowBecomesAndStaysInvisible() { - testSpec.assertWm { - this.isAppWindowOnTop(LAUNCHER_COMPONENT) - .then() - .isAppWindowNotOnTop(LAUNCHER_COMPONENT) + flicker.assertWm { + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) + .then() + .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) } } /** - * Checks that the launcher layer starts off visible and becomes invisible at some point before - * the end of the transition and then stays invisible until the end of the transition. + * Checks that the [ComponentNameMatcher.LAUNCHER] layer starts off visible and becomes + * invisible at some point before the end of the transition and then stays invisible until the + * end of the transition. */ @Presubmit @Test fun launcherLayerBecomesAndStaysInvisible() { - testSpec.assertLayers { - this.isVisible(LAUNCHER_COMPONENT) - .then() - .isInvisible(LAUNCHER_COMPONENT) + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isInvisible(ComponentNameMatcher.LAUNCHER) } } /** - * Checks that the launcher window is visible at least until the app window is visible. Ensures - * that at any point, either the launcher or [testApp] windows are at least partially visible. + * Checks that the [ComponentNameMatcher.LAUNCHER] window is visible at least until the app + * window is visible. Ensures that at any point, either the launcher or [testApp] windows are at + * least partially visible. */ @Presubmit @Test fun appWindowIsVisibleOnceLauncherWindowIsInvisible() { - testSpec.assertWm { - this.isAppWindowOnTop(LAUNCHER_COMPONENT) - .then() - .isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isAppWindowVisible(testApp.component) + flicker.assertWm { + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) + .then() + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(testApp) } } /** - * Checks that the launcher layer is visible at least until the app layer is visible. Ensures - * that at any point, either the launcher or [testApp] layers are at least partially visible. + * Checks that the [ComponentNameMatcher.LAUNCHER] layer is visible at least until the app layer + * is visible. Ensures that at any point, either the launcher or [testApp] layers are at least + * partially visible. */ @Presubmit @Test fun appLayerIsVisibleOnceLauncherLayerIsInvisible() { - testSpec.assertLayers { - this.isVisible(LAUNCHER_COMPONENT) - .then() - .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) - .then() - .isVisible(testApp.component) + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(testApp) } } - /** - * Checks that the navbar window is visible throughout the entire transition. - */ - @Presubmit - @Test - fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsVisible() - - /** - * Checks that the navbar layer is visible throughout the entire transition. - */ - @Presubmit - @Test - fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible() - - /** - * Checks that the navbar is always in the right position and covers the expected region. - * - * NOTE: This doesn't check that the navbar is visible or not. - */ + /** {@inheritDoc} */ @Presubmit @Test - fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales() + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() - /** - * Checks that the status bar window is visible throughout the entire transition. - */ - @Presubmit + /** {@inheritDoc} */ + @Ignore("Nav bar window becomes invisible during quick switch") @Test - fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible() + override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() - /** - * Checks that the status bar layer is visible throughout the entire transition. - */ - @Presubmit + @FlakyTest(bugId = 246285528) @Test - fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible() - - /** - * Checks that the screen is always fully covered by visible layers throughout the transition. - */ - @Presubmit - @Test - fun screenIsAlwaysFilled() = testSpec.entireScreenCovered() + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } companion object { + /** {@inheritDoc} */ + private var startDisplayBounds = Rect.EMPTY + @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests( - repetitions = 3, - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ), - // TODO: Test with 90 rotation - supportedRotations = listOf(Surface.ROTATION_0) - ) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL), + // TODO: Test with 90 rotation + supportedRotations = listOf(Rotation.ROTATION_0) + ) } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTestCfArm.kt new file mode 100644 index 000000000000..af671df194ae --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTestCfArm.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.quickswitch + +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class QuickSwitchFromLauncherTestCfArm(flicker: FlickerTest) : + QuickSwitchFromLauncherTest(flicker) { + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL), + // TODO: Test with 90 rotation + supportedRotations = listOf(Rotation.ROTATION_0) + ) + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index f0d16f3edb11..ef75c6105b53 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -16,19 +16,15 @@ package com.android.server.wm.flicker.rotation +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit -import androidx.test.filters.FlakyTest +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 -import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.SimpleAppHelper -import com.android.server.wm.flicker.statusBarLayerIsVisible -import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.server.wm.flicker.statusBarWindowIsVisible -import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -39,54 +35,62 @@ import org.junit.runners.Parameterized * Test opening an app and cycling through app rotations * * Currently runs: + * ``` * 0 -> 90 degrees * 90 -> 0 degrees + * ``` * * Actions: + * ``` * Launch an app (via intent) * Set initial device orientation * Start tracing * Change device orientation * Stop tracing + * ``` * * To run this test: `atest FlickerTests:ChangeAppRotationTest` * * To run only the presubmit assertions add: `-- + * + * ``` * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` + * ``` * * To run only the postsubmit assertions add: `-- + * + * ``` * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` + * ``` * * To run only the flaky assertions add: `-- + * + * ``` * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` + * ``` * * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [RotationTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 -class ChangeAppRotationTest( - testSpec: FlickerTestParameter -) : RotationTransition(testSpec) { +open class ChangeAppRotationTest(flicker: FlickerTest) : RotationTransition(flicker) { override val testApp = SimpleAppHelper(instrumentation) override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) - setup { - test { - testApp.launchViaIntent(wmHelper) - } - } + setup { testApp.launchViaIntent(wmHelper) } } /** @@ -96,29 +100,27 @@ class ChangeAppRotationTest( @Presubmit @Test fun focusChanges() { - testSpec.assertEventLog { - this.focusChanges(testApp.`package`) - } + flicker.assertEventLog { this.focusChanges(testApp.`package`) } } /** - * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition, - * doesn't flicker, and disappears before the transition is complete + * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't + * flicker, and disappears before the transition is complete */ fun rotationLayerAppearsAndVanishesAssertion() { - testSpec.assertLayers { - this.isVisible(testApp.component) + flicker.assertLayers { + this.isVisible(testApp) .then() - .isVisible(FlickerComponentName.ROTATION) + .isVisible(ComponentNameMatcher.ROTATION) .then() - .isVisible(testApp.component) - .isInvisible(FlickerComponentName.ROTATION) + .isVisible(testApp) + .isInvisible(ComponentNameMatcher.ROTATION) } } /** - * Checks that the [FlickerComponentName.ROTATION] layer appears during the transition, - * doesn't flicker, and disappears before the transition is complete + * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't + * flicker, and disappears before the transition is complete */ @Presubmit @Test @@ -126,57 +128,25 @@ class ChangeAppRotationTest( rotationLayerAppearsAndVanishesAssertion() } - /** - * Checks that the status bar window is visible and above the app windows in all WM - * trace entries - */ - @Presubmit - @Test - fun statusBarWindowIsVisible() { - testSpec.statusBarWindowIsVisible() - } - - /** - * Checks that the status bar layer is visible at the start and end of the transition - */ - @Presubmit - @Test - fun statusBarLayerIsVisible() { - testSpec.statusBarLayerIsVisible() - } - - /** - * Checks the position of the status bar at the start and end of the transition - */ - @FlakyTest(bugId = 206753786) - @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() - - /** {@inheritDoc} */ - @FlakyTest @Test - override fun navBarLayerRotatesAndScales() { - super.navBarLayerRotatesAndScales() + @PlatinumTest(focusArea = "framework") + override fun cujCompleted() { + super.cujCompleted() + focusChanges() + rotationLayerAppearsAndVanishes() } - /** {@inheritDoc} */ - @FlakyTest - @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() - companion object { /** * Creates the test configurations. * - * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * See [FlickerTestFactory.rotationTests] for configuring screen orientation and navigation + * modes. */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigRotationTests(repetitions = 3) + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.rotationTests() } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt new file mode 100644 index 000000000000..0e6b20fc21c6 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTestCfArm.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.rotation + +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class ChangeAppRotationTestCfArm(flicker: FlickerTest) : ChangeAppRotationTest(flicker) { + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.rotationTests] for configuring screen orientation and navigation + * modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.rotationTests() + } + } +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt index 0becadf630e1..fe9da335a675 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt @@ -16,141 +16,67 @@ package com.android.server.wm.flicker.rotation -import android.app.Instrumentation import android.platform.test.annotations.Presubmit -import androidx.test.platform.app.InstrumentationRegistry -import com.android.server.wm.flicker.FlickerBuilderProvider -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.helpers.StandardAppHelper +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.navBarLayerIsVisible -import com.android.server.wm.flicker.navBarLayerRotatesAndScales -import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.entireScreenCovered -import com.android.server.wm.traces.common.FlickerComponentName import org.junit.Test -/** - * Base class for app rotation tests - */ -abstract class RotationTransition(protected val testSpec: FlickerTestParameter) { +/** Base class for app rotation tests */ +abstract class RotationTransition(flicker: FlickerTest) : BaseTest(flicker) { protected abstract val testApp: StandardAppHelper - protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - - protected open val transition: FlickerBuilder.() -> Unit = { - setup { - eachRun { - this.setRotation(testSpec.startRotation) - } - } - teardown { - test { - testApp.exit() - } - } - transitions { - this.setRotation(testSpec.endRotation) - } - } - - /** - * Entry point for the test runner. It will use this method to initialize and cache - * flicker executions - */ - @FlickerBuilderProvider - fun buildFlicker(): FlickerBuilder { - return FlickerBuilder(instrumentation).apply { - transition() - } - } - - /** - * Checks that the navigation bar window is visible and above the app windows in all WM - * trace entries - */ - @Presubmit - @Test - open fun navBarWindowIsVisible() { - testSpec.navBarWindowIsVisible() + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { this.setRotation(flicker.scenario.startRotation) } + teardown { testApp.exit(wmHelper) } + transitions { this.setRotation(flicker.scenario.endRotation) } } - /** - * Checks that the navigation bar layer is visible at the start and end of the transition - */ + /** {@inheritDoc} */ @Presubmit @Test - open fun navBarLayerIsVisible() { - testSpec.navBarLayerIsVisible() - } - - /** - * Checks the position of the navigation bar at the start and end of the transition - */ - @Presubmit - @Test - open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales() - - /** - * Checks that all layers that are visible on the trace, are visible for at least 2 - * consecutive entries. - */ - @Presubmit - @Test - open fun visibleLayersShownMoreThanOneConsecutiveEntry() { - testSpec.assertLayers { + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry( - ignoreLayers = listOf(FlickerComponentName.SPLASH_SCREEN, - FlickerComponentName.SNAPSHOT, - FlickerComponentName("", "SecondaryHomeHandle") - ) + ignoreLayers = + listOf( + ComponentNameMatcher.SPLASH_SCREEN, + ComponentNameMatcher.SNAPSHOT, + ComponentNameMatcher("", "SecondaryHomeHandle") + ) ) } } - /** - * Checks that all windows that are visible on the trace, are visible for at least 2 - * consecutive entries. - */ - @Presubmit - @Test - open fun visibleWindowsShownMoreThanOneConsecutiveEntry() { - testSpec.assertWm { - this.visibleWindowsShownMoreThanOneConsecutiveEntry() - } - } - - /** - * Checks that all parts of the screen are covered during the transition - */ - @Presubmit - @Test - open fun entireScreenCovered() = testSpec.entireScreenCovered() - - /** - * Checks that [testApp] layer covers the entire screen at the start of the transition - */ + /** Checks that [testApp] layer covers the entire screen at the start of the transition */ @Presubmit @Test open fun appLayerRotates_StartingPos() { - testSpec.assertLayersStart { + flicker.assertLayersStart { this.entry.displays.map { display -> - this.visibleRegion(testApp.component).coversExactly(display.layerStackSpace) + this.visibleRegion(testApp).coversExactly(display.layerStackSpace) } } } - /** - * Checks that [testApp] layer covers the entire screen at the end of the transition - */ + /** Checks that [testApp] layer covers the entire screen at the end of the transition */ @Presubmit @Test open fun appLayerRotates_EndingPos() { - testSpec.assertLayersEnd { + flicker.assertLayersEnd { this.entry.displays.map { display -> - this.visibleRegion(testApp.component).coversExactly(display.layerStackSpace) + this.visibleRegion(testApp).coversExactly(display.layerStackSpace) } } } + + override fun cujCompleted() { + super.cujCompleted() + appLayerRotates_StartingPos() + appLayerRotates_EndingPos() + } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index fac5baf7a2f9..f654bdd48e89 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -16,19 +16,20 @@ package com.android.server.wm.flicker.rotation +import android.platform.test.annotations.PlatinumTest import android.platform.test.annotations.Presubmit -import android.platform.test.annotations.RequiresDevice +import android.tools.common.ScenarioBuilder +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory import android.view.WindowManager -import androidx.test.filters.FlakyTest -import com.android.server.wm.flicker.FlickerParametersRunnerFactory -import com.android.server.wm.flicker.FlickerTestParameter -import com.android.server.wm.flicker.FlickerTestParameterFactory -import com.android.server.wm.flicker.annotation.Group3 -import com.android.server.wm.flicker.dsl.FlickerBuilder +import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -37,205 +38,241 @@ import org.junit.runners.Parameterized /** * Test opening an app and cycling through app rotations using seamless rotations * - * Currently runs: + * Currently, runs: + * ``` * 0 -> 90 degrees * 0 -> 90 degrees (with starved UI thread) * 90 -> 0 degrees * 90 -> 0 degrees (with starved UI thread) + * ``` * * Actions: + * ``` * Launch an app in fullscreen and supporting seamless rotation (via intent) * Set initial device orientation * Start tracing * Change device orientation * Stop tracing + * ``` * * To run this test: `atest FlickerTests:SeamlessAppRotationTest` * * To run only the presubmit assertions add: `-- + * + * ``` * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` + * ``` * * To run only the postsubmit assertions add: `-- + * + * ``` * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` + * ``` * * To run only the flaky assertions add: `-- + * + * ``` * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` + * ``` * * Notes: + * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) * are inherited [RotationTransition] * 2. Part of the test setup occurs automatically via * [com.android.server.wm.flicker.TransitionRunnerWithRules], * including configuring navigation mode, initial orientation and ensuring no * apps are running before setup + * ``` */ @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@Group3 -open class SeamlessAppRotationTest( - testSpec: FlickerTestParameter -) : RotationTransition(testSpec) { +open class SeamlessAppRotationTest(flicker: FlickerTest) : RotationTransition(flicker) { override val testApp = SeamlessRotationAppHelper(instrumentation) + /** {@inheritDoc} */ override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) setup { - test { - testApp.launchViaIntent(wmHelper, - stringExtras = mapOf(ActivityOptions.EXTRA_STARVE_UI_THREAD - to testSpec.starveUiThread.toString()) - ) - } + testApp.launchViaIntent( + wmHelper, + stringExtras = + mapOf( + ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD to + flicker.starveUiThread.toString() + ) + ) } } - /** - * Checks that [testApp] window is always in full screen - */ + /** Checks that [testApp] window is always in full screen */ @Presubmit @Test fun appWindowFullScreen() { - testSpec.assertWm { + flicker.assertWm { this.invoke("isFullScreen") { - val appWindow = it.windowState(testApp.`package`) - val flags = appWindow.windowState?.attributes?.flags ?: 0 - appWindow.verify("isFullScreen") + val appWindow = + it.windowState(testApp.`package`) + ?: error("App window for package ${testApp.`package`} not found") + val flags = appWindow.windowState.attributes.flags + appWindow + .check { "isFullScreen" } .that(flags.and(WindowManager.LayoutParams.FLAG_FULLSCREEN)) - .isGreaterThan(0) + .isGreater(0) } } } - /** - * Checks that [testApp] window is always with seamless rotation - */ + /** Checks that [testApp] window is always with seamless rotation */ @Presubmit @Test fun appWindowSeamlessRotation() { - testSpec.assertWm { + flicker.assertWm { this.invoke("isRotationSeamless") { - val appWindow = it.windowState(testApp.`package`) - val rotationAnimation = appWindow.windowState?.attributes?.rotationAnimation ?: 0 - appWindow.verify("isRotationSeamless") - .that(rotationAnimation - .and(WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS)) - .isGreaterThan(0) + val appWindow = + it.windowState(testApp.`package`) + ?: error("App window for package ${testApp.`package`} not found") + val rotationAnimation = appWindow.windowState.attributes.rotationAnimation + appWindow + .check { "isRotationSeamless" } + .that( + rotationAnimation.and( + WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS + ) + ) + .isGreater(0) } } } - /** - * Checks that [testApp] window is always visible - */ + /** Checks that [testApp] window is always visible */ @Presubmit @Test fun appLayerAlwaysVisible() { - testSpec.assertLayers { - isVisible(testApp.component) - } + flicker.assertLayers { isVisible(testApp) } } - /** - * Checks that [testApp] layer covers the entire screen during the whole transition - */ + /** Checks that [testApp] layer covers the entire screen during the whole transition */ @Presubmit @Test fun appLayerRotates() { - testSpec.assertLayers { + flicker.assertLayers { this.invoke("entireScreenCovered") { entry -> entry.entry.displays.map { display -> - entry.visibleRegion(testApp.component).coversExactly(display.layerStackSpace) + entry.visibleRegion(testApp).coversExactly(display.layerStackSpace) } } } } + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. App is full screen") + override fun statusBarLayerPositionAtStartAndEnd() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. App is full screen") + override fun statusBarLayerIsVisibleAtStartAndEnd() {} + + /** {@inheritDoc} */ + @Test + @Ignore("Not applicable to this CUJ. App is full screen") + override fun statusBarWindowIsAlwaysVisible() {} + /** - * Checks that the [FlickerComponentName.STATUS_BAR] window is invisible during the whole + * Checks that the [ComponentNameMatcher.STATUS_BAR] window is invisible during the whole * transition */ @Presubmit @Test fun statusBarWindowIsAlwaysInvisible() { - testSpec.assertWm { - this.isAboveAppWindowInvisible(FlickerComponentName.STATUS_BAR) - } + flicker.assertWm { this.isAboveAppWindowInvisible(ComponentNameMatcher.STATUS_BAR) } } /** - * Checks that the [FlickerComponentName.STATUS_BAR] layer is invisible during the whole + * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is invisible during the whole * transition */ @Presubmit @Test fun statusBarLayerIsAlwaysInvisible() { - testSpec.assertLayers { - this.isInvisible(FlickerComponentName.STATUS_BAR) - } + flicker.assertLayers { this.isInvisible(ComponentNameMatcher.STATUS_BAR) } } - /** - * Checks that the focus doesn't change during animation - */ + /** Checks that the focus doesn't change during animation */ @Presubmit @Test fun focusDoesNotChange() { - testSpec.assertEventLog { - this.focusDoesNotChange() - } + flicker.assertEventLog { this.focusDoesNotChange() } } - /** {@inheritDoc} */ - @FlakyTest @Test - override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + @PlatinumTest(focusArea = "framework") + override fun cujCompleted() { + appWindowFullScreen() + appWindowSeamlessRotation() + focusDoesNotChange() + statusBarLayerIsAlwaysInvisible() + statusBarWindowIsAlwaysInvisible() + appLayerRotates_StartingPos() + appLayerRotates_EndingPos() + entireScreenCovered() + visibleLayersShownMoreThanOneConsecutiveEntry() + visibleWindowsShownMoreThanOneConsecutiveEntry() + + runAndIgnoreAssumptionViolation { appLayerRotates() } + runAndIgnoreAssumptionViolation { appLayerAlwaysVisible() } + runAndIgnoreAssumptionViolation { navBarLayerIsVisibleAtStartAndEnd() } + runAndIgnoreAssumptionViolation { navBarWindowIsAlwaysVisible() } + runAndIgnoreAssumptionViolation { navBarLayerPositionAtStartAndEnd() } + runAndIgnoreAssumptionViolation { taskBarLayerIsVisibleAtStartAndEnd() } + runAndIgnoreAssumptionViolation { taskBarWindowIsAlwaysVisible() } + } companion object { - private val FlickerTestParameter.starveUiThread - get() = config.getOrDefault(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) as Boolean + private val FlickerTest.starveUiThread + get() = + getConfigValue<Boolean>(ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD) + ?: false - private fun createConfig( - sourceConfig: FlickerTestParameter, + @JvmStatic + protected fun createConfig( + sourceConfig: FlickerTest, starveUiThread: Boolean - ): FlickerTestParameter { - val newConfig = sourceConfig.config.toMutableMap() - .also { it[ActivityOptions.EXTRA_STARVE_UI_THREAD] = starveUiThread } + ): FlickerTest { + val originalScenario = sourceConfig.initialize("createConfig") val nameExt = if (starveUiThread) "_BUSY_UI_THREAD" else "" - return FlickerTestParameter(newConfig, nameOverride = "$sourceConfig$nameExt") - } - - /** - * Creates the test configurations for seamless rotation based on the default rotation - * tests from [FlickerTestParameterFactory.getConfigRotationTests], but adding an - * additional flag ([ActivityOptions.EXTRA_STARVE_UI_THREAD]) to indicate if the app - * should starve the UI thread of not - */ - @JvmStatic - private fun getConfigurations(): List<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance() - .getConfigRotationTests(repetitions = 2) - .flatMap { sourceConfig -> - val defaultRun = createConfig(sourceConfig, starveUiThread = false) - val busyUiRun = createConfig(sourceConfig, starveUiThread = true) - listOf(defaultRun, busyUiRun) - } + val newConfig = + ScenarioBuilder() + .fromScenario(originalScenario) + .withExtraConfig( + ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD, + starveUiThread + ) + .withDescriptionOverride("${originalScenario.description}$nameExt") + return FlickerTest(newConfig) } /** - * Creates the test configurations. - * - * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring - * repetitions, screen orientation and navigation modes. + * Creates the test configurations for seamless rotation based on the default rotation tests + * from [FlickerTestFactory.rotationTests], but adding a flag ( + * [ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate if the app should + * starve the UI thread of not */ @Parameterized.Parameters(name = "{0}") @JvmStatic - fun getParams(): Collection<FlickerTestParameter> { - return getConfigurations() + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.rotationTests().flatMap { sourceConfig -> + val defaultRun = createConfig(sourceConfig, starveUiThread = false) + val busyUiRun = createConfig(sourceConfig, starveUiThread = true) + listOf(defaultRun, busyUiRun) + } } } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt new file mode 100644 index 000000000000..b236d87616ab --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTestCfArm.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.rotation + +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import com.android.server.wm.flicker.testapp.ActivityOptions +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** This test should fail because of b/264518826 */ +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class SeamlessAppRotationTestCfArm(flicker: FlickerTest) : SeamlessAppRotationTest(flicker) { + companion object { + /** + * Creates the test configurations for seamless rotation based on the default rotation tests + * from [FlickerTestFactory.rotationTests], but adding a flag ( + * [ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate if the app should + * starve the UI thread of not + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.rotationTests().flatMap { sourceConfig -> + val defaultRun = createConfig(sourceConfig, starveUiThread = false) + val busyUiRun = createConfig(sourceConfig, starveUiThread = true) + listOf(defaultRun, busyUiRun) + } + } + } +} diff --git a/tests/FlickerTests/test-apps/Android.bp b/tests/FlickerTests/test-apps/Android.bp deleted file mode 100644 index e69de29bb2d1..000000000000 --- a/tests/FlickerTests/test-apps/Android.bp +++ /dev/null diff --git a/tests/FlickerTests/test-apps/flickerapp/Android.bp b/tests/FlickerTests/test-apps/flickerapp/Android.bp index 78660c04d8d4..75e35ee9c765 100644 --- a/tests/FlickerTests/test-apps/flickerapp/Android.bp +++ b/tests/FlickerTests/test-apps/flickerapp/Android.bp @@ -26,6 +26,24 @@ android_test { srcs: ["**/*.java"], sdk_version: "current", test_suites: ["device-tests"], + static_libs: [ + "androidx.annotation_annotation", + "androidx.appcompat_appcompat", + "androidx-constraintlayout_constraintlayout", + "androidx.core_core", + "androidx.fragment_fragment", + "androidx.recyclerview_recyclerview", + "androidx.test.ext.junit", + "androidx.navigation_navigation-common-ktx", + "androidx.navigation_navigation-fragment-ktx", + "androidx.navigation_navigation-runtime-ktx", + "androidx.navigation_navigation-ui-ktx", + "kotlin-stdlib", + "kotlinx-coroutines-android", + "wm-flicker-common-app-helpers", + "wm-flicker-common-assertions", + "wm-flicker-window-extensions", + ], } java_library { diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 43aa4b151548..1ec9ec9b0eda 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -15,38 +15,41 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.server.wm.flicker.testapp"> + package="com.android.server.wm.flicker.testapp"> <uses-sdk android:minSdkVersion="29" - android:targetSdkVersion="29"/> + android:targetSdkVersion="29"/> <application android:allowBackup="false" - android:supportsRtl="true"> + android:supportsRtl="true"> + <uses-library android:name="androidx.window.extensions" android:required="false"/> + <activity android:name=".SimpleActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity" - android:theme="@style/CutoutShortEdges" - android:label="SimpleApp" - android:exported="true"> + android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity" + android:theme="@style/CutoutShortEdges" + android:label="SimpleActivity" + 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=".ImeActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivity" - android:theme="@style/CutoutShortEdges" - android:label="ImeApp" - android:exported="true"> + android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivity" + android:theme="@style/CutoutShortEdges" + android:label="ImeActivity" + 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=".ImeActivityAutoFocus" - android:theme="@style/CutoutShortEdges" - android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus" - android:windowSoftInputMode="stateVisible" - android:label="ImeAppAutoFocus" - android:exported="true"> + android:theme="@style/CutoutShortEdges" + android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus" + android:windowSoftInputMode="stateVisible" + android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" + android:label="ImeAppAutoFocus" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> @@ -63,34 +66,46 @@ </intent-filter> </activity> <activity android:name=".SeamlessRotationActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity" - android:theme="@style/CutoutShortEdges" - android:configChanges="orientation|screenSize" - android:label="SeamlessApp" - android:exported="true"> + android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity" + android:theme="@style/CutoutShortEdges" + android:configChanges="orientation|screenSize" + android:label="SeamlessActivity" + 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=".NonResizeableActivity" - android:theme="@style/CutoutShortEdges" - android:resizeableActivity="false" - android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableActivity" - android:label="NonResizeableApp" - android:exported="true" - android:showOnLockScreen="true"> + android:theme="@style/CutoutShortEdges" + android:resizeableActivity="false" + android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableActivity" + android:label="NonResizeableActivity" + android:exported="true" + android:showOnLockScreen="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> - <activity android:name=".ButtonActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.ButtonActivity" - android:theme="@style/CutoutShortEdges" - android:configChanges="orientation|screenSize" - android:label="ButtonActivity" - android:exported="true"> + <activity android:name=".NonResizeablePortraitActivity" + android:theme="@style/CutoutNever" + android:resizeableActivity="false" + android:screenOrientation="portrait" + android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeablePortraitActivity" + android:label="NonResizeablePortraitActivity" + 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=".LaunchNewActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewActivity" + android:theme="@style/CutoutShortEdges" + android:configChanges="orientation|screenSize" + android:label="LaunchNewActivity" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> @@ -108,59 +123,215 @@ </intent-filter> </activity> <activity android:name=".DialogThemedActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.DialogThemedActivity" - android:configChanges="orientation|screenSize" - android:theme="@style/DialogTheme" - android:label="DialogThemedActivity" - android:exported="true"> + android:taskAffinity="com.android.server.wm.flicker.testapp.DialogThemedActivity" + android:configChanges="orientation|screenSize" + android:theme="@style/DialogTheme" + android:label="DialogThemedActivity" + 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=".PortraitOnlyActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitOnlyActivity" - android:theme="@style/CutoutShortEdges" - android:screenOrientation="portrait" - android:configChanges="orientation|screenSize" - android:exported="true"> + android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitOnlyActivity" + android:theme="@style/CutoutShortEdges" + android:screenOrientation="portrait" + android:configChanges="orientation|screenSize" + 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=".ImeEditorPopupDialogActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.ImeEditorPopupDialogActivity" - android:configChanges="orientation|screenSize" - android:theme="@style/CutoutShortEdges" - android:label="ImeEditorPopupDialogActivity" - android:exported="true"> + android:taskAffinity="com.android.server.wm.flicker.testapp.ImeEditorPopupDialogActivity" + android:configChanges="orientation|screenSize" + android:theme="@style/CutoutShortEdges" + android:label="ImeEditorPopupDialogActivity" + 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=".ShowWhenLockedActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.ShowWhenLockedActivity" - android:theme="@style/CutoutShortEdges" - android:configChanges="orientation|screenSize" - android:label="ShowWhenLockedActivity" - android:showWhenLocked="true" - android:exported="true"> + android:taskAffinity="com.android.server.wm.flicker.testapp.ShowWhenLockedActivity" + android:theme="@style/CutoutShortEdges" + android:configChanges="orientation|screenSize" + android:label="ShowWhenLockedActivity" + android:showWhenLocked="true" + 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=".NotificationActivity" - android:taskAffinity="com.android.server.wm.flicker.testapp.NotificationActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.NotificationActivity" + android:theme="@style/CutoutShortEdges" + android:configChanges="orientation|screenSize" + android:label="NotificationActivity" + 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=".ActivityEmbeddingMainActivity" + android:label="ActivityEmbedding Main" + android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" android:theme="@style/CutoutShortEdges" - android:configChanges="orientation|screenSize" + android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" 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=".ActivityEmbeddingSecondaryActivity" + android:label="ActivityEmbedding Secondary" + android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" + android:theme="@style/CutoutShortEdges" + android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" + android:exported="false"/> + <activity + android:name=".ActivityEmbeddingPlaceholderPrimaryActivity" + android:label="ActivityEmbedding Placeholder Primary" + android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" + android:theme="@style/CutoutShortEdges" + android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" + android:exported="false"> + </activity> + <activity + android:name=".ActivityEmbeddingPlaceholderSecondaryActivity" + android:label="ActivityEmbedding Placeholder Secondary" + android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" + android:theme="@style/CutoutShortEdges" + android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" + android:exported="false"/> + <activity android:name=".MailActivity" + android:exported="true" + android:label="MailActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.MailActivity" + android:theme="@style/Theme.AppCompat.Light"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".GameActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.GameActivity" + android:immersive="true" + android:theme="@android:style/Theme.NoTitleBar" + android:configChanges="screenSize" + android:label="GameActivity" + 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=".PipActivity" + android:resizeableActivity="true" + android:supportsPictureInPicture="true" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" + android:taskAffinity="com.android.server.wm.flicker.testapp.PipActivity" + android:theme="@style/CutoutShortEdges" + android:launchMode="singleTop" + android:label="PipActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LEANBACK_LAUNCHER"/> + </intent-filter> + </activity> + <activity android:name=".SplitScreenActivity" + android:resizeableActivity="true" + android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenActivity" + android:theme="@style/CutoutShortEdges" + android:label="SplitScreenPrimaryActivity" + 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=".SplitScreenSecondaryActivity" + android:resizeableActivity="true" + android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenSecondaryActivity" + android:theme="@style/CutoutShortEdges" + android:label="SplitScreenSecondaryActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + <meta-data android:name="android.app.shortcuts" + android:resource="@xml/shortcuts" /> + </activity> + <activity android:name=".SendNotificationActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.SendNotificationActivity" + android:theme="@style/CutoutShortEdges" + android:label="SendNotificationActivity" + 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=".LaunchBubbleActivity" + android:label="LaunchBubbleActivity" + android:exported="true" + android:theme="@style/CutoutShortEdges" + android:launchMode="singleTop"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <action android:name="android.intent.action.VIEW"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity + android:name=".BubbleActivity" + android:label="BubbleActivity" + android:exported="false" + android:theme="@style/CutoutShortEdges" + android:resizeableActivity="true"/> + <service + android:name=".AssistantInteractionSessionService" + android:exported="true" + android:permission="android.permission.BIND_VOICE_INTERACTION"/> + <service + android:name=".AssistantRecognitionService" + android:exported="true" + android:label="Test Voice Interaction Service"> + <intent-filter> + <action android:name="android.speech.RecognitionService"/> + <category android:name="android.intent.category.DEFAULT"/> + </intent-filter> + <meta-data + android:name="android.speech" + android:resource="@xml/recognition_service"/> + </service> + <service + android:name=".AssistantInteractionService" + android:exported="true" + android:label="Test Voice Interaction Service" + android:permission="android.permission.BIND_VOICE_INTERACTION"> + <intent-filter> + <action android:name="android.service.voice.VoiceInteractionService"/> + </intent-filter> + <meta-data + android:name="android.voice_interaction" + android:resource="@xml/interaction_service"/> + </service> </application> + <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/> </manifest> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/anim/slide_in_from_bottom.xml b/tests/FlickerTests/test-apps/flickerapp/res/anim/slide_in_from_bottom.xml new file mode 100644 index 000000000000..c0165e5c1086 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/anim/slide_in_from_bottom.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<set + xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/linear_interpolator" + android:shareInterpolator="false"> + <translate + android:duration="500" + android:fromYDelta="100%p" + android:toYDelta="0%p" /> +</set> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/drawable/bg.png b/tests/FlickerTests/test-apps/flickerapp/res/drawable/bg.png Binary files differnew file mode 100644 index 000000000000..d424a17b4157 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/drawable/bg.png diff --git a/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_bubble.xml b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_bubble.xml new file mode 100644 index 000000000000..4ea156de9f40 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_bubble.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M7.2,14.4m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/> + <path + android:fillColor="#FF000000" + android:pathData="M14.8,18m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/> + <path + android:fillColor="#FF000000" + android:pathData="M15.2,8.8m-4.8,0a4.8,4.8 0,1 1,9.6 0a4.8,4.8 0,1 1,-9.6 0"/> +</vector> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_message.xml b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_message.xml new file mode 100644 index 000000000000..45ed98c6e503 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_message.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M12,4c-4.97,0 -9,3.58 -9,8c0,1.53 0.49,2.97 1.33,4.18c0.12,0.18 0.2,0.46 0.1,0.66c-0.33,0.68 -0.79,1.52 -1.38,2.39c-0.12,0.17 0.01,0.41 0.21,0.39c0.63,-0.05 1.86,-0.26 3.38,-0.91c0.17,-0.07 0.36,-0.06 0.52,0.03C8.55,19.54 10.21,20 12,20c4.97,0 9,-3.58 9,-8S16.97,4 12,4zM16.94,11.63l-3.29,3.29c-0.13,0.13 -0.34,0.04 -0.34,-0.14v-1.57c0,-0.11 -0.1,-0.21 -0.21,-0.2c-2.19,0.06 -3.65,0.65 -5.14,1.95c-0.15,0.13 -0.38,0 -0.33,-0.19c0.7,-2.57 2.9,-4.57 5.5,-4.75c0.1,-0.01 0.18,-0.09 0.18,-0.19V8.2c0,-0.18 0.22,-0.27 0.34,-0.14l3.29,3.29C17.02,11.43 17.02,11.55 16.94,11.63z" + android:fillColor="#000000" + android:fillType="evenOdd"/> +</vector> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml new file mode 100644 index 000000000000..7c7b2cacaefb --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <Button + android:id="@+id/button_finish" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:layout_marginStart="8dp" + android:text="Finish" /> + <Button + android:id="@+id/button_new_task" + android:layout_width="wrap_content" + android:layout_height="46dp" + android:layout_marginStart="8dp" + android:text="New Task" /> + <Button + android:id="@+id/button_new_bubble" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + android:text="New Bubble" /> + + <Button + android:id="@+id/button_activity_for_result" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:layout_marginStart="8dp" + android:text="Activity For Result" /> +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml new file mode 100644 index 000000000000..3a02cadc90dd --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_base_layout.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/root_activity_layout" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml new file mode 100644 index 000000000000..d78b9a836a37 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="@android:color/holo_orange_light"> + + <Button + android:id="@+id/launch_secondary_activity_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:layout_centerHorizontal="true" + android:onClick="launchSecondaryActivity" + android:text="Launch Secondary Activity" /> + + <Button + android:id="@+id/launch_placeholder_split_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:layout_centerHorizontal="true" + android:onClick="launchPlaceholderSplit" + android:text="Launch Placeholder Split" /> + +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml index baaf7073b3a6..fa73e2c63780 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml @@ -26,14 +26,27 @@ android:layout_width="match_parent" android:imeOptions="flagNoExtractUi" android:inputType="text"/> - <Button - android:id="@+id/finish_activity_btn" + <LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="Finish activity" /> - <Button - android:id="@+id/start_dialog_themed_activity_btn" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="Start dialog themed activity" /> + android:layout_height="match_parent" + android:orientation="horizontal"> + <Button + android:id="@+id/finish_activity_btn" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Finish activity" /> + <Button + android:id="@+id/start_dialog_themed_activity_btn" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Dialog themed activity" /> + <ToggleButton + android:id="@+id/toggle_fixed_portrait_btn" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textOn="Portrait (On)" + android:textOff="Portrait (Off)" + /> + </LinearLayout> </LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml index fe7bced690f9..fe7bced690f9 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml new file mode 100644 index 000000000000..8a97433ede04 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_mail.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <fragment + android:id="@+id/main_fragment" + android:name="androidx.navigation.fragment.NavHostFragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:defaultNavHost="true" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:navGraph="@navigation/mail_navigation"></fragment> +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_main.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_main.xml new file mode 100644 index 000000000000..553c7fe08096 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_main.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="@android:color/black"> + + <Button + android:id="@+id/button_create" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:layout_centerVertical="true" + android:text="Add Bubble" /> + + <Button + android:id="@+id/button_cancel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/button_create" + android:layout_centerHorizontal="true" + android:layout_marginTop="20dp" + android:text="Cancel Bubble" /> + + <Button + android:id="@+id/button_cancel_all" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/button_cancel" + android:layout_centerHorizontal="true" + android:layout_marginTop="20dp" + android:text="Cancel All Bubble" /> +</RelativeLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml new file mode 100644 index 000000000000..f7ba45b25d48 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml @@ -0,0 +1,141 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="@android:color/holo_blue_bright"> + + <!-- All the buttons (and other clickable elements) should be arranged in a way so that it is + possible to "cycle" over all them by clicking on the D-Pad DOWN button. The way we do it + here is by arranging them this vertical LL and by relying on the nextFocusDown attribute + where things are arranged differently and to circle back up to the top once we reach the + bottom. --> + + <Button + android:id="@+id/enter_pip" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Enter PIP" + android:onClick="enterPip"/> + + <CheckBox + android:id="@+id/with_custom_actions" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="With custom actions"/> + + <RadioGroup + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:checkedButton="@id/enter_pip_on_leave_disabled"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Enter PiP on home press"/> + + <RadioButton + android:id="@+id/enter_pip_on_leave_disabled" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Disabled" + android:onClick="onAutoPipSelected"/> + + <RadioButton + android:id="@+id/enter_pip_on_leave_manual" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Via code behind" + android:onClick="onAutoPipSelected"/> + + <RadioButton + android:id="@+id/enter_pip_on_leave_autoenter" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Auto-enter PiP" + android:onClick="onAutoPipSelected"/> + </RadioGroup> + + <RadioGroup + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:checkedButton="@id/ratio_default"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Ratio"/> + + <RadioButton + android:id="@+id/ratio_default" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Default" + android:onClick="onRatioSelected"/> + + <RadioButton + android:id="@+id/ratio_square" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Square [1:1]" + android:onClick="onRatioSelected"/> + + <RadioButton + android:id="@+id/ratio_wide" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Wide [2:1]" + android:onClick="onRatioSelected"/> + + <RadioButton + android:id="@+id/ratio_tall" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Tall [1:2]" + android:onClick="onRatioSelected"/> + </RadioGroup> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Media Session"/> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <Button + android:id="@+id/media_session_start" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:nextFocusDown="@id/media_session_stop" + android:text="Start"/> + + <Button + android:id="@+id/media_session_stop" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:nextFocusDown="@id/enter_pip" + android:text="Stop"/> + + </LinearLayout> + +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml new file mode 100644 index 000000000000..79e88e49b99e --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="@android:color/holo_green_light"> + + <TextView + android:id="@+id/SplitScreenTest" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:gravity="center_vertical|center_horizontal" + android:textIsSelectable="true" + android:text="PrimaryActivity" + android:textAppearance="?android:attr/textAppearanceLarge"/> + +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml new file mode 100644 index 000000000000..ed9feaf1eade --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="@android:color/holo_blue_light"> + + <TextView + android:id="@+id/SplitScreenTest" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:gravity="center_vertical|center_horizontal" + android:text="SecondaryActivity" + android:textAppearance="?android:attr/textAppearanceLarge"/> + +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_surfaceview.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_surfaceview.xml new file mode 100644 index 000000000000..0b4693dec6e1 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_surfaceview.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2018 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. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/holo_orange_light"> + + <SurfaceView + android:id="@+id/surface_view" + android:layout_width="match_parent" + android:layout_height="match_parent"/> +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml new file mode 100644 index 000000000000..eb7f3074ebfb --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/assistant_session.xml @@ -0,0 +1,26 @@ +<!-- + ~ 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. + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <FrameLayout + android:id="@+id/vis_frame" + android:layout_width="match_parent" + android:layout_height="300dp" + android:layout_gravity="bottom" + android:background="#37474F"/> +</FrameLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_content.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_content.xml new file mode 100644 index 000000000000..fbbdc5b812ad --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_content.xml @@ -0,0 +1,21 @@ +<!-- + ~ 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. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_list.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_list.xml new file mode 100644 index 000000000000..0e30745bdfc9 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/fragment_mail_list.xml @@ -0,0 +1,25 @@ +<!-- + ~ 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. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/mail_recycle_view" + android:layout_width="match_parent" + android:layout_height="match_parent"/> +</LinearLayout>
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/mail_row_item.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/mail_row_item.xml new file mode 100644 index 000000000000..c39bfb35e9b0 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/mail_row_item.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/list_item_height" + android:layout_marginLeft="@dimen/margin_medium" + android:layout_marginRight="@dimen/margin_medium"> + <ImageView + android:id="@+id/mail_row_item_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingBottom="@dimen/padding_small" + android:paddingEnd="@dimen/padding_small" + android:paddingStart="@dimen/padding_small" + android:paddingTop="@dimen/padding_small"/> + <TextView + android:id="@+id/mail_row_item_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:paddingEnd="@dimen/padding_small" + android:textSize="@dimen/list_item_text_size" /> +</LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/navigation/mail_navigation.xml b/tests/FlickerTests/test-apps/flickerapp/res/navigation/mail_navigation.xml new file mode 100644 index 000000000000..d12707a7a123 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/navigation/mail_navigation.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<navigation xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/mail_navigation" + app:startDestination="@id/fragment_mail_list"> + <fragment + android:id="@+id/fragment_mail_list" + android:name=".MailListFragment" + android:label="Mail List"> + <action + android:id="@+id/action_mail_list_to_mail_content" + app:destination="@id/fragment_mail_content" + app:enterAnim="@anim/slide_in_from_bottom" /> + </fragment> + <fragment + android:id="@+id/fragment_mail_content" + android:name=".MailContentFragment" + android:label="Mail Content"> + </fragment> +</navigation> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/dimens.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/dimens.xml new file mode 100644 index 000000000000..0b0763d7071d --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/dimens.xml @@ -0,0 +1,24 @@ +<!-- + ~ 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. + --> + +<resources> + <dimen name="padding_small">8dp</dimen> + <dimen name="margin_medium">16dp</dimen> + <dimen name="list_item_height">72dp</dimen> + <dimen name="list_item_text_size">24dp</dimen> + <dimen name="icon_size">64dp</dimen> + <dimen name="icon_text_size">36dp</dimen> +</resources> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/strings.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/strings.xml new file mode 100644 index 000000000000..24830deac99f --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/strings.xml @@ -0,0 +1,19 @@ +<!-- + ~ 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. + --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Label for split screen shortcut--> + <string name="split_screen_shortcut_label">Split Screen Secondary Activity</string> +</resources>
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml new file mode 100644 index 000000000000..2e661fbd3122 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/interaction_service.xml @@ -0,0 +1,20 @@ +<!-- + ~ 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. + --> + +<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android" + android:recognitionService="com.android.server.wm.flicker.testapp.AssistantRecognitionService" + android:sessionService="com.android.server.wm.flicker.testapp.AssistantInteractionSessionService" + android:supportsAssist="true" /> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml new file mode 100644 index 000000000000..2e124982084f --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/recognition_service.xml @@ -0,0 +1,17 @@ +<!-- + ~ 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. + --> + +<recognition-service xmlns:android="http://schemas.android.com/apk/res/android" /> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/shortcuts.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/shortcuts.xml new file mode 100644 index 000000000000..804ec998477f --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/shortcuts.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<shortcuts xmlns:android="http://schemas.android.com/apk/res/android"> + <shortcut + android:shortcutId="split_screen_shortcut" + android:shortcutShortLabel="@string/split_screen_shortcut_label"> + <intent + android:action="android.intent.action.VIEW" + android:targetPackage="com.android.server.wm.flicker.testapp" + android:targetClass="com.android.server.wm.flicker.testapp.SplitScreenSecondaryActivity" /> + </shortcut> +</shortcuts>
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingBaseActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingBaseActivity.java new file mode 100644 index 000000000000..cd23e9f76ebf --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingBaseActivity.java @@ -0,0 +1,34 @@ +/* + * 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.android.server.wm.flicker.testapp; + +import android.app.Activity; +import android.os.Bundle; + +/** Base activity of ActivityEmbedding split activities. */ +public abstract class ActivityEmbeddingBaseActivity extends Activity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_embedding_base_layout); + findViewById(R.id.root_activity_layout).setBackgroundColor(getBackgroundColor()); + } + + /** Sets different colors to visually distinguish split pairs. */ + abstract int getBackgroundColor(); +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java new file mode 100644 index 000000000000..6a7a2ccd3378 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java @@ -0,0 +1,102 @@ +/* + * 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.android.server.wm.flicker.testapp; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.util.ArraySet; +import android.util.Log; +import android.view.View; + +import androidx.window.extensions.embedding.ActivityEmbeddingComponent; +import androidx.window.extensions.embedding.EmbeddingRule; +import androidx.window.extensions.embedding.SplitPairRule; +import androidx.window.extensions.embedding.SplitPlaceholderRule; + +import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper; + +import java.util.Set; + +/** Main activity of the ActivityEmbedding test app to launch other embedding activities. */ +public class ActivityEmbeddingMainActivity extends Activity { + private static final String TAG = "ActivityEmbeddingMainActivity"; + private static final float DEFAULT_SPLIT_RATIO = 0.5f; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_embedding_main_layout); + } + + /** R.id.launch_secondary_activity_button onClick */ + public void launchSecondaryActivity(View view) { + initializeSplitRules(createSplitPairRules()); + startActivity(new Intent().setComponent( + ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT)); + } + + /** R.id.launch_placeholder_split_button onClick */ + public void launchPlaceholderSplit(View view) { + initializeSplitRules(createSplitPlaceholderRules()); + startActivity(new Intent().setComponent( + ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT)); + } + + private void initializeSplitRules(Set<EmbeddingRule> rules) { + ActivityEmbeddingComponent embeddingComponent = + ActivityEmbeddingAppHelper.getActivityEmbeddingComponent(); + if (embeddingComponent == null) { + // Embedding not supported + Log.d(TAG, "ActivityEmbedding is not supported on this device"); + finish(); + return; + } + embeddingComponent.setEmbeddingRules(rules); + } + + private Set<EmbeddingRule> createSplitPairRules() { + final Set<EmbeddingRule> rules = new ArraySet<>(); + final SplitPairRule rule = new SplitPairRule.Builder( + activitiesPair -> activitiesPair.first instanceof ActivityEmbeddingMainActivity + && activitiesPair.second instanceof ActivityEmbeddingSecondaryActivity, + activityIntentPair -> + activityIntentPair.first instanceof ActivityEmbeddingMainActivity + && activityIntentPair.second.getComponent().equals(ActivityOptions + .ActivityEmbedding.SecondaryActivity.COMPONENT), + windowMetrics -> true) + .setSplitRatio(DEFAULT_SPLIT_RATIO) + .build(); + rules.add(rule); + return rules; + } + + private Set<EmbeddingRule> createSplitPlaceholderRules() { + final Set<EmbeddingRule> rules = new ArraySet<>(); + final SplitPlaceholderRule rule = new SplitPlaceholderRule.Builder( + new Intent().setComponent( + ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT), + activity -> activity instanceof ActivityEmbeddingPlaceholderPrimaryActivity, + intent -> intent.getComponent().equals( + ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT), + windowMetrics -> true) + .setSplitRatio(DEFAULT_SPLIT_RATIO) + .build(); + rules.add(rule); + return rules; + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingPlaceholderPrimaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingPlaceholderPrimaryActivity.java new file mode 100644 index 000000000000..05c7a0b34c33 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingPlaceholderPrimaryActivity.java @@ -0,0 +1,30 @@ +/* + * 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.android.server.wm.flicker.testapp; + +import android.graphics.Color; + +/** + * Primary activity that will launch {@link ActivityEmbeddingPlaceholderSecondaryActivity} to split + * as placeholder based on the placeholder rule. + */ +public class ActivityEmbeddingPlaceholderPrimaryActivity extends ActivityEmbeddingBaseActivity { + @Override + int getBackgroundColor() { + return Color.BLUE; + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingPlaceholderSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingPlaceholderSecondaryActivity.java new file mode 100644 index 000000000000..a9a51cd9ad8b --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingPlaceholderSecondaryActivity.java @@ -0,0 +1,30 @@ +/* + * 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.android.server.wm.flicker.testapp; + +import android.graphics.Color; + +/** + * Activity to be used as the secondary placeholder activity to split with + * {@link ActivityEmbeddingPlaceholderPrimaryActivity}. + */ +public class ActivityEmbeddingPlaceholderSecondaryActivity extends ActivityEmbeddingBaseActivity { + @Override + int getBackgroundColor() { + return Color.GREEN; + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java new file mode 100644 index 000000000000..00f4c2576eb1 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 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.android.server.wm.flicker.testapp; + +import android.graphics.Color; + +/** + * Activity to be used as the secondary activity to split with + * {@link ActivityEmbeddingMainActivity}. + */ +public class ActivityEmbeddingSecondaryActivity extends ActivityEmbeddingBaseActivity { + @Override + int getBackgroundColor() { + return Color.YELLOW; + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index 6cda482dd30a..9c3226b5292c 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -19,72 +19,195 @@ package com.android.server.wm.flicker.testapp; import android.content.ComponentName; public class ActivityOptions { - public static final String EXTRA_STARVE_UI_THREAD = "StarveUiThread"; public static final String FLICKER_APP_PACKAGE = "com.android.server.wm.flicker.testapp"; - public static final String SEAMLESS_ACTIVITY_LAUNCHER_NAME = "SeamlessApp"; - public static final ComponentName SEAMLESS_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".SeamlessRotationActivity"); + public static class SimpleActivity { + public static final String LABEL = "SimpleActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".SimpleActivity"); + } - public static final String IME_ACTIVITY_AUTO_FOCUS_LAUNCHER_NAME = "ImeAppAutoFocus"; - public static final ComponentName IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".ImeActivityAutoFocus"); + public static class SeamlessRotation { + public static final String LABEL = "SeamlessRotationActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".SeamlessRotationActivity"); + + public static final String EXTRA_STARVE_UI_THREAD = "StarveUiThread"; + } - public static final String IME_ACTIVITY_LAUNCHER_NAME = "ImeActivity"; - public static final ComponentName IME_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, + public static class Ime { + public static class Default { + public static final String LABEL = "ImeActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".ImeActivity"); + } + + public static class AutoFocusActivity { + public static final String LABEL = "ImeAppAutoFocus"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ImeActivityAutoFocus"); + } - public static final String IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME = "ImeStateInitializeActivity"; - public static final ComponentName IME_ACTIVITY_INITIALIZE_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, + public static class StateInitializeActivity { + public static final String LABEL = "ImeStateInitializeActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".ImeStateInitializeActivity"); + } - public static final String SIMPLE_ACTIVITY_LAUNCHER_NAME = "SimpleApp"; - public static final ComponentName SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".SimpleActivity"); - - public static final String NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME = "NonResizeableApp"; - public static final ComponentName NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".NonResizeableActivity"); - - public static final String BUTTON_ACTIVITY_LAUNCHER_NAME = "ButtonApp"; - public static final ComponentName BUTTON_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".ButtonActivity"); - - public static final String LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME = "LaunchNewTaskApp"; - public static final ComponentName LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity"); - - public static final String DIALOG_THEMED_ACTIVITY = "DialogThemedActivity"; - public static final ComponentName DIALOG_THEMED_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".DialogThemedActivity"); - - public static final String PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME = "PortraitOnlyActivity"; - public static final ComponentName PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".PortraitOnlyActivity"); - - public static final String EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME = - "ImeEditorPopupDialogActivity"; - public static final ComponentName EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, + public static class EditorPopupDialogActivity { + public static final String LABEL = "ImeEditorPopupDialogActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".ImeEditorPopupDialogActivity"); - - public static final String SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME = "ShowWhenLockedApp"; - public static final ComponentName SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity"); - - public static final String NOTIFICATION_ACTIVITY_LAUNCHER_NAME = "NotificationApp"; - public static final ComponentName NOTIFICATION_ACTIVITY_COMPONENT_NAME = - new ComponentName(FLICKER_APP_PACKAGE, - FLICKER_APP_PACKAGE + ".NotificationActivity"); + } + } + + public static class NonResizeableActivity { + public static final String LABEL = "NonResizeableActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".NonResizeableActivity"); + } + + public static class NonResizeablePortraitActivity { + public static final String LABEL = "NonResizeablePortraitActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity"); + } + + public static class DialogThemedActivity { + public static final String LABEL = "DialogThemedActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".DialogThemedActivity"); + } + + public static class PortraitOnlyActivity { + public static final String LABEL = "PortraitOnlyActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".PortraitOnlyActivity"); + public static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation"; + } + + public static class ActivityEmbedding { + public static class MainActivity { + public static final String LABEL = "ActivityEmbeddingMainActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ActivityEmbeddingMainActivity"); + } + + public static class SecondaryActivity { + public static final String LABEL = "ActivityEmbeddingSecondaryActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ActivityEmbeddingSecondaryActivity"); + } + + public static class PlaceholderPrimaryActivity { + public static final String LABEL = "ActivityEmbeddingPlaceholderPrimaryActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderPrimaryActivity"); + } + + public static class PlaceholderSecondaryActivity { + public static final String LABEL = "ActivityEmbeddingPlaceholderSecondaryActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderSecondaryActivity"); + } + } + + public static class Notification { + public static final String LABEL = "NotificationActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".NotificationActivity"); + } + + public static class Mail { + public static final String LABEL = "MailActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".MailActivity"); + } + + public static class ShowWhenLockedActivity { + public static final String LABEL = "ShowWhenLockedActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity"); + } + + public static class LaunchNewTask { + public static final String LABEL = "LaunchNewTaskActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity"); + } + + public static class Game { + public static final String LABEL = "GameActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".GameActivity"); + } + + public static class LaunchNewActivity { + public static final String LABEL = "LaunchNewActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".LaunchNewActivity"); + } + + public static class Pip { + // Test App > Pip Activity + public static final String LABEL = "PipActivity"; + public static final String MENU_ACTION_NO_OP = "No-Op"; + public static final String MENU_ACTION_ON = "On"; + public static final String MENU_ACTION_OFF = "Off"; + public static final String MENU_ACTION_CLEAR = "Clear"; + + // Intent action that this activity dynamically registers to enter picture-in-picture + public static final String ACTION_ENTER_PIP = + FLICKER_APP_PACKAGE + ".PipActivity.ENTER_PIP"; + // Intent action that this activity dynamically registers to set requested orientation. + // Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra. + public static final String ACTION_SET_REQUESTED_ORIENTATION = + FLICKER_APP_PACKAGE + ".PipActivity.SET_REQUESTED_ORIENTATION"; + + // Calls enterPictureInPicture() on creation + public static final String EXTRA_ENTER_PIP = "enter_pip"; + // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation} + public static final String EXTRA_PIP_ORIENTATION = "fixed_orientation"; + // Adds a click listener to finish this activity when it is clicked + public static final String EXTRA_TAP_TO_FINISH = "tap_to_finish"; + + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".PipActivity"); + } + + public static class SplitScreen { + public static class Primary { + public static final String LABEL = "SplitScreenPrimaryActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".SplitScreenActivity"); + } + + public static class Secondary { + public static final String LABEL = "SplitScreenSecondaryActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".SplitScreenSecondaryActivity"); + } + } + + public static class Bubbles { + public static class LaunchBubble { + public static final String LABEL = "LaunchBubbleActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".LaunchBubbleActivity"); + } + + public static class BubbleActivity { + public static final String LABEL = "BubbleActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".BubbleActivity"); + } + } + + public static final String GAME_ACTIVITY_LAUNCHER_NAME = "GameApp"; + public static final ComponentName GAME_ACTIVITY_COMPONENT_NAME = + new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".GameActivity"); + + public static final ComponentName ASSISTANT_SERVICE_COMPONENT_NAME = + new ComponentName( + FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".AssistantInteractionService"); } diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target00.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionService.java index 8fb4e91f790c..d60143cdf40a 100644 --- a/tests/componentalias/src/android/content/componentalias/tests/b/Target00.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.content.componentalias.tests.b; -import android.content.componentalias.tests.s.BaseService; +package com.android.server.wm.flicker.testapp; -public class Target00 extends BaseReceiver { +import android.service.voice.VoiceInteractionService; + +public class AssistantInteractionService extends VoiceInteractionService { } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java new file mode 100644 index 000000000000..d2c9b37704b8 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSession.java @@ -0,0 +1,53 @@ +/* + * 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.android.server.wm.flicker.testapp; + +import android.content.Context; +import android.graphics.Color; +import android.os.Bundle; +import android.service.voice.VoiceInteractionSession; +import android.view.View; +import android.view.Window; + +public class AssistantInteractionSession extends VoiceInteractionSession { + + public AssistantInteractionSession(Context context) { + super(context); + } + + @Override + public void onCreate() { + Window window = getWindow().getWindow(); + if (window != null) { + window.getDecorView().setBackgroundColor(Color.TRANSPARENT); + + } + View rootView = getLayoutInflater().inflate(R.layout.assistant_session, null); + setContentView(rootView); + setUiEnabled(false); + } + + @Override + public void onShow(Bundle args, int showFlags) { + setUiEnabled(true); + } + + @Override + public void onHide() { + setUiEnabled(false); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java new file mode 100644 index 000000000000..4d6125c9a5d8 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantInteractionSessionService.java @@ -0,0 +1,28 @@ +/* + * 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.android.server.wm.flicker.testapp; + +import android.os.Bundle; +import android.service.voice.VoiceInteractionSession; +import android.service.voice.VoiceInteractionSessionService; + +public class AssistantInteractionSessionService extends VoiceInteractionSessionService { + @Override + public VoiceInteractionSession onNewSession(Bundle args) { + return new AssistantInteractionSession(this); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java new file mode 100644 index 000000000000..68aae4520fe9 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/AssistantRecognitionService.java @@ -0,0 +1,37 @@ +/* + * 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.android.server.wm.flicker.testapp; + +import android.content.Intent; +import android.speech.RecognitionService; + +public class AssistantRecognitionService extends RecognitionService { + @Override + protected void onStartListening(Intent recognizerIntent, Callback listener) { + + } + + @Override + protected void onCancel(Callback listener) { + + } + + @Override + protected void onStopListening(Callback listener) { + + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleActivity.java new file mode 100644 index 000000000000..58d7e670e908 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleActivity.java @@ -0,0 +1,77 @@ +/* + * 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.android.server.wm.flicker.testapp; + + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.widget.Toast; + +public class BubbleActivity extends Activity { + private int mNotifId = 0; + + public BubbleActivity() { + super(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent = getIntent(); + if (intent != null) { + mNotifId = intent.getIntExtra(BubbleHelper.EXTRA_BUBBLE_NOTIF_ID, -1); + } else { + mNotifId = -1; + } + + setContentView(R.layout.activity_bubble); + } + + @Override + protected void onStart() { + super.onStart(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + protected void onStop() { + super.onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + String result = resultCode == Activity.RESULT_OK ? "OK" : "CANCELLED"; + Toast.makeText(this, "Activity result: " + result, Toast.LENGTH_SHORT).show(); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java new file mode 100644 index 000000000000..c92b82b896f2 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java @@ -0,0 +1,173 @@ +/* + * 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.android.server.wm.flicker.testapp; + + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Person; +import android.content.Context; +import android.content.Intent; +import android.graphics.Point; +import android.graphics.drawable.Icon; +import android.os.SystemClock; +import android.service.notification.StatusBarNotification; +import android.view.WindowManager; + +import java.util.HashMap; + +public class BubbleHelper { + + static final String EXTRA_BUBBLE_NOTIF_ID = "EXTRA_BUBBLE_NOTIF_ID"; + static final String CHANNEL_ID = "bubbles"; + static final String CHANNEL_NAME = "Bubbles"; + static final int DEFAULT_HEIGHT_DP = 300; + + private static BubbleHelper sInstance; + + private final Context mContext; + private NotificationManager mNotificationManager; + private float mDisplayHeight; + + private HashMap<Integer, BubbleInfo> mBubbleMap = new HashMap<>(); + + private int mNextNotifyId = 0; + private int mColourIndex = 0; + + public static class BubbleInfo { + public int id; + public int height; + public Icon icon; + + public BubbleInfo(int id, int height, Icon icon) { + this.id = id; + this.height = height; + this.icon = icon; + } + } + + public static BubbleHelper getInstance(Context context) { + if (sInstance == null) { + sInstance = new BubbleHelper(context); + } + return sInstance; + } + + private BubbleHelper(Context context) { + mContext = context; + mNotificationManager = context.getSystemService(NotificationManager.class); + + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, + NotificationManager.IMPORTANCE_DEFAULT); + channel.setDescription("Channel that posts bubbles"); + channel.setAllowBubbles(true); + mNotificationManager.createNotificationChannel(channel); + + Point p = new Point(); + WindowManager wm = context.getSystemService(WindowManager.class); + wm.getDefaultDisplay().getRealSize(p); + mDisplayHeight = p.y; + + } + + private int getNextNotifyId() { + int id = mNextNotifyId; + mNextNotifyId++; + return id; + } + + private Icon getIcon() { + return Icon.createWithResource(mContext, R.drawable.bg); + } + + public int addNewBubble(boolean autoExpand, boolean suppressNotif) { + int id = getNextNotifyId(); + BubbleInfo info = new BubbleInfo(id, DEFAULT_HEIGHT_DP, getIcon()); + mBubbleMap.put(info.id, info); + + Notification.BubbleMetadata data = getBubbleBuilder(info) + .setSuppressNotification(suppressNotif) + .setAutoExpandBubble(false) + .build(); + Notification notification = getNotificationBuilder(info.id) + .setBubbleMetadata(data).build(); + + mNotificationManager.notify(info.id, notification); + return info.id; + } + + private Notification.Builder getNotificationBuilder(int id) { + Person chatBot = new Person.Builder() + .setBot(true) + .setName("BubbleChat") + .setImportant(true) + .build(); + String shortcutId = "BubbleChat"; + return new Notification.Builder(mContext, CHANNEL_ID) + .setChannelId(CHANNEL_ID) + .setShortcutId(shortcutId) + .setContentTitle("BubbleChat") + .setContentIntent(PendingIntent.getActivity(mContext, 0, + new Intent(mContext, LaunchBubbleActivity.class), + PendingIntent.FLAG_UPDATE_CURRENT)) + .setStyle(new Notification.MessagingStyle(chatBot) + .setConversationTitle("BubbleChat") + .addMessage("BubbleChat", + SystemClock.currentThreadTimeMillis() - 300000, chatBot) + .addMessage("Is it me, " + id + ", you're looking for?", + SystemClock.currentThreadTimeMillis(), chatBot) + ) + .setSmallIcon(R.drawable.ic_bubble); + } + + private Notification.BubbleMetadata.Builder getBubbleBuilder(BubbleInfo info) { + Intent target = new Intent(mContext, BubbleActivity.class); + target.putExtra(EXTRA_BUBBLE_NOTIF_ID, info.id); + PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, info.id, target, + PendingIntent.FLAG_UPDATE_CURRENT); + + return new Notification.BubbleMetadata.Builder() + .setIntent(bubbleIntent) + .setIcon(info.icon) + .setDesiredHeight(info.height); + } + + public void cancel(int id) { + mNotificationManager.cancel(id); + } + + public void cancelAll() { + mNotificationManager.cancelAll(); + } + + public void cancelLast() { + StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications(); + if (activeNotifications.length > 0) { + mNotificationManager.cancel( + activeNotifications[activeNotifications.length - 1].getId()); + } + } + + public void cancelFirst() { + StatusBarNotification[] activeNotifications = mNotificationManager.getActiveNotifications(); + if (activeNotifications.length > 0) { + mNotificationManager.cancel(activeNotifications[0].getId()); + } + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/FixedActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/FixedActivity.java new file mode 100644 index 000000000000..722929fb81e5 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/FixedActivity.java @@ -0,0 +1,35 @@ +/* + * 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.android.server.wm.flicker.testapp; + +import static com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION; + +import android.os.Bundle; + +public class FixedActivity extends SimpleActivity { + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + // Set the fixed orientation if requested + if (getIntent().hasExtra(EXTRA_FIXED_ORIENTATION)) { + final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_FIXED_ORIENTATION)); + setRequestedOrientation(ori); + } + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/GameActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/GameActivity.java new file mode 100644 index 000000000000..ef75d4ddcdcd --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/GameActivity.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2021 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.android.server.wm.flicker.testapp; + +import android.app.Activity; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.os.Bundle; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; +import androidx.core.view.WindowInsetsControllerCompat; + +public class GameActivity extends Activity implements SurfaceHolder.Callback { + private SurfaceHolder mSurfaceHolder; + private SurfaceView mSurfaceView; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_surfaceview); + + mSurfaceView = (SurfaceView) findViewById(R.id.surface_view); + mSurfaceView.setZOrderOnTop(true); + mSurfaceHolder = mSurfaceView.getHolder(); + mSurfaceHolder.setFormat(PixelFormat.TRANSPARENT); + mSurfaceHolder.addCallback(this); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + hideSystemBars(); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Canvas canvas = holder.lockCanvas(); + canvas.drawColor(Color.BLUE); + holder.unlockCanvasAndPost(canvas); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + } + + private void hideSystemBars() { + WindowInsetsControllerCompat windowInsetsController = + ViewCompat.getWindowInsetsController(getWindow().getDecorView()); + if (windowInsetsController == null) { + return; + } + // Configure the behavior of the hidden system bars. + windowInsetsController.setSystemBarsBehavior( + WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + ); + // Hide both the status bar and the navigation bar. + windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java index bb200f125507..7ee8debddcf1 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java @@ -16,21 +16,29 @@ package com.android.server.wm.flicker.testapp; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + import android.content.Intent; import android.widget.Button; import android.widget.EditText; +import android.widget.ToggleButton; public class ImeActivityAutoFocus extends ImeActivity { - @Override protected void onStart() { super.onStart(); - EditText editTextField = findViewById(R.id.plain_text_input); - editTextField.requestFocus(); - Button startThemedActivityButton = findViewById(R.id.start_dialog_themed_activity_btn); startThemedActivityButton.setOnClickListener( button -> startActivity(new Intent(this, DialogThemedActivity.class))); + + ToggleButton toggleFixedPortraitButton = findViewById(R.id.toggle_fixed_portrait_btn); + toggleFixedPortraitButton.setOnCheckedChangeListener( + (button, isChecked) -> setRequestedOrientation( + isChecked ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_UNSPECIFIED)); + + EditText editTextField = findViewById(R.id.plain_text_input); + editTextField.requestFocus(); } } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java index a8613f531e1c..95f933f97bb2 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeEditorPopupDialogActivity.java @@ -16,6 +16,8 @@ package com.android.server.wm.flicker.testapp; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN; + import android.app.Activity; import android.app.AlertDialog; import android.os.Bundle; @@ -30,6 +32,7 @@ public class ImeEditorPopupDialogActivity extends Activity { WindowManager.LayoutParams p = getWindow().getAttributes(); p.layoutInDisplayCutoutMode = WindowManager.LayoutParams .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + p.softInputMode = SOFT_INPUT_STATE_ALWAYS_HIDDEN; getWindow().setAttributes(p); LinearLayout layout = new LinearLayout(this); layout.setOrientation(LinearLayout.VERTICAL); diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java new file mode 100644 index 000000000000..dea34442464d --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java @@ -0,0 +1,82 @@ +/* + * 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.android.server.wm.flicker.testapp; + + +import android.app.Activity; +import android.app.Person; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.view.View; + +import java.util.Arrays; + +public class LaunchBubbleActivity extends Activity { + + private BubbleHelper mBubbleHelper; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addInboxShortcut(getApplicationContext()); + mBubbleHelper = BubbleHelper.getInstance(this); + setContentView(R.layout.activity_main); + findViewById(R.id.button_create).setOnClickListener(this::add); + findViewById(R.id.button_cancel).setOnClickListener(this::cancel); + findViewById(R.id.button_cancel_all).setOnClickListener(this::cancelAll); + } + + private void add(View v) { + mBubbleHelper.addNewBubble(false /* autoExpand */, false /* suppressNotif */); + } + + private void cancel(View v) { + mBubbleHelper.cancelLast(); + } + + private void cancelAll(View v) { + mBubbleHelper.cancelAll(); + } + + private void addInboxShortcut(Context context) { + Icon icon = Icon.createWithResource(this, R.drawable.bg); + Person[] persons = new Person[4]; + for (int i = 0; i < persons.length; i++) { + persons[i] = new Person.Builder() + .setBot(false) + .setIcon(icon) + .setName("google" + i) + .setImportant(true) + .build(); + } + + ShortcutInfo shortcut = new ShortcutInfo.Builder(context, "BubbleChat") + .setShortLabel("BubbleChat") + .setLongLived(true) + .setIntent(new Intent(Intent.ACTION_VIEW)) + .setIcon(Icon.createWithResource(context, R.drawable.ic_message)) + .setPersons(persons) + .build(); + ShortcutManager scmanager = context.getSystemService(ShortcutManager.class); + scmanager.addDynamicShortcuts(Arrays.asList(shortcut)); + } + +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java index b42ac2a6fd97..e5710c850f07 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java @@ -22,7 +22,7 @@ import android.os.Bundle; import android.view.WindowManager; import android.widget.Button; -public class ButtonActivity extends Activity { +public class LaunchNewActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -30,11 +30,11 @@ public class ButtonActivity extends Activity { p.layoutInDisplayCutoutMode = WindowManager.LayoutParams .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; getWindow().setAttributes(p); - setContentView(R.layout.activity_button); + setContentView(R.layout.activity_launch_new); Button button = findViewById(R.id.launch_second_activity); button.setOnClickListener(v -> { - Intent intent = new Intent(ButtonActivity.this, SimpleActivity.class); + Intent intent = new Intent(LaunchNewActivity.this, SimpleActivity.class); startActivity(intent); }); } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailActivity.java new file mode 100644 index 000000000000..419d438baf69 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailActivity.java @@ -0,0 +1,31 @@ +/* + * 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.android.server.wm.flicker.testapp; + +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +public class MailActivity extends AppCompatActivity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_mail); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailAdapter.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailAdapter.java new file mode 100644 index 000000000000..984263acebac --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailAdapter.java @@ -0,0 +1,134 @@ +/* + * 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.android.server.wm.flicker.testapp; + +import static androidx.navigation.fragment.NavHostFragment.findNavController; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.OvalShape; +import android.graphics.drawable.shapes.Shape; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.navigation.NavController; +import androidx.recyclerview.widget.RecyclerView; + +public class MailAdapter extends RecyclerView.Adapter<MailAdapter.ViewHolder> { + + private final Fragment mFragment; + private final int mSize; + + static class BadgeShape extends OvalShape { + Context mContext; + String mLabel; + + BadgeShape(Context context, String label) { + mContext = context; + mLabel = label; + } + + @Override + public void draw(Canvas canvas, Paint paint) { + final Resources resources = mContext.getResources(); + int textSize = resources.getDimensionPixelSize(R.dimen.icon_text_size); + paint.setColor(Color.BLACK); + super.draw(canvas, paint); + paint.setColor(Color.WHITE); + paint.setTextSize(textSize); + paint.setTypeface(Typeface.DEFAULT_BOLD); + paint.setTextAlign(Paint.Align.CENTER); + Paint.FontMetrics fontMetrics = paint.getFontMetrics(); + float distance = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom; + canvas.drawText( + mLabel, + rect().centerX(), + rect().centerY() + distance, + paint); + } + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + NavController mNavController; + ImageView mImageView; + TextView mTextView; + DisplayMetrics mDisplayMetrics; + + ViewHolder(@NonNull View itemView, NavController navController) { + super(itemView); + mNavController = navController; + itemView.setOnClickListener(this::onClick); + mImageView = itemView.findViewById(R.id.mail_row_item_icon); + mTextView = itemView.findViewById(R.id.mail_row_item_text); + mDisplayMetrics = itemView.getContext().getResources().getDisplayMetrics(); + } + + void onClick(View v) { + mNavController.navigate(R.id.action_mail_list_to_mail_content); + } + + public void setContent(int i) { + final Resources resources = mImageView.getContext().getResources(); + final int badgeSize = resources.getDimensionPixelSize(R.dimen.icon_size); + final char c = (char) ('A' + i % 26); + final Shape badge = new BadgeShape(mImageView.getContext(), String.valueOf(c)); + ShapeDrawable drawable = new ShapeDrawable(); + drawable.setIntrinsicHeight(badgeSize); + drawable.setIntrinsicWidth(badgeSize); + drawable.setShape(badge); + mImageView.setImageDrawable(drawable); + mTextView.setText(String.format("%s-%04d", c, i)); + } + } + + public MailAdapter(Fragment fragment, int size) { + super(); + mFragment = fragment; + mSize = size; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { + View v = LayoutInflater + .from(viewGroup.getContext()) + .inflate(R.layout.mail_row_item, viewGroup, false); + return new ViewHolder(v, findNavController(mFragment)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) { + viewHolder.setContent(i); + } + + @Override + public int getItemCount() { + return mSize; + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailContentFragment.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailContentFragment.java new file mode 100644 index 000000000000..278b92e4b353 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailContentFragment.java @@ -0,0 +1,36 @@ +/* + * 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.android.server.wm.flicker.testapp; + +import android.graphics.Color; +import android.os.Bundle; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +public class MailContentFragment extends Fragment { + public MailContentFragment() { + super(R.layout.fragment_mail_content); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + view.setBackgroundColor(Color.LTGRAY); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailListFragment.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailListFragment.java new file mode 100644 index 000000000000..551820c3322b --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/MailListFragment.java @@ -0,0 +1,49 @@ +/* + * 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.android.server.wm.flicker.testapp; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +public class MailListFragment extends Fragment { + + protected RecyclerView mRecyclerView; + protected RecyclerView.LayoutManager mLayoutManager; + protected MailAdapter mAdapter; + + public MailListFragment() { + super(R.layout.fragment_mail_list); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_mail_list, container, false); + mRecyclerView = rootView.findViewById(R.id.mail_recycle_view); + mAdapter = new MailAdapter(this, 1000); + mRecyclerView.setAdapter(mAdapter); + mLayoutManager = new LinearLayoutManager(getActivity()); + mRecyclerView.setLayoutManager(mLayoutManager); + return rootView; + } +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeablePortraitActivity.java index df7579d8304d..4b420dcea54d 100644 --- a/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeablePortraitActivity.java @@ -13,7 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.content.componentalias.tests.b; -public class Target02 extends BaseReceiver { +package com.android.server.wm.flicker.testapp; + +import android.app.Activity; +import android.os.Bundle; + +public class NonResizeablePortraitActivity extends Activity { + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.activity_non_resizeable); + } } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java index b31af385d363..a4dd5753539d 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java @@ -50,12 +50,12 @@ public class NotificationActivity extends Activity { Intent resultIntent = new Intent(this, NotificationActivity.class); TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); stackBuilder.addNextIntentWithParentStack(resultIntent); - PendingIntent resultPendingIntent = - stackBuilder.getPendingIntent(0, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - + PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0, + new Intent(this, NotificationActivity.class), + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_notification) + .setWhen(System.currentTimeMillis()) .setContentTitle("Flicker Test Notification") .setContentText("Flicker Test Notification") // Set the intent that will fire when the user taps the notification diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java new file mode 100644 index 000000000000..cdb1d42bd4f2 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java @@ -0,0 +1,307 @@ +/* + * 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.android.server.wm.flicker.testapp; + +import static android.media.MediaMetadata.METADATA_KEY_TITLE; +import static android.media.session.PlaybackState.ACTION_PAUSE; +import static android.media.session.PlaybackState.ACTION_PLAY; +import static android.media.session.PlaybackState.ACTION_STOP; +import static android.media.session.PlaybackState.STATE_PAUSED; +import static android.media.session.PlaybackState.STATE_PLAYING; +import static android.media.session.PlaybackState.STATE_STOPPED; + +import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP; +import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_SET_REQUESTED_ORIENTATION; +import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.EXTRA_ENTER_PIP; +import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.EXTRA_PIP_ORIENTATION; + +import android.app.Activity; +import android.app.PendingIntent; +import android.app.PictureInPictureParams; +import android.app.RemoteAction; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.drawable.Icon; +import android.media.MediaMetadata; +import android.media.session.MediaSession; +import android.media.session.PlaybackState; +import android.os.Bundle; +import android.util.Log; +import android.util.Rational; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.CheckBox; +import android.widget.RadioButton; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class PipActivity extends Activity { + private static final String TAG = PipActivity.class.getSimpleName(); + /** + * A media session title for when the session is in {@link STATE_PLAYING}. + * TvPipNotificationTests check whether the actual notification title matches this string. + */ + private static final String TITLE_STATE_PLAYING = "TestApp media is playing"; + /** + * A media session title for when the session is in {@link STATE_PAUSED}. + * TvPipNotificationTests check whether the actual notification title matches this string. + */ + private static final String TITLE_STATE_PAUSED = "TestApp media is paused"; + + private static final Rational RATIO_DEFAULT = null; + private static final Rational RATIO_SQUARE = new Rational(1, 1); + private static final Rational RATIO_WIDE = new Rational(2, 1); + private static final Rational RATIO_TALL = new Rational(1, 2); + + private static final String PIP_ACTION_NO_OP = "No-Op"; + private static final String PIP_ACTION_OFF = "Off"; + private static final String PIP_ACTION_ON = "On"; + private static final String PIP_ACTION_CLEAR = "Clear"; + private static final String ACTION_NO_OP = "com.android.wm.shell.flicker.testapp.NO_OP"; + private static final String ACTION_SWITCH_OFF = + "com.android.wm.shell.flicker.testapp.SWITCH_OFF"; + private static final String ACTION_SWITCH_ON = "com.android.wm.shell.flicker.testapp.SWITCH_ON"; + private static final String ACTION_CLEAR = "com.android.wm.shell.flicker.testapp.CLEAR"; + + private final PictureInPictureParams.Builder mPipParamsBuilder = + new PictureInPictureParams.Builder() + .setAspectRatio(RATIO_DEFAULT); + private MediaSession mMediaSession; + private final PlaybackState.Builder mPlaybackStateBuilder = new PlaybackState.Builder() + .setActions(ACTION_PLAY | ACTION_PAUSE | ACTION_STOP) + .setState(STATE_STOPPED, 0, 1f); + private PlaybackState mPlaybackState = mPlaybackStateBuilder.build(); + private final MediaMetadata.Builder mMediaMetadataBuilder = new MediaMetadata.Builder(); + + private final List<RemoteAction> mSwitchOffActions = new ArrayList<>(); + private final List<RemoteAction> mSwitchOnActions = new ArrayList<>(); + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (isInPictureInPictureMode()) { + switch (intent.getAction()) { + case ACTION_SWITCH_ON: + mPipParamsBuilder.setActions(mSwitchOnActions); + break; + case ACTION_SWITCH_OFF: + mPipParamsBuilder.setActions(mSwitchOffActions); + break; + case ACTION_CLEAR: + mPipParamsBuilder.setActions(Collections.emptyList()); + break; + case ACTION_NO_OP: + return; + default: + Log.w(TAG, "Unhandled action=" + intent.getAction()); + return; + } + setPictureInPictureParams(mPipParamsBuilder.build()); + } else { + switch (intent.getAction()) { + case ACTION_ENTER_PIP: + enterPip(null); + break; + case ACTION_SET_REQUESTED_ORIENTATION: + setRequestedOrientation(Integer.parseInt(intent.getStringExtra( + EXTRA_PIP_ORIENTATION))); + break; + default: + Log.w(TAG, "Unhandled action=" + intent.getAction()); + } + } + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final Window window = getWindow(); + final WindowManager.LayoutParams layoutParams = window.getAttributes(); + layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams + .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + window.setAttributes(layoutParams); + + setContentView(R.layout.activity_pip); + + findViewById(R.id.media_session_start) + .setOnClickListener(v -> updateMediaSessionState(STATE_PLAYING)); + findViewById(R.id.media_session_stop) + .setOnClickListener(v -> updateMediaSessionState(STATE_STOPPED)); + + mMediaSession = new MediaSession(this, "WMShell_TestApp"); + mMediaSession.setPlaybackState(mPlaybackStateBuilder.build()); + mMediaSession.setCallback(new MediaSession.Callback() { + @Override + public void onPlay() { + updateMediaSessionState(STATE_PLAYING); + } + + @Override + public void onPause() { + updateMediaSessionState(STATE_PAUSED); + } + + @Override + public void onStop() { + updateMediaSessionState(STATE_STOPPED); + } + }); + + // Build two sets of the custom actions. We'll replace one with the other when 'On'/'Off' + // action is invoked. + // The first set consists of 3 actions: 1) Off; 2) No-Op; 3) Clear. + // The second set consists of 2 actions: 1) On; 2) Clear. + // Upon invocation 'Clear' action clear-off all the custom actions, including itself. + final Icon icon = Icon.createWithResource(this, android.R.drawable.ic_menu_help); + final RemoteAction noOpAction = buildRemoteAction(icon, PIP_ACTION_NO_OP, ACTION_NO_OP); + final RemoteAction switchOnAction = + buildRemoteAction(icon, PIP_ACTION_ON, ACTION_SWITCH_ON); + final RemoteAction switchOffAction = + buildRemoteAction(icon, PIP_ACTION_OFF, ACTION_SWITCH_OFF); + final RemoteAction clearAllAction = buildRemoteAction(icon, PIP_ACTION_CLEAR, ACTION_CLEAR); + mSwitchOffActions.addAll(Arrays.asList(switchOnAction, clearAllAction)); + mSwitchOnActions.addAll(Arrays.asList(noOpAction, switchOffAction, clearAllAction)); + + final IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_NO_OP); + filter.addAction(ACTION_SWITCH_ON); + filter.addAction(ACTION_SWITCH_OFF); + filter.addAction(ACTION_CLEAR); + filter.addAction(ACTION_SET_REQUESTED_ORIENTATION); + filter.addAction(ACTION_ENTER_PIP); + registerReceiver(mBroadcastReceiver, filter); + + handleIntentExtra(getIntent()); + } + + @Override + protected void onDestroy() { + unregisterReceiver(mBroadcastReceiver); + super.onDestroy(); + } + + @Override + protected void onUserLeaveHint() { + // Only used when auto PiP is disabled. This is to simulate the behavior that an app + // supports regular PiP but not auto PiP. + final boolean manuallyEnterPip = + ((RadioButton) findViewById(R.id.enter_pip_on_leave_manual)).isChecked(); + if (manuallyEnterPip) { + enterPictureInPictureMode(); + } + } + + private RemoteAction buildRemoteAction(Icon icon, String label, String action) { + final Intent intent = new Intent(action); + final PendingIntent pendingIntent = + PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + return new RemoteAction(icon, label, label, pendingIntent); + } + + public void enterPip(View v) { + final boolean withCustomActions = + ((CheckBox) findViewById(R.id.with_custom_actions)).isChecked(); + mPipParamsBuilder.setActions( + withCustomActions ? mSwitchOnActions : Collections.emptyList()); + enterPictureInPictureMode(mPipParamsBuilder.build()); + } + + public void onAutoPipSelected(View v) { + switch (v.getId()) { + case R.id.enter_pip_on_leave_manual: + // disable auto enter PiP + case R.id.enter_pip_on_leave_disabled: + mPipParamsBuilder.setAutoEnterEnabled(false); + setPictureInPictureParams(mPipParamsBuilder.build()); + break; + case R.id.enter_pip_on_leave_autoenter: + mPipParamsBuilder.setAutoEnterEnabled(true); + setPictureInPictureParams(mPipParamsBuilder.build()); + break; + } + } + + public void onRatioSelected(View v) { + switch (v.getId()) { + case R.id.ratio_default: + mPipParamsBuilder.setAspectRatio(RATIO_DEFAULT); + break; + + case R.id.ratio_square: + mPipParamsBuilder.setAspectRatio(RATIO_SQUARE); + break; + + case R.id.ratio_wide: + mPipParamsBuilder.setAspectRatio(RATIO_WIDE); + break; + + case R.id.ratio_tall: + mPipParamsBuilder.setAspectRatio(RATIO_TALL); + break; + } + } + + private void updateMediaSessionState(int newState) { + if (mPlaybackState.getState() == newState) { + return; + } + final String title; + switch (newState) { + case STATE_PLAYING: + title = TITLE_STATE_PLAYING; + break; + case STATE_PAUSED: + title = TITLE_STATE_PAUSED; + break; + case STATE_STOPPED: + title = ""; + break; + + default: + throw new IllegalArgumentException("Unknown state " + newState); + } + + mPlaybackStateBuilder.setState(newState, 0, 1f); + mPlaybackState = mPlaybackStateBuilder.build(); + + mMediaMetadataBuilder.putText(METADATA_KEY_TITLE, title); + + mMediaSession.setPlaybackState(mPlaybackState); + mMediaSession.setMetadata(mMediaMetadataBuilder.build()); + mMediaSession.setActive(newState != STATE_STOPPED); + } + + private void handleIntentExtra(Intent intent) { + // Set the fixed orientation if requested + if (intent.hasExtra(EXTRA_PIP_ORIENTATION)) { + final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_PIP_ORIENTATION)); + setRequestedOrientation(ori); + } + // Enter picture in picture with the given aspect ratio if provided + if (intent.hasExtra(EXTRA_ENTER_PIP)) { + mPipParamsBuilder.setActions(mSwitchOnActions); + enterPip(null); + } + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java index 5cf81cb90fbc..ce7a0059fa2d 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java @@ -18,7 +18,7 @@ package com.android.server.wm.flicker.testapp; import static android.os.SystemClock.sleep; -import static com.android.server.wm.flicker.testapp.ActivityOptions.EXTRA_STARVE_UI_THREAD; +import static com.android.server.wm.flicker.testapp.ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD; import android.app.Activity; import android.os.Bundle; diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java new file mode 100644 index 000000000000..70196aee9ff1 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.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.android.server.wm.flicker.testapp; + +import android.app.Activity; +import android.os.Bundle; + +public class SplitScreenActivity extends Activity { + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.activity_splitscreen); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenSecondaryActivity.java new file mode 100644 index 000000000000..a8ce8ff8f589 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenSecondaryActivity.java @@ -0,0 +1,28 @@ +/* + * 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.android.server.wm.flicker.testapp; + +import android.app.Activity; +import android.os.Bundle; + +public class SplitScreenSecondaryActivity extends Activity { + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.activity_splitscreen_secondary); + } +} diff --git a/tests/FrameworkPerf/AndroidManifest.xml b/tests/FrameworkPerf/AndroidManifest.xml index 07e775aeb838..9696fc31469a 100644 --- a/tests/FrameworkPerf/AndroidManifest.xml +++ b/tests/FrameworkPerf/AndroidManifest.xml @@ -3,6 +3,16 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.frameworkperf"> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> + <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/> + <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/> + <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"/> + <uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/> + <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/> + <uses-permission android:name="Manifest.permission.ACCESS_FINE_LOCATION"/> + <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/> + + <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-sdk android:minSdkVersion="5"/> diff --git a/tests/HandwritingIme/OWNERS b/tests/HandwritingIme/OWNERS new file mode 100644 index 000000000000..6bb4b17ed4eb --- /dev/null +++ b/tests/HandwritingIme/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 34867 + +include /services/core/java/com/android/server/inputmethod/OWNERS diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/BoundsInfoDrawHelper.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/BoundsInfoDrawHelper.java new file mode 100644 index 000000000000..6b924f335ef7 --- /dev/null +++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/BoundsInfoDrawHelper.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2023 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.google.android.test.handwritingime; + +import static com.google.android.test.handwritingime.HandwritingIme.BOUNDS_INFO_EDITOR_BOUNDS; +import static com.google.android.test.handwritingime.HandwritingIme.BOUNDS_INFO_NONE; +import static com.google.android.test.handwritingime.HandwritingIme.BOUNDS_INFO_VISIBLE_LINE_BOUNDS; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.RectF; +import android.view.View; +import android.view.inputmethod.CursorAnchorInfo; +import android.view.inputmethod.EditorBoundsInfo; + +import androidx.annotation.Nullable; + +import com.android.internal.graphics.ColorUtils; + +import java.util.List; + +public class BoundsInfoDrawHelper { + private static final Paint sPaint = new Paint(); + private static final int EDITOR_BOUNDS_COLOR = + ColorUtils.setAlphaComponent(Color.DKGRAY, 128); + private static final int HANDWRITING_BOUNDS_COLOR = + ColorUtils.setAlphaComponent(Color.BLUE, 128); + private static final int VISIBLE_LINE_BOUNDS_COLOR = + ColorUtils.setAlphaComponent(Color.MAGENTA, 128); + + public static void draw(Canvas canvas, View inkView, int boundsInfoMode, + CursorAnchorInfo cursorAnchorInfo) { + if (boundsInfoMode == BOUNDS_INFO_NONE || cursorAnchorInfo == null) { + return; + } + + // The matrix in CursorAnchorInfo transforms the editor coordinates to on-screen + // coordinates. We then transform the matrix from the on-screen coordinates to the + // inkView's coordinates. So the result matrix transforms the editor coordinates + // to the inkView coordinates. + final Matrix matrix = cursorAnchorInfo.getMatrix(); + inkView.transformMatrixToLocal(matrix); + + if ((boundsInfoMode & BOUNDS_INFO_EDITOR_BOUNDS) != 0) { + drawEditorBoundsInfo(canvas, matrix, cursorAnchorInfo.getEditorBoundsInfo()); + } + + if ((boundsInfoMode & BOUNDS_INFO_VISIBLE_LINE_BOUNDS) != 0) { + drawVisibleLineBounds(canvas, matrix, cursorAnchorInfo.getVisibleLineBounds()); + } + } + + private static void setPaintForEditorBoundsInfo() { + sPaint.reset(); + sPaint.setStyle(Paint.Style.STROKE); + sPaint.setStrokeWidth(5f); + } + + private static void drawEditorBoundsInfo(Canvas canvas, Matrix matrix, + @Nullable EditorBoundsInfo editorBoundsInfo) { + if (editorBoundsInfo == null) { + return; + } + final RectF editorBounds = editorBoundsInfo.getEditorBounds(); + setPaintForEditorBoundsInfo(); + if (editorBounds != null) { + final RectF localEditorBounds = new RectF(editorBounds); + matrix.mapRect(localEditorBounds); + sPaint.setColor(EDITOR_BOUNDS_COLOR); + canvas.drawRect(localEditorBounds, sPaint); + } + + final RectF handwritingBounds = editorBoundsInfo.getHandwritingBounds(); + if (handwritingBounds != null) { + final RectF localHandwritingBounds = new RectF(handwritingBounds); + matrix.mapRect(localHandwritingBounds); + sPaint.setColor(HANDWRITING_BOUNDS_COLOR); + canvas.drawRect(localHandwritingBounds, sPaint); + } + } + + private static void setPaintForVisibleLineBounds() { + sPaint.reset(); + sPaint.setStyle(Paint.Style.STROKE); + sPaint.setStrokeWidth(2f); + sPaint.setColor(VISIBLE_LINE_BOUNDS_COLOR); + } + + private static void drawVisibleLineBounds(Canvas canvas, Matrix matrix, + List<RectF> visibleLineBounds) { + if (visibleLineBounds.isEmpty()) { + return; + } + setPaintForVisibleLineBounds(); + for (RectF lineBound : visibleLineBounds) { + matrix.mapRect(lineBound); + canvas.drawRect(lineBound, sPaint); + } + } +} diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java index bf8bd14645a0..8380dcf4b4a4 100644 --- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java +++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java @@ -15,28 +15,60 @@ */ package com.google.android.test.handwritingime; +import android.R; import android.annotation.Nullable; +import android.graphics.PointF; +import android.graphics.RectF; import android.inputmethodservice.InputMethodService; import android.util.Log; -import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.Window; +import android.view.inputmethod.CursorAnchorInfo; +import android.view.inputmethod.DeleteGesture; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.HandwritingGesture; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InsertGesture; +import android.view.inputmethod.JoinOrSplitGesture; +import android.view.inputmethod.RemoveSpaceGesture; +import android.view.inputmethod.SelectGesture; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; import android.widget.FrameLayout; -import android.widget.TextView; +import android.widget.LinearLayout; +import android.widget.Spinner; import android.widget.Toast; import java.util.Random; +import java.util.function.IntConsumer; public class HandwritingIme extends InputMethodService { + private static final int OP_NONE = 0; + private static final int OP_SELECT = 1; + private static final int OP_DELETE = 2; + private static final int OP_INSERT = 3; + private static final int OP_REMOVE_SPACE = 4; + private static final int OP_JOIN_OR_SPLIT = 5; - public static final int HEIGHT_DP = 100; - - private Window mInkWindow; private InkView mInk; static final String TAG = "HandwritingIme"; + private int mRichGestureMode = OP_NONE; + private int mRichGestureGranularity = -1; + private Spinner mRichGestureModeSpinner; + private Spinner mRichGestureGranularitySpinner; + private PointF mRichGestureStartPoint; + + static final int BOUNDS_INFO_NONE = 0; + static final int BOUNDS_INFO_VISIBLE_LINE_BOUNDS = 1; + static final int BOUNDS_INFO_EDITOR_BOUNDS = 2; + private int mBoundsInfoMode = BOUNDS_INFO_NONE; + private LinearLayout mBoundsInfoCheckBoxes; + + private final IntConsumer mResultConsumer = value -> Log.d(TAG, "Gesture result: " + value); interface HandwritingFinisher { void finish(); @@ -66,8 +98,108 @@ public class HandwritingIme extends InputMethodService { private void onStylusEvent(@Nullable MotionEvent event) { // TODO Hookup recognizer here - if (event.getAction() == MotionEvent.ACTION_UP) { - sendKeyChar((char) (56 + new Random().nextInt(66))); + switch (event.getAction()) { + case MotionEvent.ACTION_UP: { + if (areRichGesturesEnabled()) { + HandwritingGesture gesture = null; + switch (mRichGestureMode) { + case OP_SELECT: + gesture = new SelectGesture.Builder() + .setGranularity(mRichGestureGranularity) + .setSelectionArea(getSanitizedRectF(mRichGestureStartPoint.x, + mRichGestureStartPoint.y, event.getX(), event.getY())) + .setFallbackText("fallback text") + .build(); + break; + case OP_DELETE: + gesture = new DeleteGesture.Builder() + .setGranularity(mRichGestureGranularity) + .setDeletionArea(getSanitizedRectF(mRichGestureStartPoint.x, + mRichGestureStartPoint.y, event.getX(), event.getY())) + .setFallbackText("fallback text") + .build(); + break; + case OP_INSERT: + gesture = new InsertGesture.Builder() + .setInsertionPoint(new PointF( + mRichGestureStartPoint.x, mRichGestureStartPoint.y)) + .setTextToInsert(" ") + .setFallbackText("fallback text") + .build(); + break; + case OP_REMOVE_SPACE: + gesture = new RemoveSpaceGesture.Builder() + .setPoints( + new PointF(mRichGestureStartPoint.x, + mRichGestureStartPoint.y), + new PointF(event.getX(), event.getY())) + .setFallbackText("fallback text") + .build(); + break; + case OP_JOIN_OR_SPLIT: + gesture = new JoinOrSplitGesture.Builder() + .setJoinOrSplitPoint(new PointF( + mRichGestureStartPoint.x, mRichGestureStartPoint.y)) + .setFallbackText("fallback text") + .build(); + break; + } + if (gesture == null) { + // This shouldn't happen + Log.e(TAG, "Unrecognized gesture mode: " + mRichGestureMode); + return; + } + performGesture(gesture); + } else { + // insert random ASCII char + sendKeyChar((char) (56 + new Random().nextInt(66))); + } + return; + } + case MotionEvent.ACTION_DOWN: { + if (areRichGesturesEnabled()) { + mRichGestureStartPoint = new PointF(event.getX(), event.getY()); + } + return; + } + } + } + + /** + * sanitize values to support rectangles in all cases. + */ + private RectF getSanitizedRectF(float left, float top, float right, float bottom) { + // swap values when left > right OR top > bottom. + if (left > right) { + float temp = left; + left = right; + right = temp; + } + if (top > bottom) { + float temp = top; + top = bottom; + bottom = temp; + } + // increment by a pixel so that RectF.isEmpty() isn't true. + if (left == right) { + right++; + } + if (top == bottom) { + bottom++; + } + + RectF rectF = new RectF(left, top, right, bottom); + Log.d(TAG, "Sending RichGesture " + rectF.toShortString()); + return rectF; + } + + private void performGesture(HandwritingGesture gesture) { + InputConnection ic = getCurrentInputConnection(); + if (getCurrentInputStarted() && ic != null) { + ic.performHandwritingGesture(gesture, Runnable::run, mResultConsumer); + } else { + // This shouldn't happen + Log.e(TAG, "No active InputConnection"); } } @@ -75,29 +207,158 @@ public class HandwritingIme extends InputMethodService { public View onCreateInputView() { Log.d(TAG, "onCreateInputView"); final ViewGroup view = new FrameLayout(this); - final View inner = new View(this); - final float density = getResources().getDisplayMetrics().density; - final int height = (int) (HEIGHT_DP * density); view.setPadding(0, 0, 0, 0); - view.addView(inner, new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, height)); - TextView text = new TextView(this); - text.setText("Handwriting IME"); - text.setTextSize(13f); - text.setTextColor(getColor(android.R.color.white)); - text.setGravity(Gravity.CENTER); - text.setLayoutParams(new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, height)); - view.addView(text); - inner.setBackgroundColor(0xff0110fe); // blue + + LinearLayout layout = new LinearLayout(this); + layout.setLayoutParams(new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + layout.setOrientation(LinearLayout.VERTICAL); + layout.addView(getRichGestureActionsSpinner()); + layout.addView(getRichGestureGranularitySpinner()); + layout.addView(getBoundsInfoCheckBoxes()); + layout.setBackgroundColor(getColor(R.color.holo_green_light)); + view.addView(layout); return view; } + private View getRichGestureActionsSpinner() { + if (mRichGestureModeSpinner != null) { + return mRichGestureModeSpinner; + } + mRichGestureModeSpinner = new Spinner(this); + mRichGestureModeSpinner.setPadding(100, 0, 100, 0); + mRichGestureModeSpinner.setTooltipText("Handwriting IME mode"); + String[] items = new String[]{ + "Handwriting IME - Rich gesture disabled", + "Rich gesture SELECT", + "Rich gesture DELETE", + "Rich gesture INSERT", + "Rich gesture REMOVE SPACE", + "Rich gesture JOIN OR SPLIT", + }; + ArrayAdapter<String> adapter = new ArrayAdapter<>(this, + android.R.layout.simple_spinner_dropdown_item, items); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mRichGestureModeSpinner.setAdapter(adapter); + mRichGestureModeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + mRichGestureMode = position; + mRichGestureGranularitySpinner.setEnabled( + mRichGestureMode == OP_SELECT || mRichGestureMode == OP_DELETE); + Log.d(TAG, "Setting RichGesture Mode " + mRichGestureMode); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + mRichGestureMode = OP_NONE; + mRichGestureGranularitySpinner.setEnabled(false); + } + }); + mRichGestureModeSpinner.setSelection(0); // default disabled + return mRichGestureModeSpinner; + } + + private void updateCursorAnchorInfo(int boundsInfoMode) { + final InputConnection ic = getCurrentInputConnection(); + if (ic == null) return; + + if (boundsInfoMode == BOUNDS_INFO_NONE) { + ic.requestCursorUpdates(0); + return; + } + + final int cursorUpdateMode = InputConnection.CURSOR_UPDATE_MONITOR; + int cursorUpdateFilter = 0; + if ((boundsInfoMode & BOUNDS_INFO_EDITOR_BOUNDS) != 0) { + cursorUpdateFilter |= InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS; + } + + if ((boundsInfoMode & BOUNDS_INFO_VISIBLE_LINE_BOUNDS) != 0) { + cursorUpdateFilter |= InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS; + } + ic.requestCursorUpdates(cursorUpdateMode | cursorUpdateFilter); + } + + private void updateBoundsInfoMode() { + if (mInk != null) { + mInk.setBoundsInfoMode(mBoundsInfoMode); + } + updateCursorAnchorInfo(mBoundsInfoMode); + } + + private View getBoundsInfoCheckBoxes() { + if (mBoundsInfoCheckBoxes != null) { + return mBoundsInfoCheckBoxes; + } + mBoundsInfoCheckBoxes = new LinearLayout(this); + mBoundsInfoCheckBoxes.setPadding(100, 0, 100, 0); + mBoundsInfoCheckBoxes.setOrientation(LinearLayout.HORIZONTAL); + + final CheckBox editorBoundsInfoCheckBox = new CheckBox(this); + editorBoundsInfoCheckBox.setText("EditorBoundsInfo"); + editorBoundsInfoCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked) { + mBoundsInfoMode |= BOUNDS_INFO_EDITOR_BOUNDS; + } else { + mBoundsInfoMode &= ~BOUNDS_INFO_EDITOR_BOUNDS; + } + updateBoundsInfoMode(); + }); + + final CheckBox visibleLineBoundsInfoCheckBox = new CheckBox(this); + visibleLineBoundsInfoCheckBox.setText("VisibleLineBounds"); + visibleLineBoundsInfoCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked) { + mBoundsInfoMode |= BOUNDS_INFO_VISIBLE_LINE_BOUNDS; + } else { + mBoundsInfoMode &= ~BOUNDS_INFO_VISIBLE_LINE_BOUNDS; + } + updateBoundsInfoMode(); + }); + + mBoundsInfoCheckBoxes.addView(editorBoundsInfoCheckBox); + mBoundsInfoCheckBoxes.addView(visibleLineBoundsInfoCheckBox); + return mBoundsInfoCheckBoxes; + } + + private View getRichGestureGranularitySpinner() { + if (mRichGestureGranularitySpinner != null) { + return mRichGestureGranularitySpinner; + } + mRichGestureGranularitySpinner = new Spinner(this); + mRichGestureGranularitySpinner.setPadding(100, 0, 100, 0); + mRichGestureGranularitySpinner.setTooltipText(" Granularity"); + String[] items = + new String[] { "Granularity - UNDEFINED", + "Granularity - WORD", "Granularity - CHARACTER"}; + ArrayAdapter<String> adapter = new ArrayAdapter<>(this, + android.R.layout.simple_spinner_dropdown_item, items); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mRichGestureGranularitySpinner.setAdapter(adapter); + mRichGestureGranularitySpinner.setOnItemSelectedListener( + new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + mRichGestureGranularity = position; + Log.d(TAG, "Setting RichGesture Granularity " + mRichGestureGranularity); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + mRichGestureGranularity = 0; + } + }); + mRichGestureGranularitySpinner.setSelection(1); + return mRichGestureGranularitySpinner; + } + public void onPrepareStylusHandwriting() { Log.d(TAG, "onPrepareStylusHandwriting "); if (mInk == null) { mInk = new InkView(this, new HandwritingFinisherImpl(), new StylusConsumer()); + mInk.setBoundsInfoMode(mBoundsInfoMode); } } @@ -105,8 +366,8 @@ public class HandwritingIme extends InputMethodService { public boolean onStartStylusHandwriting() { Log.d(TAG, "onStartStylusHandwriting "); Toast.makeText(this, "START HW", Toast.LENGTH_SHORT).show(); - mInkWindow = getStylusHandwritingWindow(); - mInkWindow.setContentView(mInk, mInk.getLayoutParams()); + Window inkWindow = getStylusHandwritingWindow(); + inkWindow.setContentView(mInk, mInk.getLayoutParams()); return true; } @@ -118,4 +379,25 @@ public class HandwritingIme extends InputMethodService { ((ViewGroup) mInk.getParent()).removeView(mInk); mInk = null; } + + @Override + public boolean onEvaluateFullscreenMode() { + return false; + } + + private boolean areRichGesturesEnabled() { + return mRichGestureMode != OP_NONE; + } + + @Override + public void onUpdateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) { + if (mInk != null) { + mInk.setCursorAnchorInfo(cursorAnchorInfo); + } + } + + @Override + public void onStartInput(EditorInfo attribute, boolean restarting) { + updateCursorAnchorInfo(mBoundsInfoMode); + } } diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java index 87a5b900cc18..86b324cf08d9 100644 --- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java +++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java @@ -19,27 +19,28 @@ package com.google.android.test.handwritingime; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.Insets; import android.graphics.Paint; import android.graphics.Path; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowMetrics; +import android.view.inputmethod.CursorAnchorInfo; class InkView extends View { - private static final long FINISH_TIMEOUT = 2500; + private static final long FINISH_TIMEOUT = 1500; private final HandwritingIme.HandwritingFinisher mHwCanceller; private final HandwritingIme.StylusConsumer mConsumer; - private final int mTopInset; - private Paint mPaint; - private Path mPath; + private final Paint mPaint; + private final Path mPath; private float mX, mY; private static final float STYLUS_MOVE_TOLERANCE = 1; private Runnable mFinishRunnable; + private CursorAnchorInfo mCursorAnchorInfo; + private int mBoundsInfoMode; + InkView(Context context, HandwritingIme.HandwritingFinisher hwController, HandwritingIme.StylusConsumer consumer) { super(context); @@ -59,12 +60,8 @@ class InkView extends View { WindowManager wm = context.getSystemService(WindowManager.class); WindowMetrics metrics = wm.getCurrentWindowMetrics(); - Insets insets = metrics.getWindowInsets() - .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()); setLayoutParams(new ViewGroup.LayoutParams( - metrics.getBounds().width() - insets.left - insets.right, - metrics.getBounds().height() - insets.top - insets.bottom)); - mTopInset = insets.top; + metrics.getBounds().width(), metrics.getBounds().height())); } @Override @@ -73,17 +70,16 @@ class InkView extends View { canvas.drawPath(mPath, mPaint); canvas.drawARGB(20, 255, 50, 50); + BoundsInfoDrawHelper.draw(canvas, this, mBoundsInfoMode, mCursorAnchorInfo); } private void stylusStart(float x, float y) { - y = y - mTopInset; mPath.moveTo(x, y); mX = x; mY = y; } private void stylusMove(float x, float y) { - y = y - mTopInset; float dx = Math.abs(x - mX); float dy = Math.abs(y - mY); if (mPath.isEmpty()) { @@ -165,4 +161,15 @@ class InkView extends View { return mFinishRunnable; } + void setCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) { + mCursorAnchorInfo = cursorAnchorInfo; + invalidate(); + } + + void setBoundsInfoMode(int boundsInfoMode) { + if (boundsInfoMode != mBoundsInfoMode) { + invalidate(); + } + mBoundsInfoMode = boundsInfoMode; + } } diff --git a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java index 2ad0da98c409..8b9c02049351 100644 --- a/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java +++ b/tests/HierarchyViewerTest/src/com/android/test/hierarchyviewer/ViewDumpParser.java @@ -58,7 +58,7 @@ public class ViewDumpParser { Object hash = getProperty(props, "__hash__"); if (name instanceof String && hash instanceof Integer) { - return String.format(Locale.US, "%s@%x", name, hash); + return String.format(Locale.US, "%s@%x", name, (Integer) hash); } else { return null; } diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index b0ccbd1cf22f..80c7a21dc11b 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -120,6 +120,15 @@ </intent-filter> </activity> + <activity android:name="ColorBitmapActivity" + android:label="Bitmaps/BitmapColors" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="com.android.test.hwui.TEST"/> + </intent-filter> + </activity> + <activity android:name="PathOffsetActivity" android:label="Path/Offset" android:exported="true"> @@ -436,6 +445,15 @@ </intent-filter> </activity> + <activity android:name="SurfaceViewAlphaActivity" + android:label="SurfaceView/SurfaceView with Alpha" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="com.android.test.hwui.TEST"/> + </intent-filter> + </activity> + <activity android:name=".PenStylusActivity" android:label="Pen/Draw" android:exported="true"> @@ -1127,6 +1145,15 @@ </intent-filter> </activity> + <activity android:name="HardwareBufferRendererActivity" + android:label="HardwareRenderer/HardwareBufferRenderer" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="com.android.test.hwui.TEST"/> + </intent-filter> + </activity> + <activity android:name="MyLittleTextureView" android:label="HardwareRenderer/MyLittleTextureView" android:screenOrientation="fullSensor" @@ -1137,5 +1164,21 @@ </intent-filter> </activity> + <activity android:name="MeshActivity" + android:label="Mesh/SimpleMesh" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="com.android.test.hwui.TEST"/> + </intent-filter> + </activity> + <activity android:name="MeshLargeActivity" + android:label="Mesh/LargeMesh" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="com.android.test.hwui.TEST"/> + </intent-filter> + </activity> </application> </manifest> diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java new file mode 100644 index 000000000000..e2d17cdbe9e6 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2023 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.android.test.hwui; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorSpace; +import android.graphics.HardwareBufferRenderer; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.RenderNode; +import android.graphics.Shader; +import android.graphics.SurfaceTexture; +import android.hardware.HardwareBuffer; +import android.media.Image; +import android.media.ImageWriter; +import android.os.Bundle; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.TextureView; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; + +@SuppressWarnings({"UnusedDeclaration"}) +public class ColorBitmapActivity extends Activity implements SurfaceHolder.Callback, + TextureView.SurfaceTextureListener { + + private static final int WIDTH = 512; + private static final int HEIGHT = 512; + + private ImageView mImageView; + private SurfaceView mSurfaceView; + private TextureView mTextureView; + private HardwareBuffer mGradientBuffer; + private Map<View, ImageWriter> mImageWriters = new HashMap<>(); + private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); + private String[] mColorNames = {"sRGB", "BT2020_HLG", "BT2020_PQ"}; + + private int mGradientEndColor = 0xFFFFFFFF; + + private int[] mGradientEndColors = {0xFFFFFFFF, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF}; + private String[] mGradientColorNames = {"Grayscale", "Red", "Green", "Blue"}; + + private final ExecutorService mBufferFenceExecutor = Executors.newFixedThreadPool(1); + private final ExecutorService mBufferExecutor = Executors.newFixedThreadPool(1); + + private FutureTask<HardwareBuffer> authorGradientBuffer( + HardwareBuffer buffer, int gradentEndColor) { + HardwareBufferRenderer renderer = new HardwareBufferRenderer(buffer); + RenderNode node = new RenderNode("content"); + node.setPosition(0, 0, buffer.getWidth(), buffer.getHeight()); + + Canvas canvas = node.beginRecording(); + LinearGradient gradient = new LinearGradient( + 0, 0, buffer.getWidth(), buffer.getHeight(), 0xFF000000, + gradentEndColor, Shader.TileMode.CLAMP); + Paint paint = new Paint(); + paint.setShader(gradient); + paint.setDither(true); + canvas.drawRect(0f, 0f, buffer.getWidth(), buffer.getHeight(), paint); + node.endRecording(); + + renderer.setContentRoot(node); + + ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.SRGB); + FutureTask<HardwareBuffer> resolvedBuffer = new FutureTask<>(() -> buffer); + renderer.obtainRenderRequest() + .setColorSpace(colorSpace) + .draw(mBufferFenceExecutor, result -> { + result.getFence().await(Duration.ofSeconds(3)); + resolvedBuffer.run(); + }); + return resolvedBuffer; + } + + private FutureTask<HardwareBuffer> getGradientBuffer() { + HardwareBuffer buffer = HardwareBuffer.create( + WIDTH, HEIGHT, PixelFormat.RGBA_8888, 1, + HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE + | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT); + return authorGradientBuffer(buffer, mGradientEndColor); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + try { + + mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); + + ArrayAdapter<String> colorSpaceAdapter = new ArrayAdapter<>( + this, android.R.layout.simple_spinner_item, mColorNames); + + colorSpaceAdapter + .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + Spinner colorSpaceSpinner = new Spinner(this); + colorSpaceSpinner.setAdapter(colorSpaceAdapter); + colorSpaceSpinner.setOnItemSelectedListener(new ColorSpaceOnItemSelectedListener()); + + ArrayAdapter<String> gradientColorAdapter = new ArrayAdapter<>( + this, android.R.layout.simple_spinner_item, mGradientColorNames); + + gradientColorAdapter + .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + Spinner gradientColorSpinner = new Spinner(this); + gradientColorSpinner.setAdapter(gradientColorAdapter); + gradientColorSpinner + .setOnItemSelectedListener(new GradientColorOnItemSelectedListener()); + + mGradientBuffer = getGradientBuffer().get(); + + LinearLayout linearLayout = new LinearLayout(this); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + TextView imageViewText = new TextView(this); + imageViewText.setText("ImageView"); + mImageView = new ImageView(this); + + TextView textureViewText = new TextView(this); + textureViewText.setText("TextureView"); + mTextureView = new TextureView(this); + mTextureView.setSurfaceTextureListener(this); + + TextView surfaceViewText = new TextView(this); + surfaceViewText.setText("SurfaceView"); + mSurfaceView = new SurfaceView(this); + mSurfaceView.getHolder().addCallback(this); + + LinearLayout spinnerLayout = new LinearLayout(this); + spinnerLayout.setOrientation(LinearLayout.HORIZONTAL); + + spinnerLayout.addView(colorSpaceSpinner, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + + spinnerLayout.addView(gradientColorSpinner, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + + linearLayout.addView(spinnerLayout, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + + linearLayout.addView(imageViewText, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + linearLayout.addView(mImageView, new LinearLayout.LayoutParams(WIDTH, HEIGHT)); + linearLayout.addView(textureViewText, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + linearLayout.addView(mTextureView, new LinearLayout.LayoutParams(WIDTH, HEIGHT)); + linearLayout.addView(surfaceViewText, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + linearLayout.addView(mSurfaceView, new LinearLayout.LayoutParams(WIDTH, HEIGHT)); + + setContentView(linearLayout); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private ColorSpace getFromName(String name) { + if (name.equals("sRGB")) { + return ColorSpace.get(ColorSpace.Named.SRGB); + } else if (name.equals("BT2020_HLG")) { + return ColorSpace.get(ColorSpace.Named.BT2020_HLG); + } else if (name.equals("BT2020_PQ")) { + return ColorSpace.get(ColorSpace.Named.BT2020_PQ); + } + + throw new RuntimeException("Unrecognized Colorspace!"); + } + + private void populateBuffers() { + try { + Bitmap bitmap = Bitmap.wrapHardwareBuffer( + getGradientBuffer().get(), ColorSpace.get(ColorSpace.Named.SRGB)); + Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, false); + copy.setColorSpace(mColorSpace); + mImageView.setImageBitmap(copy); + + for (ImageWriter writer : mImageWriters.values()) { + mBufferExecutor.execute(() -> { + try (Image image = writer.dequeueInputImage()) { + authorGradientBuffer(image.getHardwareBuffer(), mGradientEndColor).get(); + image.setDataSpace(mColorSpace.getDataSpace()); + writer.queueInputImage(image); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + mImageWriters.put(mSurfaceView, new ImageWriter.Builder(holder.getSurface()) + .setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE + | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT + | HardwareBuffer.USAGE_COMPOSER_OVERLAY) + .build()); + populateBuffers(); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + if (mImageWriters.containsKey(mSurfaceView)) { + mImageWriters.remove(mSurfaceView); + } + } + + @Override + public void onSurfaceTextureAvailable( + @NonNull SurfaceTexture surface, int width, int height) { + mImageWriters.put(mTextureView, new ImageWriter.Builder(new Surface(surface)) + .setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE + | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT) + .build()); + populateBuffers(); + } + + @Override + public void onSurfaceTextureSizeChanged( + @NonNull SurfaceTexture surface, int width, int height) { + + } + + @Override + public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { + if (mImageWriters.containsKey(mTextureView)) { + mImageWriters.remove(mTextureView); + } + return false; + } + + @Override + public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { + + } + + private final class ColorSpaceOnItemSelectedListener + implements AdapterView.OnItemSelectedListener { + + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + ColorBitmapActivity.this.mColorSpace = + getFromName(ColorBitmapActivity.this.mColorNames[position]); + ColorBitmapActivity.this.getMainExecutor() + .execute(ColorBitmapActivity.this::populateBuffers); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + + } + } + + private final class GradientColorOnItemSelectedListener + implements AdapterView.OnItemSelectedListener { + + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + ColorBitmapActivity.this.mGradientEndColor = + ColorBitmapActivity.this.mGradientEndColors[position]; + ColorBitmapActivity.this.getMainExecutor() + .execute(ColorBitmapActivity.this::populateBuffers); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + + } + } +} diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareBufferRendererActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareBufferRendererActivity.java new file mode 100644 index 000000000000..e4de434f1ed2 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareBufferRendererActivity.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 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.android.test.hwui; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorSpace; +import android.graphics.ColorSpace.Named; +import android.graphics.HardwareBufferRenderer; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.RenderNode; +import android.hardware.HardwareBuffer; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import java.time.Duration; +import java.util.concurrent.Executors; + +public class HardwareBufferRendererActivity extends Activity { + + private ImageView mImageView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mImageView = new ImageView(this); + mImageView.setBackgroundColor(Color.MAGENTA); + FrameLayout layout = new FrameLayout(this); + layout.setBackgroundColor(Color.CYAN); + layout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + layout.addView(mImageView, new FrameLayout.LayoutParams(100, 100)); + setContentView(layout); + + HardwareBuffer buffer = HardwareBuffer.create(100, 100, PixelFormat.RGBA_8888, 1, + HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT); + HardwareBufferRenderer renderer = new HardwareBufferRenderer(buffer); + RenderNode node = new RenderNode("content"); + node.setPosition(0, 0, 100, 100); + + Canvas canvas = node.beginRecording(); + canvas.drawColor(Color.BLUE); + + Paint paint = new Paint(); + paint.setColor(Color.RED); + canvas.drawRect(0f, 0f, 50f, 50f, paint); + node.endRecording(); + + renderer.setContentRoot(node); + + ColorSpace colorSpace = ColorSpace.get(Named.SRGB); + Handler handler = new Handler(Looper.getMainLooper()); + renderer.obtainRenderRequest() + .setColorSpace(colorSpace) + .draw(Executors.newSingleThreadExecutor(), result -> { + result.getFence().await(Duration.ofMillis(3000)); + handler.post(() -> { + Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, colorSpace); + Bitmap copy = bitmap.copy(Config.ARGB_8888, false); + mImageView.setImageBitmap(copy); + }); + }); + } +} diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java new file mode 100644 index 000000000000..ae3dcb834687 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java @@ -0,0 +1,125 @@ +/* + * 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.android.test.hwui; + +import android.app.Activity; +import android.content.Context; +import android.graphics.BlendMode; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Mesh; +import android.graphics.MeshSpecification; +import android.graphics.MeshSpecification.Attribute; +import android.graphics.MeshSpecification.Varying; +import android.graphics.Paint; +import android.graphics.RectF; +import android.os.Bundle; +import android.view.View; + +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +public class MeshActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(new MeshView(this)); + } + + static class MeshView extends View { + MeshView(Context c) { + super(c); + this.setOnTouchListener((v, event) -> { + invalidate(); + return true; + }); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + MeshSpecification meshSpec = createMeshSpecification(); + FloatBuffer vertexBuffer = FloatBuffer.allocate(6); + vertexBuffer.put(0, 100.0f); + vertexBuffer.put(1, 100.0f); + vertexBuffer.put(2, 400.0f); + vertexBuffer.put(3, 0.0f); + vertexBuffer.put(4, 0.0f); + vertexBuffer.put(5, 400.0f); + vertexBuffer.rewind(); + Mesh mesh = new Mesh( + meshSpec, Mesh.TRIANGLES, vertexBuffer, 3, new RectF(0, 0, 1000, 1000)); + + canvas.drawMesh(mesh, BlendMode.COLOR, new Paint()); + + int numTriangles = 100; + // number of triangles plus first 2 vertices + FloatBuffer iVertexBuffer = FloatBuffer.allocate(numTriangles * 2 + 4); + ShortBuffer indexBuffer = ShortBuffer.allocate(300); + + int radius = 200; + // origin + iVertexBuffer.put(0, 500.0f); + iVertexBuffer.put(1, 500.0f); + + // first point + iVertexBuffer.put(2, 500.0f + radius); + iVertexBuffer.put(3, 500.0f); + int nVert = 2; + int nInd = 0; + for (int i = 1; i <= numTriangles; i++) { + double angle = (Math.PI * i) / numTriangles; + double x = radius * Math.cos(angle); + double y = radius * Math.sin(angle); + iVertexBuffer.put((i + 1) * 2, 500 + (float) x); + iVertexBuffer.put((i + 1) * 2 + 1, 500 + (float) y); + + indexBuffer.put(nInd++, (short) 0); + indexBuffer.put(nInd++, (short) (nVert - 1)); + indexBuffer.put(nInd++, (short) nVert); + nVert++; + } + iVertexBuffer.rewind(); + indexBuffer.rewind(); + Mesh mesh2 = new Mesh(meshSpec, Mesh.TRIANGLES, iVertexBuffer, 102, indexBuffer, + new RectF(0, 0, 1000, 1000)); + Paint paint = new Paint(); + paint.setColor(Color.RED); + canvas.drawMesh(mesh2, BlendMode.COLOR, paint); + } + + private MeshSpecification createMeshSpecification() { + String vs = "Varyings main(const Attributes attributes) { " + + " Varyings varyings;" + + " varyings.position = attributes.position;" + + " return varyings;" + + "}"; + String fs = "float2 main(const Varyings varyings, out float4 color) {\n" + + " color = vec4(1.0, 0.0, 0.0, 1.0);" + + " return varyings.position;\n" + + "}"; + Attribute[] attList = new Attribute[]{ + new Attribute(MeshSpecification.TYPE_FLOAT2, 0, "position"), + + }; + Varying[] varyList = new Varying[0]; + return MeshSpecification.make(attList, 8, varyList, vs, fs); + } + } +} diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java new file mode 100644 index 000000000000..01ca2fcdbb86 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshLargeActivity.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2023 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.android.test.hwui; + +import android.app.Activity; +import android.content.Context; +import android.graphics.BlendMode; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Mesh; +import android.graphics.MeshSpecification; +import android.graphics.MeshSpecification.Attribute; +import android.graphics.MeshSpecification.Varying; +import android.graphics.Paint; +import android.graphics.RectF; +import android.os.Bundle; +import android.view.View; + +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +public class MeshLargeActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(new MeshView(this)); + } + + static class MeshView extends View { + MeshView(Context c) { + super(c); + this.setOnTouchListener((v, event) -> { + invalidate(); + return true; + }); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + MeshSpecification meshSpec = createMeshSpecification(); + int numTriangles = 10000; + // number of triangles plus first 2 vertices + FloatBuffer vertexBuffer = FloatBuffer.allocate((numTriangles + 2) * 30); + ShortBuffer indexBuffer = ShortBuffer.allocate(numTriangles * 3); + + float origin = 500.0f; + int radius = 200; + + // origin + vertexBuffer.put(0, origin); + vertexBuffer.put(1, origin); + for (int i = 0; i < 7; i++) { + vertexBuffer.put(2 + (i * 4), 1.0f); + vertexBuffer.put(2 + (i * 4) + 1, 1.0f); + vertexBuffer.put(2 + (i * 4) + 2, 1.0f); + vertexBuffer.put(2 + (i * 4) + 3, 1.0f); + } + + // first point + vertexBuffer.put(30, origin + radius); + vertexBuffer.put(31, origin); + for (int i = 0; i < 7; i++) { + vertexBuffer.put(32 + (i * 4), 1.0f); + vertexBuffer.put(32 + (i * 4) + 1, 1.0f); + vertexBuffer.put(32 + (i * 4) + 2, 1.0f); + vertexBuffer.put(32 + (i * 4) + 3, 1.0f); + } + + int nVert = 2; + int nInd = 0; + for (int i = 2; i <= numTriangles + 1; i++) { + double angle = 2 * Math.PI * i / numTriangles; + double x = radius * Math.cos(angle); + double y = radius * Math.sin(angle); + // position + vertexBuffer.put(i * 30, origin + (float) x); + vertexBuffer.put(i * 30 + 1, origin + (float) y); + + // test through test7 + for (int j = 0; j < 7; j++) { + vertexBuffer.put((i * 30 + 2) + (j * 4), 1.0f); + vertexBuffer.put((i * 30 + 2) + (j * 4) + 1, 1.0f); + vertexBuffer.put((i * 30 + 2) + (j * 4) + 2, 1.0f); + vertexBuffer.put((i * 30 + 2) + (j * 4) + 3, 1.0f); + } + + indexBuffer.put(nInd++, (short) 0); + indexBuffer.put(nInd++, (short) (nVert - 1)); + indexBuffer.put(nInd++, (short) nVert); + nVert++; + } + vertexBuffer.rewind(); + indexBuffer.rewind(); + Mesh mesh = new Mesh( + meshSpec, Mesh.TRIANGLES, vertexBuffer, numTriangles + 2, indexBuffer, + new RectF(0, 0, 1000, 1000) + ); + mesh.setFloatUniform("test", 1.0f, 2.0f); + Paint paint = new Paint(); + paint.setColor(Color.BLUE); + + canvas.drawMesh(mesh, BlendMode.SRC, paint); + } + + private MeshSpecification createMeshSpecification() { + String vs = "Varyings main(const Attributes attributes) { " + + " Varyings varyings;" + + " varyings.position = attributes.position;" + + " return varyings;" + + "}"; + String fs = "uniform float2 test;" + + "float2 main(const Varyings varyings, out float4 color) {\n" + + " color = vec4(1.0, 0.0, 0.0, 1.0);" + + " return varyings.position;\n" + + "}"; + Attribute[] attList = new Attribute[]{ + new Attribute(MeshSpecification.TYPE_FLOAT2, 0, "position"), + new Attribute(MeshSpecification.TYPE_FLOAT4, 8, "test"), + new Attribute(MeshSpecification.TYPE_FLOAT4, 24, "test2"), + new Attribute( + MeshSpecification.TYPE_FLOAT4, + 40, + "test3" + ), + new Attribute( + MeshSpecification.TYPE_FLOAT4, + 56, + "test4" + ), + new Attribute( + MeshSpecification.TYPE_FLOAT4, + 72, + "test5" + ), + new Attribute( + MeshSpecification.TYPE_FLOAT4, + 88, + "test6" + ), + new Attribute( + MeshSpecification.TYPE_FLOAT4, + 104, + "test7" + ) + }; + Varying[] varyList = new Varying[0]; + return MeshSpecification.make(attList, 120, varyList, vs, fs); + } + } +} diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/SurfaceViewAlphaActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/SurfaceViewAlphaActivity.java new file mode 100644 index 000000000000..01fe6ae0518b --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/SurfaceViewAlphaActivity.java @@ -0,0 +1,168 @@ +/* + * Copyright 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.android.test.hwui; + +import android.app.Activity; +import android.graphics.Canvas; +import android.graphics.Color; +import android.os.Bundle; +import android.view.SurfaceHolder; +import android.view.SurfaceHolder.Callback; +import android.view.SurfaceView; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.SeekBar; +import android.widget.TextView; + +public class SurfaceViewAlphaActivity extends Activity implements Callback { + SurfaceView mSurfaceView; + + private enum ZOrder { + ABOVE, + BELOW + } + + private float mAlpha = 127f / 255f; + private ZOrder mZOrder = ZOrder.BELOW; + + + private String getAlphaText() { + return "Alpha: " + mAlpha; + } + + private void toggleZOrder() { + if (ZOrder.ABOVE.equals(mZOrder)) { + mZOrder = ZOrder.BELOW; + } else { + mZOrder = ZOrder.ABOVE; + } + } + + // Overlaps a blue view on the left, then the SurfaceView in the center, then a blue view on the + // right. + private void overlapViews(SurfaceView view, LinearLayout parent) { + float density = getResources().getDisplayMetrics().density; + int surfaceViewSize = (int) (200 * density); + int blueViewSize = (int) (surfaceViewSize * 2 / 3f); + int totalSize = (int) (surfaceViewSize * 5 / 3f); + + RelativeLayout overlapLayout = new RelativeLayout(this); + + RelativeLayout.LayoutParams leftViewLayoutParams = new RelativeLayout.LayoutParams( + blueViewSize, surfaceViewSize); + leftViewLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT); + + View leftBlueView = new View(this); + leftBlueView.setBackgroundColor(Color.BLUE); + overlapLayout.addView(leftBlueView, leftViewLayoutParams); + + RelativeLayout.LayoutParams sVLayoutParams = new RelativeLayout.LayoutParams( + surfaceViewSize, surfaceViewSize); + sVLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); + overlapLayout.addView(view, sVLayoutParams); + + RelativeLayout.LayoutParams rightViewLayoutParams = new RelativeLayout.LayoutParams( + blueViewSize, surfaceViewSize); + rightViewLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + + View rightBlueView = new View(this); + rightBlueView.setBackgroundColor(Color.BLUE); + overlapLayout.addView(rightBlueView, rightViewLayoutParams); + + parent.addView(overlapLayout, new LinearLayout.LayoutParams( + totalSize, surfaceViewSize)); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mSurfaceView = new SurfaceView(this); + mSurfaceView.getHolder().addCallback(this); + mSurfaceView.setAlpha(mAlpha); + + LinearLayout content = new LinearLayout(this); + content.setOrientation(LinearLayout.VERTICAL); + + TextView alphaText = new TextView(this); + alphaText.setText(getAlphaText()); + + SeekBar alphaToggle = new SeekBar(this); + alphaToggle.setMin(0); + alphaToggle.setMax(255); + alphaToggle.setProgress(Math.round(mAlpha * 255)); + alphaToggle.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mAlpha = progress / 255f; + alphaText.setText(getAlphaText()); + mSurfaceView.setAlpha(mAlpha); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + content.addView(alphaText, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + + content.addView(alphaToggle, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + + Button button = new Button(this); + button.setText("Z " + mZOrder.toString()); + button.setOnClickListener(v -> { + toggleZOrder(); + mSurfaceView.setZOrderOnTop(ZOrder.ABOVE.equals(mZOrder)); + button.setText("Z " + mZOrder.toString()); + }); + + content.addView(button, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + + overlapViews(mSurfaceView, content); + + setContentView(content); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Canvas canvas = holder.lockCanvas(); + canvas.drawColor(Color.RED); + holder.unlockCanvasAndPost(canvas); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + } +} diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp index de9bbb6ef9fa..4fa6fbe1d4e1 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -13,15 +13,19 @@ android_test { "src/**/*.java", "src/**/*.kt", ], + kotlincflags: [ + "-Werror", + ], platform_apis: true, certificate: "platform", static_libs: [ "androidx.test.ext.junit", "androidx.test.rules", + "mockito-target-minus-junit4", "services.core.unboosted", "testables", "truth-prebuilt", - "ub-uiautomator", + "androidx.test.uiautomator_uiautomator", ], test_suites: ["device-tests"], } diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt index 1d65cc35c3bc..d185ee6ae116 100644 --- a/tests/Input/src/com/android/test/input/AnrTest.kt +++ b/tests/Input/src/com/android/test/input/AnrTest.kt @@ -27,14 +27,15 @@ import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLI import android.os.SystemClock import android.provider.Settings import android.provider.Settings.Global.HIDE_ERROR_DIALOGS -import android.support.test.uiautomator.By -import android.support.test.uiautomator.UiDevice -import android.support.test.uiautomator.UiObject2 -import android.support.test.uiautomator.Until import android.testing.PollingCheck import android.view.InputDevice import android.view.MotionEvent +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until + import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -73,7 +74,7 @@ class AnrTest { val contentResolver = instrumentation.targetContext.contentResolver hideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0) Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, 0) - PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage().getName() + PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage()!!.getName() } @After diff --git a/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java index 63500774816a..d075b5f01ef9 100644 --- a/tests/Input/src/com/android/test/input/InputDeviceTest.java +++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java @@ -17,8 +17,8 @@ package android.view; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import android.hardware.input.HostUsiVersion; import android.os.Parcel; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -55,11 +55,15 @@ public class InputDeviceTest { assertEquals(device.isExternal(), outDevice.isExternal()); assertEquals(device.getSources(), outDevice.getSources()); assertEquals(device.getKeyboardType(), outDevice.getKeyboardType()); + assertEquals(device.getKeyboardLanguageTag(), outDevice.getKeyboardLanguageTag()); + assertEquals(device.getKeyboardLayoutType(), outDevice.getKeyboardLayoutType()); assertEquals(device.getMotionRanges().size(), outDevice.getMotionRanges().size()); + assertEquals(device.getHostUsiVersion(), outDevice.getHostUsiVersion()); + assertEquals(device.getAssociatedDisplayId(), outDevice.getAssociatedDisplayId()); KeyCharacterMap keyCharacterMap = device.getKeyCharacterMap(); KeyCharacterMap outKeyCharacterMap = outDevice.getKeyCharacterMap(); - assertTrue("keyCharacterMap not equal", keyCharacterMap.equals(outKeyCharacterMap)); + assertEquals("keyCharacterMap not equal", keyCharacterMap, outKeyCharacterMap); for (int j = 0; j < device.getMotionRanges().size(); j++) { assertMotionRangeEquals(device.getMotionRanges().get(j), @@ -68,12 +72,39 @@ public class InputDeviceTest { } private void assertInputDeviceParcelUnparcel(KeyCharacterMap keyCharacterMap) { - final InputDevice device = - new InputDevice(DEVICE_ID, 0 /* generation */, 0 /* controllerNumber */, "name", - 0 /* vendorId */, 0 /* productId */, "descriptor", true /* isExternal */, - 0 /* sources */, 0 /* keyboardType */, keyCharacterMap, - false /* hasVibrator */, false /* hasMicrophone */, false /* hasButtonUnderpad */, - true /* hasSensor */, false /* hasBattery */); + final InputDevice.Builder deviceBuilder = new InputDevice.Builder() + .setId(DEVICE_ID) + .setGeneration(42) + .setControllerNumber(43) + .setName("Test Device " + DEVICE_ID) + .setVendorId(44) + .setProductId(45) + .setDescriptor("descriptor") + .setExternal(true) + .setSources(InputDevice.SOURCE_HDMI) + .setKeyboardType(InputDevice.KEYBOARD_TYPE_NON_ALPHABETIC) + .setKeyCharacterMap(keyCharacterMap) + .setHasVibrator(true) + .setHasMicrophone(true) + .setHasButtonUnderPad(true) + .setHasSensor(true) + .setHasBattery(true) + .setKeyboardLanguageTag("en-US") + .setKeyboardLayoutType("qwerty") + .setUsiVersion(new HostUsiVersion(2, 0)); + + for (int i = 0; i < 30; i++) { + deviceBuilder.addMotionRange( + MotionEvent.AXIS_GENERIC_1, + InputDevice.SOURCE_UNKNOWN, + i, + i + 1, + i + 2, + i + 3, + i + 4); + } + + final InputDevice device = deviceBuilder.build(); Parcel parcel = Parcel.obtain(); device.writeToParcel(parcel, 0); diff --git a/tests/Input/src/com/android/test/input/MotionPredictorTest.kt b/tests/Input/src/com/android/test/input/MotionPredictorTest.kt new file mode 100644 index 000000000000..24a567130ff0 --- /dev/null +++ b/tests/Input/src/com/android/test/input/MotionPredictorTest.kt @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2023 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.android.test.input + +import android.content.Context +import android.content.res.Resources +import android.os.SystemProperties +import android.view.InputDevice +import android.view.MotionEvent +import android.view.MotionEvent.ACTION_DOWN +import android.view.MotionEvent.ACTION_MOVE +import android.view.MotionEvent.PointerCoords +import android.view.MotionEvent.PointerProperties +import android.view.MotionPredictor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry + +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` + +import java.time.Duration + +private fun getStylusMotionEvent( + eventTime: Duration, + action: Int, + x: Float, + y: Float, + ): MotionEvent{ + val pointerCount = 1 + val properties = arrayOfNulls<MotionEvent.PointerProperties>(pointerCount) + val coords = arrayOfNulls<MotionEvent.PointerCoords>(pointerCount) + + for (i in 0 until pointerCount) { + properties[i] = PointerProperties() + properties[i]!!.id = i + properties[i]!!.toolType = MotionEvent.TOOL_TYPE_STYLUS + coords[i] = PointerCoords() + coords[i]!!.x = x + coords[i]!!.y = y + } + + return MotionEvent.obtain(/*downTime=*/0, eventTime.toMillis(), action, properties.size, + properties, coords, /*metaState=*/0, /*buttonState=*/0, + /*xPrecision=*/0f, /*yPrecision=*/0f, /*deviceId=*/0, /*edgeFlags=*/0, + InputDevice.SOURCE_STYLUS, /*flags=*/0) +} + +private fun getPredictionContext(offset: Duration, enablePrediction: Boolean): Context { + val context = mock(Context::class.java) + val resources: Resources = mock(Resources::class.java) + `when`(context.getResources()).thenReturn(resources) + `when`(resources.getInteger( + com.android.internal.R.integer.config_motionPredictionOffsetNanos)).thenReturn( + offset.toNanos().toInt()) + `when`(resources.getBoolean( + com.android.internal.R.bool.config_enableMotionPrediction)).thenReturn(enablePrediction) + return context +} + +@RunWith(AndroidJUnit4::class) +@SmallTest +class MotionPredictorTest { + private val instrumentation = InstrumentationRegistry.getInstrumentation() + val initialPropertyValue = SystemProperties.get("persist.input.enable_motion_prediction") + + @Before + fun setUp() { + instrumentation.uiAutomation.executeShellCommand( + "setprop persist.input.enable_motion_prediction true") + } + + @After + fun tearDown() { + instrumentation.uiAutomation.executeShellCommand( + "setprop persist.input.enable_motion_prediction $initialPropertyValue") + } + + /** + * In a typical usage, app will send the event to the predictor and then call .predict to draw + * a prediction. Here, we send 2 events to the predictor and check the returned event. + * Input: + * t = 0 x = 0 y = 0 + * t = 4 x = 10 y = 20 + * Output (expected): + * t = 12 x = 30 y = 60 ± error + * + * Historical data is ignored for simplicity. + */ + @Test + fun testPredictedCoordinatesAndTime() { + val context = getPredictionContext( + /*offset=*/Duration.ofMillis(1), /*enablePrediction=*/true) + val predictor = MotionPredictor(context) + var eventTime = Duration.ofMillis(0) + val downEvent = getStylusMotionEvent(eventTime, ACTION_DOWN, /*x=*/0f, /*y=*/0f) + // ACTION_DOWN t=0 x=0 y=0 + predictor.record(downEvent) + + eventTime += Duration.ofMillis(4) + val moveEvent = getStylusMotionEvent(eventTime, ACTION_MOVE, /*x=*/10f, /*y=*/20f) + // ACTION_MOVE t=1 x=1 y=2 + predictor.record(moveEvent) + + val predicted = predictor.predict(Duration.ofMillis(8).toNanos()) + assertNotNull(predicted) + + // Prediction will happen for t=12 (since it is the next input interval after the requested + // time, 8, plus the model offset, 1). + assertEquals(12, predicted!!.eventTime) + assertEquals(30f, predicted.x, /*delta=*/5f) + assertEquals(60f, predicted.y, /*delta=*/15f) + } +} diff --git a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt index d83a4570fedc..3a24406e2b73 100644 --- a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt +++ b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt @@ -45,7 +45,8 @@ class UnresponsiveGestureMonitorActivity : Activity() { private lateinit var mInputMonitor: InputMonitor override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - mInputMonitor = InputManager.getInstance().monitorGestureInput(MONITOR_NAME, displayId) + val inputManager = getSystemService(InputManager::class.java) + mInputMonitor = inputManager.monitorGestureInput(MONITOR_NAME, displayId) mInputEventReceiver = UnresponsiveReceiver( mInputMonitor.getInputChannel(), Looper.myLooper()) } diff --git a/tests/InputMethodStressTest/Android.bp b/tests/InputMethodStressTest/Android.bp index 0ad38768238a..84845c69fb27 100644 --- a/tests/InputMethodStressTest/Android.bp +++ b/tests/InputMethodStressTest/Android.bp @@ -30,7 +30,9 @@ android_test { ], test_suites: [ "general-tests", - "vts", ], - sdk_version: "31", + data: [ + ":SimpleTestIme", + ], + sdk_version: "current", } diff --git a/tests/InputMethodStressTest/AndroidManifest.xml b/tests/InputMethodStressTest/AndroidManifest.xml index f5fe8f2e8e76..e890c9974882 100644 --- a/tests/InputMethodStressTest/AndroidManifest.xml +++ b/tests/InputMethodStressTest/AndroidManifest.xml @@ -16,11 +16,10 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.inputmethod.stresstest"> - + package="com.android.inputmethod.stresstest"> + <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <application> - <activity android:name=".AutoShowTest$TestActivity"/> - <activity android:name=".ImeOpenCloseStressTest$TestActivity"/> + <activity android:name=".ImeStressTestUtil$TestActivity"/> </application> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" diff --git a/tests/InputMethodStressTest/AndroidTest.xml b/tests/InputMethodStressTest/AndroidTest.xml index 9ac41351f684..bfebb42ad244 100644 --- a/tests/InputMethodStressTest/AndroidTest.xml +++ b/tests/InputMethodStressTest/AndroidTest.xml @@ -19,12 +19,14 @@ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="setprop debug.wm.disable_deprecated_abi_dialog 1" /> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" /> <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="SimpleTestIme.apk" /> <option name="test-file-name" value="InputMethodStressTest.apk" /> </target_preparer> diff --git a/tests/InputMethodStressTest/TEST_MAPPING b/tests/InputMethodStressTest/TEST_MAPPING index ad07205ab02d..06e2ce84eb0f 100644 --- a/tests/InputMethodStressTest/TEST_MAPPING +++ b/tests/InputMethodStressTest/TEST_MAPPING @@ -1,5 +1,5 @@ { - "presubmit": [ + "presubmit-large": [ { "name": "InputMethodStressTest" } diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java index c84c2bcf19c6..807f0c63668c 100644 --- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java @@ -16,77 +16,460 @@ package com.android.inputmethod.stresstest; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; -import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED; - -import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.UNFOCUSABLE_VIEW; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.getWindowAndSoftInputFlagParameters; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.hasUnfocusableWindowFlags; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeAlwaysHiddenWithWindowFlagSet; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus; import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown; -import android.app.Activity; +import static com.google.common.truth.Truth.assertThat; + import android.app.Instrumentation; import android.content.Intent; -import android.os.Bundle; +import android.content.res.Configuration; +import android.os.SystemClock; import android.platform.test.annotations.RootPermissionTest; import android.platform.test.rule.UnlockScreenRule; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.UiObject2; +import android.support.test.uiautomator.Until; +import android.view.WindowManager; import android.widget.EditText; -import android.widget.LinearLayout; -import androidx.annotation.Nullable; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Collections; +import java.util.List; +/** + * Tests to verify the "auto-show" behavior in {@code InputMethodManagerService} when the window + * gaining the focus to start the input. + */ @RootPermissionTest -@RunWith(AndroidJUnit4.class) +@RunWith(Parameterized.class) public final class AutoShowTest { - @Rule - public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule(); - - @Rule - public ScreenCaptureRule mScreenCaptureRule = + @Rule(order = 0) public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule(); + @Rule(order = 1) public ImeStressTestRule mImeStressTestRule = + new ImeStressTestRule(true /* useSimpleTestIme */); + @Rule(order = 2) public ScreenCaptureRule mScreenCaptureRule = new ScreenCaptureRule("/sdcard/InputMethodStressTest"); + @Parameterized.Parameters( + name = "windowFocusFlags={0}, softInputVisibility={1}, softInputAdjustment={2}") + public static List<Object[]> windowAndSoftInputFlagParameters() { + return getWindowAndSoftInputFlagParameters(); + } + + private final int mSoftInputFlags; + private final int mWindowFocusFlags; + private final Instrumentation mInstrumentation; + private final boolean mIsLargeScreen; + + public AutoShowTest(int windowFocusFlags, int softInputVisibility, int softInputAdjustment) { + mSoftInputFlags = softInputVisibility | softInputAdjustment; + mWindowFocusFlags = windowFocusFlags; + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + mIsLargeScreen = mInstrumentation.getContext().getResources() + .getConfiguration().isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); + } + + /** + * Test auto-show IME behavior when the {@link EditText} is focusable ({@link + * EditText#isFocusableInTouchMode} is {@code true}) and has called {@link + * EditText#requestFocus}. + */ + @Test + public void autoShow_hasFocusedView_requestFocus() { + // request focus at onCreate() + Intent intent = + createIntent( + mWindowFocusFlags, + mSoftInputFlags, + Collections.singletonList(REQUEST_FOCUS_ON_CREATE)); + TestActivity activity = TestActivity.start(intent); + + verifyAutoShowBehavior_forwardWithKeyboardOff(activity); + } + + /** + * Test auto-show IME behavior when the {@link EditText} is focusable ({@link + * EditText#isFocusableInTouchMode} is {@code true}) and {@link EditText#requestFocus} is not + * called. The IME should never be shown because there is no focused editor in the window. + */ + @Test + public void autoShow_hasFocusedView_notRequestFocus() { + // request focus not set + Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); + TestActivity activity = TestActivity.start(intent); + EditText editText = activity.getEditText(); + int windowFlags = activity.getWindow().getAttributes().flags; + if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) { + // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus. + verifyWindowAndViewFocus( + editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false); + } else { + verifyWindowAndViewFocus( + editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false); + } + // IME is always hidden because there is no view focus. + verifyImeIsAlwaysHidden(editText); + } + + /** + * Test auto-show IME behavior when the {@link EditText} is not focusable ({@link + * EditText#isFocusableInTouchMode} is {@code false}) and {@link EditText#requestFocus} is not + * called. The IME should never be shown because there is no focusable editor in the window. + */ @Test - public void autoShow() { - Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - Intent intent = new Intent() - .setAction(Intent.ACTION_MAIN) - .setClass(instrumentation.getContext(), TestActivity.class) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - TestActivity activity = (TestActivity) instrumentation.startActivitySync(intent); + public void autoShow_notFocusedView_notRequestFocus() { + // Unfocusable view, request focus not set + Intent intent = + createIntent( + mWindowFocusFlags, + mSoftInputFlags, + Collections.singletonList(UNFOCUSABLE_VIEW)); + TestActivity activity = TestActivity.start(intent); EditText editText = activity.getEditText(); - waitOnMainUntil("activity should gain focus", editText::hasWindowFocus); - waitOnMainUntilImeIsShown(editText); - } - - public static class TestActivity extends Activity { - private EditText mEditText; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // IME will be auto-shown if the following conditions are met: - // 1. SoftInputMode state is SOFT_INPUT_STATE_UNSPECIFIED. - // 2. SoftInputMode adjust is SOFT_INPUT_ADJUST_RESIZE. - getWindow().setSoftInputMode(SOFT_INPUT_STATE_UNSPECIFIED | SOFT_INPUT_ADJUST_RESIZE); - LinearLayout rootView = new LinearLayout(this); - rootView.setOrientation(LinearLayout.VERTICAL); - mEditText = new EditText(this); - rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); - setContentView(rootView); - // 3. The focused view is a text editor (View#onCheckIsTextEditor() returns true). - mEditText.requestFocus(); + + int windowFlags = activity.getWindow().getAttributes().flags; + if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) { + // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus. + verifyWindowAndViewFocus( + editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false); + } else { + verifyWindowAndViewFocus( + editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false); } + // IME is always hidden because there is no focused view. + verifyImeIsAlwaysHidden(editText); + } + + /** + * Test auto-show IME behavior when the activity is navigated forward from another activity with + * keyboard off. + */ + @Test + public void autoShow_forwardWithKeyboardOff() { + // Create first activity with keyboard off + Intent intent1 = + createIntent( + 0x0 /* No window focus flags */, + WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED + | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE, + Collections.emptyList()); + TestActivity firstActivity = TestActivity.start(intent1); + + // Create second activity with parameterized flags: + Intent intent2 = + createIntent( + mWindowFocusFlags, + mSoftInputFlags, + Collections.singletonList(REQUEST_FOCUS_ON_CREATE)); + TestActivity secondActivity = firstActivity.startSecondTestActivity(intent2); + + // The auto-show behavior should be the same as opening the app + verifyAutoShowBehavior_forwardWithKeyboardOff(secondActivity); + } + + /** + * Test auto-show IME behavior when the activity is navigated forward from another activity with + * keyboard on. + */ + @Test + public void autoShow_forwardWithKeyboardOn() { + // Create first activity with keyboard on + Intent intent1 = + createIntent( + 0x0 /* No window focus flags */, + WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED + | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE, + Collections.singletonList(REQUEST_FOCUS_ON_CREATE)); + TestActivity firstActivity = TestActivity.start(intent1); + // Show Ime with InputMethodManager to ensure the keyboard is on. + callOnMainSync(firstActivity::showImeWithInputMethodManager); + SystemClock.sleep(1000); + mInstrumentation.waitForIdleSync(); + + // Create second activity with parameterized flags: + Intent intent2 = + createIntent( + mWindowFocusFlags, + mSoftInputFlags, + Collections.singletonList(REQUEST_FOCUS_ON_CREATE)); + TestActivity secondActivity = firstActivity.startSecondTestActivity(intent2); + + // The auto-show behavior should be the same as open app + verifyAutoShowBehavior_forwardWithKeyboardOn(secondActivity); + } + + /** + * Test auto-show IME behavior when the activity is navigated back from another activity with + * keyboard off. + */ + @Test + public void autoShow_backwardWithKeyboardOff() { + // Not request focus at onCreate() to avoid triggering auto-show behavior + Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); + TestActivity firstActivity = TestActivity.start(intent1); + // Request view focus after app starts + mInstrumentation.runOnMainSync(firstActivity::requestFocus); + + Intent intent2 = + createIntent( + 0x0 /* No window focus flags */, + WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED + | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE, + Collections.emptyList()); + TestActivity secondActivity = firstActivity.startSecondTestActivity(intent2); + secondActivity.finish(); + mInstrumentation.waitForIdleSync(); + + // When activity is navigated back from another activity with keyboard off, the keyboard + // will not show except when soft input visibility flag is SOFT_INPUT_STATE_ALWAYS_VISIBLE. + verifyAutoShowBehavior_backwardWithKeyboardOff(firstActivity); + } + + /** + * Test auto-show IME behavior when the activity is navigated back from another activity with + * keyboard on. + */ + @Test + public void autoShow_backwardWithKeyboardOn() { + // Not request focus at onCreate() to avoid triggering auto-show behavior + Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); + TestActivity activity = TestActivity.start(intent1); + // Request view focus after app starts + mInstrumentation.runOnMainSync(activity::requestFocus); + + // Create second TestActivity + Intent intent2 = + createIntent( + 0x0 /* No window focus flags */, + WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED + | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE, + Collections.singletonList(REQUEST_FOCUS_ON_CREATE)); + ImeStressTestUtil.TestActivity secondActivity = activity.startSecondTestActivity(intent2); + // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity + callOnMainSync(secondActivity::showImeWithInputMethodManager); + SystemClock.sleep(1000); + mInstrumentation.waitForIdleSync(); + // Close the second activity + secondActivity.finish(); + SystemClock.sleep(1000); + mInstrumentation.waitForIdleSync(); + // When activity is navigated back from another activity with keyboard on, the keyboard + // will not hide except when soft input visibility flag is SOFT_INPUT_STATE_ALWAYS_HIDDEN. + verifyAutoShowBehavior_backwardWithKeyboardOn(activity); + } + + @Test + public void clickFocusableView_requestFocus() { + if ((mWindowFocusFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) { + // UiAutomator cannot get UiObject if FLAG_NOT_FOCUSABLE is set + return; + } + Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); + TestActivity activity = TestActivity.start(intent); + // Request view focus after app starts + mInstrumentation.runOnMainSync(activity::requestFocus); + + // Find the editText and click it + UiObject2 editTextUiObject = + UiDevice.getInstance(mInstrumentation) + .wait(Until.findObject(By.clazz(EditText.class)), 5000); + assertThat(editTextUiObject).isNotNull(); + editTextUiObject.click(); + + // Ime will show unless window flag is set + verifyClickBehavior(activity); + } + + @Test + public void clickFocusableView_notRequestFocus() { + if ((mWindowFocusFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) { + // UiAutomator cannot get UiObject if FLAG_NOT_FOCUSABLE is set + return; + } + // Not request focus + Intent intent1 = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); + TestActivity activity = TestActivity.start(intent1); + + // Find the editText and click it + UiObject2 editTextUiObject = + UiDevice.getInstance(mInstrumentation) + .wait(Until.findObject(By.clazz(EditText.class)), 5000); + assertThat(editTextUiObject).isNotNull(); + editTextUiObject.click(); + + // Ime will show unless window flag is set + verifyClickBehavior(activity); + } + + private void verifyAutoShowBehavior_forwardWithKeyboardOff(TestActivity activity) { + if (hasUnfocusableWindowFlags(activity)) { + verifyImeAlwaysHiddenWithWindowFlagSet(activity); + return; + } + + int softInputMode = activity.getWindow().getAttributes().softInputMode; + int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE; + int softInputAdjustment = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; + EditText editText = activity.getEditText(); + + verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true); + switch (softInputVisibility) { + case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE: + case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: { + // IME will be auto-shown if softInputMode is set with flag: + // SOFT_INPUT_STATE_VISIBLE or SOFT_INPUT_STATE_ALWAYS_VISIBLE + waitOnMainUntilImeIsShown(editText); + break; + } + case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED: + case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN: + case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: { + // IME will be not be auto-shown if softInputMode is set with flag: + // SOFT_INPUT_STATE_HIDDEN or SOFT_INPUT_STATE_ALWAYS_HIDDEN, + // or stay unchanged if set SOFT_INPUT_STATE_UNCHANGED + verifyImeIsAlwaysHidden(editText); + break; + } + case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: { + if ((softInputAdjustment + == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) || mIsLargeScreen) { + // The current system behavior will choose to show IME automatically when + // navigating forward to an app that has no visibility state specified + // (i.e. SOFT_INPUT_STATE_UNSPECIFIED) with set SOFT_INPUT_ADJUST_RESIZE + // flag or running on a large screen device. + waitOnMainUntilImeIsShown(editText); + } else { + verifyImeIsAlwaysHidden(editText); + } + break; + } + default: + break; + } + } + + private void verifyAutoShowBehavior_forwardWithKeyboardOn(TestActivity activity) { + int windowFlags = activity.getWindow().getAttributes().flags; + int softInputMode = activity.getWindow().getAttributes().softInputMode; + int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE; + int softInputAdjustment = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; + EditText editText = activity.getEditText(); + + if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) { + // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus. The IME + // will always be hidden even though the view can get focus itself. + verifyWindowAndViewFocus( + editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ true); + // TODO(b/252192121): Ime should be hidden but is shown. + // waitOnMainUntilImeIsHidden(editText); + return; + } else if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0 + || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) { + // When FLAG_ALT_FOCUSABLE_IM or FLAG_LOCAL_FOCUS_MODE is set, the view can gain both + // window focus and view focus but not IME focus. The IME will always be hidden. + verifyWindowAndViewFocus( + editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true); + // TODO(b/252192121): Ime should be hidden but is shown. + // waitOnMainUntilImeIsHidden(editText); + return; + } + verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true); + switch (softInputVisibility) { + case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED: + case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE: + case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: { + // IME will be auto-shown if softInputMode is set with flag: + // SOFT_INPUT_STATE_VISIBLE or SOFT_INPUT_STATE_ALWAYS_VISIBLE + waitOnMainUntilImeIsShown(editText); + break; + } + case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN: + case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: { + // IME will be not be auto-shown if softInputMode is set with flag: + // SOFT_INPUT_STATE_HIDDEN or SOFT_INPUT_STATE_ALWAYS_HIDDEN + // or stay unchanged if set SOFT_INPUT_STATE_UNCHANGED + verifyImeIsAlwaysHidden(editText); + break; + } + case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: { + if ((softInputAdjustment + == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) || mIsLargeScreen) { + // The current system behavior will choose to show IME automatically when + // navigating forward to an app that has no visibility state specified (i.e. + // SOFT_INPUT_STATE_UNSPECIFIED) with set SOFT_INPUT_ADJUST_RESIZE flag or + // running on a large screen device. + waitOnMainUntilImeIsShown(editText); + } else { + verifyImeIsAlwaysHidden(editText); + } + break; + } + default: + break; + } + } + + private static void verifyAutoShowBehavior_backwardWithKeyboardOff(TestActivity activity) { + if (hasUnfocusableWindowFlags(activity)) { + verifyImeAlwaysHiddenWithWindowFlagSet(activity); + return; + } + int softInputMode = activity.getWindow().getAttributes().softInputMode; + int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE; + EditText editText = activity.getEditText(); + + verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true); + if (softInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) { + waitOnMainUntilImeIsShown(editText); + } else { + verifyImeIsAlwaysHidden(editText); + } + } + + private static void verifyAutoShowBehavior_backwardWithKeyboardOn(TestActivity activity) { + if (hasUnfocusableWindowFlags(activity)) { + verifyImeAlwaysHiddenWithWindowFlagSet(activity); + return; + } + int softInputMode = activity.getWindow().getAttributes().softInputMode; + int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE; + EditText editText = activity.getEditText(); + + verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true); + if (softInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) { + verifyImeIsAlwaysHidden(editText); + } else { + waitOnMainUntilImeIsShown(editText); + } + } + + private static void verifyClickBehavior(TestActivity activity) { + int windowFlags = activity.getWindow().getAttributes().flags; + EditText editText = activity.getEditText(); - public EditText getEditText() { - return mEditText; + verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true); + if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0 + || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) { + verifyImeIsAlwaysHidden(editText); + } else { + waitOnMainUntilImeIsShown(editText); } } } diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java new file mode 100644 index 000000000000..0c267b27490b --- /dev/null +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 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.android.inputmethod.stresstest; + +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED; + +import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown; + +import android.content.Intent; +import android.platform.test.annotations.RootPermissionTest; +import android.platform.test.rule.UnlockScreenRule; +import android.widget.EditText; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Test IME visibility by using system default IME to ensure the behavior is consistent + * across Android platform versions. + */ +@RootPermissionTest +@RunWith(Parameterized.class) +public final class DefaultImeVisibilityTest { + + @Rule(order = 0) + public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule(); + // Use system default IME for test. + @Rule(order = 1) + public ImeStressTestRule mImeStressTestRule = + new ImeStressTestRule(false /* useSimpleTestIme */); + + @Rule(order = 2) + public ScreenCaptureRule mScreenCaptureRule = + new ScreenCaptureRule("/sdcard/InputMethodStressTest"); + + private static final int NUM_TEST_ITERATIONS = 10; + + @Parameterized.Parameters(name = "isPortrait={0}") + public static List<Boolean> isPortraitCases() { + // Test in both portrait and landscape mode. + return Arrays.asList(true, false); + } + + public DefaultImeVisibilityTest(boolean isPortrait) { + mImeStressTestRule.setIsPortrait(isPortrait); + } + + @Test + public void showHideDefaultIme() { + Intent intent = + createIntent( + 0x0, /* No window focus flags */ + SOFT_INPUT_STATE_UNSPECIFIED | SOFT_INPUT_ADJUST_RESIZE, + Collections.singletonList(REQUEST_FOCUS_ON_CREATE)); + ImeStressTestUtil.TestActivity activity = ImeStressTestUtil.TestActivity.start(intent); + EditText editText = activity.getEditText(); + for (int i = 0; i < NUM_TEST_ITERATIONS; i++) { + callOnMainSync(activity::showImeWithInputMethodManager); + verifyWindowAndViewFocus(editText, true, true); + waitOnMainUntilImeIsShown(editText); + + callOnMainSync(activity::hideImeWithInputMethodManager); + waitOnMainUntilImeIsHidden(editText); + } + } +} diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java index 14c5cb4bb1a9..5c0212400ff1 100644 --- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java @@ -16,226 +16,579 @@ package com.android.inputmethod.stresstest; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.INPUT_METHOD_MANAGER_HIDE_ON_CREATE; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.INPUT_METHOD_MANAGER_SHOW_ON_CREATE; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.TestActivity.createIntent; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.callOnMainSync; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.getWindowAndSoftInputFlagParameters; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.hasUnfocusableWindowFlags; import static com.android.inputmethod.stresstest.ImeStressTestUtil.isImeShown; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeAlwaysHiddenWithWindowFlagSet; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyImeIsAlwaysHidden; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowAndViewFocus; import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntil; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden; +import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown; + +import static com.google.common.truth.Truth.assertThat; -import android.app.Activity; import android.app.Instrumentation; import android.content.Intent; -import android.os.Bundle; +import android.os.Build; import android.os.SystemClock; import android.platform.test.annotations.RootPermissionTest; import android.platform.test.rule.UnlockScreenRule; +import android.support.test.uiautomator.UiDevice; import android.util.Log; -import android.view.WindowInsets; -import android.view.WindowInsetsAnimation; -import android.view.inputmethod.InputMethodManager; +import android.view.WindowManager; import android.widget.EditText; -import android.widget.LinearLayout; -import androidx.annotation.Nullable; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; -import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; @RootPermissionTest -@RunWith(AndroidJUnit4.class) +@RunWith(Parameterized.class) public final class ImeOpenCloseStressTest { private static final String TAG = "ImeOpenCloseStressTest"; private static final int NUM_TEST_ITERATIONS = 10; - @Rule - public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule(); - - @Rule - public ScreenCaptureRule mScreenCaptureRule = + @Rule(order = 0) public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule(); + @Rule(order = 1) public ImeStressTestRule mImeStressTestRule = + new ImeStressTestRule(true /* useSimpleTestIme */); + @Rule(order = 2) public ScreenCaptureRule mScreenCaptureRule = new ScreenCaptureRule("/sdcard/InputMethodStressTest"); - private Instrumentation mInstrumentation; - @Before - public void setUp() { + private final Instrumentation mInstrumentation; + private final int mSoftInputFlags; + private final int mWindowFocusFlags; + + @Parameterized.Parameters( + name = "windowFocusFlags={0}, softInputVisibility={1}, softInputAdjustment={2}") + public static List<Object[]> windowAndSoftInputFlagParameters() { + return getWindowAndSoftInputFlagParameters(); + } + + public ImeOpenCloseStressTest( + int windowFocusFlags, int softInputVisibility, int softInputAdjustment) { + mSoftInputFlags = softInputVisibility | softInputAdjustment; + mWindowFocusFlags = windowFocusFlags; mInstrumentation = InstrumentationRegistry.getInstrumentation(); } @Test - public void testShowHide_waitingVisibilityChange() { - TestActivity activity = TestActivity.start(); - EditText editText = activity.getEditText(); - waitOnMainUntil("activity should gain focus", editText::hasWindowFocus); - for (int i = 0; i < NUM_TEST_ITERATIONS; i++) { + public void testShowHideWithInputMethodManager_waitingVisibilityChange() { + Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); + TestActivity activity = TestActivity.start(intent); + // Request focus after app starts to avoid triggering auto-show behavior. + mInstrumentation.runOnMainSync(activity::requestFocus); + // Test only once if window flags set to save time. + int iterNum = hasUnfocusableWindowFlags(activity) ? 1 : NUM_TEST_ITERATIONS; + for (int i = 0; i < iterNum; i++) { String msgPrefix = "Iteration #" + i + " "; Log.i(TAG, msgPrefix + "start"); - mInstrumentation.runOnMainSync(activity::showIme); - waitOnMainUntil(msgPrefix + "IME should be visible", () -> isImeShown(editText)); - mInstrumentation.runOnMainSync(activity::hideIme); - waitOnMainUntil(msgPrefix + "IME should be hidden", () -> !isImeShown(editText)); + callOnMainSync(activity::showImeWithInputMethodManager); + verifyShowBehavior(activity); + + callOnMainSync(activity::hideImeWithInputMethodManager); + + verifyHideBehavior(activity); } } @Test - public void testShowHide_waitingAnimationEnd() { - TestActivity activity = TestActivity.start(); + public void testShowHideWithInputMethodManager_waitingAnimationEnd() { + Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); + TestActivity activity = TestActivity.start(intent); + // Request focus after app starts to avoid triggering auto-show behavior. + mInstrumentation.runOnMainSync(activity::requestFocus); + + if (hasUnfocusableWindowFlags(activity)) { + return; // Skip to save time. + } activity.enableAnimationMonitoring(); EditText editText = activity.getEditText(); - waitOnMainUntil("activity should gain focus", editText::hasWindowFocus); for (int i = 0; i < NUM_TEST_ITERATIONS; i++) { String msgPrefix = "Iteration #" + i + " "; Log.i(TAG, msgPrefix + "start"); - mInstrumentation.runOnMainSync(activity::showIme); - waitOnMainUntil(msgPrefix + "IME should be visible", + callOnMainSync(activity::showImeWithInputMethodManager); + waitOnMainUntil( + msgPrefix + "IME should be visible", () -> !activity.isAnimating() && isImeShown(editText)); - mInstrumentation.runOnMainSync(activity::hideIme); - waitOnMainUntil(msgPrefix + "IME should be hidden", + + callOnMainSync(activity::hideImeWithInputMethodManager); + waitOnMainUntil( + msgPrefix + "IME should be hidden", () -> !activity.isAnimating() && !isImeShown(editText)); } } @Test - public void testShowHide_intervalAfterHide() { + public void testShowHideWithInputMethodManager_intervalAfterHide() { // Regression test for b/221483132 - TestActivity activity = TestActivity.start(); - EditText editText = activity.getEditText(); + Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); + TestActivity activity = TestActivity.start(intent); + // Request focus after app starts to avoid triggering auto-show behavior. + mInstrumentation.runOnMainSync(activity::requestFocus); + if (hasUnfocusableWindowFlags(activity)) { + return; // Skip to save time. + } // Intervals = 10, 20, 30, ..., 100, 150, 200, ... List<Integer> intervals = new ArrayList<>(); for (int i = 10; i < 100; i += 10) intervals.add(i); for (int i = 100; i < 1000; i += 50) intervals.add(i); - waitOnMainUntil("activity should gain focus", editText::hasWindowFocus); for (int intervalMillis : intervals) { String msgPrefix = "Interval = " + intervalMillis + " "; Log.i(TAG, msgPrefix + " start"); - mInstrumentation.runOnMainSync(activity::hideIme); + callOnMainSync(activity::hideImeWithInputMethodManager); SystemClock.sleep(intervalMillis); - mInstrumentation.runOnMainSync(activity::showIme); - waitOnMainUntil(msgPrefix + "IME should be visible", - () -> isImeShown(editText)); + + callOnMainSync(activity::showImeWithInputMethodManager); + verifyShowBehavior(activity); } } - @Ignore("b/268556567") @Test - public void testShowHideInSameFrame() { - TestActivity activity = TestActivity.start(); + public void testShowHideWithInputMethodManager_inSameFrame() { + Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); + TestActivity activity = TestActivity.start(intent); + // Request focus after app starts to avoid triggering auto-show behavior. + mInstrumentation.runOnMainSync(activity::requestFocus); + + if (hasUnfocusableWindowFlags(activity)) { + return; // Skip to save time. + } + // hidden -> show -> hide + mInstrumentation.runOnMainSync( + () -> { + Log.i(TAG, "Calling showIme() and hideIme()"); + activity.showImeWithInputMethodManager(); + activity.hideImeWithInputMethodManager(); + }); + // Wait until IMMS / IMS handles messages. + SystemClock.sleep(1000); + mInstrumentation.waitForIdleSync(); + verifyHideBehavior(activity); + + mInstrumentation.runOnMainSync(activity::showImeWithInputMethodManager); + verifyShowBehavior(activity); + mInstrumentation.waitForIdleSync(); + + // shown -> hide -> show + mInstrumentation.runOnMainSync( + () -> { + Log.i(TAG, "Calling hideIme() and showIme()"); + activity.hideImeWithInputMethodManager(); + activity.showImeWithInputMethodManager(); + }); + // Wait until IMMS / IMS handles messages. + SystemClock.sleep(1000); + mInstrumentation.waitForIdleSync(); + verifyShowBehavior(activity); + } + + /** + * Test IME hidden by calling show and hide IME consecutively with + * {@link android.view.inputmethod.InputMethodManager} APIs in + * {@link android.app.Activity#onCreate}. + * + * <p> Note for developers: Use {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_UNCHANGED} + * window flag to avoid some softInputMode visibility flags may take presence over + * {@link android.view.inputmethod.InputMethodManager} APIs (e.g. use showSoftInput to show + * IME in {@link android.app.Activity#onCreate} but being hidden by + * {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN} window flag after the + * activity window focused).</p> + */ + @Test + public void testShowHideWithInputMethodManager_onCreate() { + if (mSoftInputFlags != SOFT_INPUT_STATE_UNCHANGED) { + return; + } + // Show and hide with InputMethodManager at onCreate() + Intent intent = + createIntent( + mWindowFocusFlags, + mSoftInputFlags, + Arrays.asList( + REQUEST_FOCUS_ON_CREATE, + INPUT_METHOD_MANAGER_SHOW_ON_CREATE, + INPUT_METHOD_MANAGER_HIDE_ON_CREATE)); + TestActivity activity = TestActivity.start(intent); + + verifyHideBehavior(activity); + } + + @Test + public void testShowWithInputMethodManager_notRequestFocus() { + Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); + TestActivity activity = TestActivity.start(intent); + + // Show InputMethodManager without requesting focus + callOnMainSync(activity::showImeWithInputMethodManager); + + int windowFlags = activity.getWindow().getAttributes().flags; + EditText editText = activity.getEditText(); + if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) { + verifyWindowAndViewFocus( + editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false); + } else { + verifyWindowAndViewFocus( + editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false); + } + // The Ime should always be hidden because view never gains focus. + verifyImeIsAlwaysHidden(editText); + } + + @Test + public void testShowHideWithWindowInsetsController_waitingVisibilityChange() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + return; + } + Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); + TestActivity activity = TestActivity.start(intent); + // Request focus after app starts to avoid triggering auto-show behavior. + mInstrumentation.runOnMainSync(activity::requestFocus); + // Test only once if window flags set to save time. + int iterNum = hasUnfocusableWindowFlags(activity) ? 1 : NUM_TEST_ITERATIONS; + for (int i = 0; i < iterNum; i++) { + String msgPrefix = "Iteration #" + i + " "; + Log.i(TAG, msgPrefix + "start"); + mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController); + verifyShowBehavior(activity); + mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController); + verifyHideBehavior(activity); + } + } + + @Test + public void testShowHideWithWindowInsetsController_waitingAnimationEnd() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + return; + } + Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); + TestActivity activity = TestActivity.start(intent); + // Request focus after app starts to avoid triggering auto-show behavior. + mInstrumentation.runOnMainSync(activity::requestFocus); + + if (hasUnfocusableWindowFlags(activity)) { + return; // Skip to save time. + } activity.enableAnimationMonitoring(); EditText editText = activity.getEditText(); - waitOnMainUntil("activity should gain focus", editText::hasWindowFocus); + for (int i = 0; i < NUM_TEST_ITERATIONS; i++) { + String msgPrefix = "Iteration #" + i + " "; + Log.i(TAG, msgPrefix + "start"); + mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController); + waitOnMainUntil( + msgPrefix + "IME should be visible", + () -> !activity.isAnimating() && isImeShown(editText)); + + mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController); + waitOnMainUntil( + msgPrefix + "IME should be hidden", + () -> !activity.isAnimating() && !isImeShown(editText)); + } + } + @Test + public void testShowHideWithWindowInsetsController_intervalAfterHide() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + return; + } + Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); + TestActivity activity = TestActivity.start(intent); + // Request focus after app starts to avoid triggering auto-show behavior. + mInstrumentation.runOnMainSync(activity::requestFocus); + + if (hasUnfocusableWindowFlags(activity)) { + return; // Skip to save time. + } + // Intervals = 10, 20, 30, ..., 100, 150, 200, ... + List<Integer> intervals = new ArrayList<>(); + for (int i = 10; i < 100; i += 10) intervals.add(i); + for (int i = 100; i < 1000; i += 50) intervals.add(i); + for (int intervalMillis : intervals) { + String msgPrefix = "Interval = " + intervalMillis + " "; + Log.i(TAG, msgPrefix + " start"); + mInstrumentation.runOnMainSync(activity::hideImeWithWindowInsetsController); + SystemClock.sleep(intervalMillis); + + mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController); + verifyShowBehavior(activity); + } + } + + @Test + public void testShowHideWithWindowInsetsController_inSameFrame() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + return; + } + Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); + TestActivity activity = TestActivity.start(intent); + // Request focus after app starts to avoid triggering auto-show behavior. + mInstrumentation.runOnMainSync(activity::requestFocus); + + if (hasUnfocusableWindowFlags(activity)) { + return; // Skip to save time. + } // hidden -> show -> hide - mInstrumentation.runOnMainSync(() -> { - Log.i(TAG, "Calling showIme() and hideIme()"); - activity.showIme(); - activity.hideIme(); - }); + mInstrumentation.runOnMainSync( + () -> { + Log.i(TAG, "Calling showIme() and hideIme()"); + activity.showImeWithWindowInsetsController(); + activity.hideImeWithWindowInsetsController(); + }); // Wait until IMMS / IMS handles messages. SystemClock.sleep(1000); mInstrumentation.waitForIdleSync(); - waitOnMainUntil("IME should be invisible after show/hide", () -> !isImeShown(editText)); + verifyHideBehavior(activity); - mInstrumentation.runOnMainSync(activity::showIme); - waitOnMainUntil("IME should be visible", - () -> !activity.isAnimating() && isImeShown(editText)); + mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController); + verifyShowBehavior(activity); mInstrumentation.waitForIdleSync(); // shown -> hide -> show - mInstrumentation.runOnMainSync(() -> { - Log.i(TAG, "Calling hideIme() and showIme()"); - activity.hideIme(); - activity.showIme(); - }); + mInstrumentation.runOnMainSync( + () -> { + Log.i(TAG, "Calling hideIme() and showIme()"); + activity.hideImeWithWindowInsetsController(); + activity.showImeWithWindowInsetsController(); + }); // Wait until IMMS / IMS handles messages. SystemClock.sleep(1000); mInstrumentation.waitForIdleSync(); - waitOnMainUntil("IME should be visible after hide/show", - () -> !activity.isAnimating() && isImeShown(editText)); + verifyShowBehavior(activity); } - public static class TestActivity extends Activity { + @Test + public void testShowWithWindowInsetsController_onCreate_requestFocus() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + return; + } + // Show with InputMethodManager at onCreate() + Intent intent = + createIntent( + mWindowFocusFlags, + mSoftInputFlags, + Arrays.asList( + REQUEST_FOCUS_ON_CREATE, WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE)); + TestActivity activity = TestActivity.start(intent); - private EditText mEditText; - private boolean mIsAnimating; + verifyShowBehavior(activity); + } - private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = - new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { - @Override - public WindowInsetsAnimation.Bounds onStart(WindowInsetsAnimation animation, - WindowInsetsAnimation.Bounds bounds) { - mIsAnimating = true; - return super.onStart(animation, bounds); - } + @Test + public void testShowWithWindowInsetsController_onCreate_notRequestFocus() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + return; + } + // Show and hide with InputMethodManager at onCreate() + Intent intent = + createIntent( + mWindowFocusFlags, + mSoftInputFlags, + Collections.singletonList(WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE)); + TestActivity activity = TestActivity.start(intent); - @Override - public void onEnd(WindowInsetsAnimation animation) { - super.onEnd(animation); - mIsAnimating = false; - } + // Ime is shown but with a fallback InputConnection + verifyShowBehaviorNotRequestFocus(activity); + } - @Override - public WindowInsets onProgress(WindowInsets insets, - List<WindowInsetsAnimation> runningAnimations) { - return insets; - } - }; + @Test + public void testShowWithWindowInsetsController_afterStart_notRequestFocus() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + return; + } + // Show and hide with InputMethodManager at onCreate() + Intent intent = createIntent(mWindowFocusFlags, mSoftInputFlags, Collections.emptyList()); + TestActivity activity = TestActivity.start(intent); + mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController); + + // Ime is shown but with a fallback InputConnection + verifyShowBehaviorNotRequestFocus(activity); + } - public static TestActivity start() { - Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - Intent intent = new Intent() - .setAction(Intent.ACTION_MAIN) - .setClass(instrumentation.getContext(), TestActivity.class) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - return (TestActivity) instrumentation.startActivitySync(intent); + /** + * Test IME hidden by calling show and hide IME consecutively with + * {@link android.view.WindowInsetsController} APIs in {@link android.app.Activity#onCreate}. + * + * <p> Note for developers: Use {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_UNCHANGED} + * window flag to avoid some softInputMode visibility flags may take presence over + * {@link android.view.WindowInsetsController} APIs (e.g. use showSoftInput to show + * IME in {@link android.app.Activity#onCreate} but being hidden by + * {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN} window flag after the + * activity window focused).</p> + */ + @Test + public void testHideWithWindowInsetsController_onCreate_requestFocus() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + return; + } + if (mSoftInputFlags != SOFT_INPUT_STATE_UNCHANGED) { + return; } + // Show and hide with InputMethodManager at onCreate() + Intent intent = + createIntent( + mWindowFocusFlags, + mSoftInputFlags, + Arrays.asList( + REQUEST_FOCUS_ON_CREATE, + WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE, + WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE)); + TestActivity activity = TestActivity.start(intent); + + verifyHideBehavior(activity); + } + + @Test + public void testScreenOffOn() throws Exception { + Intent intent1 = + createIntent( + mWindowFocusFlags, + mSoftInputFlags, + Collections.singletonList(REQUEST_FOCUS_ON_CREATE)); + TestActivity activity = TestActivity.start(intent1); + // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity + callOnMainSync(activity::showImeWithInputMethodManager); + + Thread.sleep(1000); + verifyShowBehavior(activity); + + UiDevice uiDevice = UiDevice.getInstance(mInstrumentation); - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - LinearLayout rootView = new LinearLayout(this); - rootView.setOrientation(LinearLayout.VERTICAL); - mEditText = new EditText(this); - rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); - setContentView(rootView); + if (uiDevice.isScreenOn()) { + uiDevice.sleep(); } + Thread.sleep(1000); + if (!uiDevice.isScreenOn()) { + uiDevice.wakeUp(); + } + + verifyShowBehavior(activity); + } + + // TODO: Add tests for activities that don't handle the rotation. + @Test + public void testRotateScreenWithKeyboardOn() throws Exception { + Intent intent = + createIntent( + mWindowFocusFlags, + mSoftInputFlags, + Collections.singletonList(REQUEST_FOCUS_ON_CREATE)); + TestActivity activity = TestActivity.start(intent); + // Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity + callOnMainSync(activity::showImeWithInputMethodManager); + Thread.sleep(2000); + verifyShowBehavior(activity); + + UiDevice uiDevice = UiDevice.getInstance(mInstrumentation); + + uiDevice.setOrientationRight(); + uiDevice.waitForIdle(); + Thread.sleep(1000); + Log.i(TAG, "Rotate screen right"); + assertThat(uiDevice.isNaturalOrientation()).isFalse(); + verifyRotateBehavior(activity); - public EditText getEditText() { - return mEditText; + uiDevice.setOrientationLeft(); + uiDevice.waitForIdle(); + Thread.sleep(1000); + Log.i(TAG, "Rotate screen left"); + assertThat(uiDevice.isNaturalOrientation()).isFalse(); + verifyRotateBehavior(activity); + + uiDevice.setOrientationNatural(); + uiDevice.waitForIdle(); + } + + private static void verifyShowBehavior(TestActivity activity) { + if (hasUnfocusableWindowFlags(activity)) { + verifyImeAlwaysHiddenWithWindowFlagSet(activity); + return; } + EditText editText = activity.getEditText(); - public void showIme() { - Log.i(TAG, "TestActivity.showIme"); - mEditText.requestFocus(); - InputMethodManager imm = getSystemService(InputMethodManager.class); - imm.showSoftInput(mEditText, 0); + verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true); + waitOnMainUntilImeIsShown(editText); + } + + private static void verifyHideBehavior(TestActivity activity) { + if (hasUnfocusableWindowFlags(activity)) { + verifyImeAlwaysHiddenWithWindowFlagSet(activity); + return; } + EditText editText = activity.getEditText(); - public void hideIme() { - Log.i(TAG, "TestActivity.hideIme"); - InputMethodManager imm = getSystemService(InputMethodManager.class); - imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); + verifyWindowAndViewFocus(editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ true); + waitOnMainUntilImeIsHidden(editText); + } + + private static void verifyShowBehaviorNotRequestFocus(TestActivity activity) { + int windowFlags = activity.getWindow().getAttributes().flags; + EditText editText = activity.getEditText(); + + if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) { + verifyWindowAndViewFocus( + editText, /*expectWindowFocus*/ false, /*expectViewFocus*/ false); + verifyImeIsAlwaysHidden(editText); + } else if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0 + || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) { + verifyWindowAndViewFocus( + editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false); + verifyImeIsAlwaysHidden(editText); + } else { + verifyWindowAndViewFocus( + editText, /*expectWindowFocus*/ true, /*expectViewFocus*/ false); + // Ime is shown but with a fallback InputConnection + waitOnMainUntilImeIsShown(editText); } + } - public void enableAnimationMonitoring() { - // Enable WindowInsetsAnimation. - // Note that this has a side effect of disabling InsetsAnimationThreadControlRunner. - InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - getWindow().setDecorFitsSystemWindows(false); - mEditText.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback); - }); + private static void verifyRotateBehavior(TestActivity activity) { + // Get the new TestActivity after recreation. + TestActivity newActivity = TestActivity.getLastCreatedInstance(); + assertThat(newActivity).isNotNull(); + assertThat(newActivity).isNotEqualTo(activity); + + EditText newEditText = newActivity.getEditText(); + int softInputMode = newActivity.getWindow().getAttributes().softInputMode; + int softInputVisibility = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE; + + if (hasUnfocusableWindowFlags(newActivity)) { + verifyImeAlwaysHiddenWithWindowFlagSet(newActivity); + return; } - public boolean isAnimating() { - return mIsAnimating; + if (softInputVisibility == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) { + // After rotation, the keyboard would be hidden only when the flag is + // SOFT_INPUT_STATE_ALWAYS_HIDDEN. However, SOFT_INPUT_STATE_HIDDEN is different because + // it requires appending SOFT_INPUT_IS_FORWARD_NAVIGATION flag, which won't be added + // when rotating the devices (rotating doesn't navigate forward to the next app window.) + verifyWindowAndViewFocus(newEditText, /*expectWindowFocus*/ true, /*expectViewFocus*/ + true); + waitOnMainUntilImeIsHidden(newEditText); + + } else { + // Other cases, keyboard would be shown. + verifyWindowAndViewFocus(newEditText, /*expectWindowFocus*/ true, /*expectViewFocus*/ + true); + waitOnMainUntilImeIsShown(newEditText); } } } diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java new file mode 100644 index 000000000000..12104b298dac --- /dev/null +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestRule.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2023 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.android.inputmethod.stresstest; + +import android.app.Instrumentation; +import android.os.RemoteException; +import android.support.test.uiautomator.UiDevice; + +import androidx.test.platform.app.InstrumentationRegistry; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; + +import java.io.IOException; + +/** + * Do setup and cleanup for Ime stress tests, including disabling lock and auto-rotate screen, + * pressing home and enabling a simple test Ime during the tests. + */ +public class ImeStressTestRule extends TestWatcher { + private static final String LOCK_SCREEN_OFF_COMMAND = "locksettings set-disabled true"; + private static final String LOCK_SCREEN_ON_COMMAND = "locksettings set-disabled false"; + private static final String SET_PORTRAIT_MODE_COMMAND = "settings put system user_rotation 0"; + private static final String SET_LANDSCAPE_MODE_COMMAND = "settings put system user_rotation 1"; + private static final String SIMPLE_IME_ID = + "com.android.apps.inputmethod.simpleime/.SimpleInputMethodService"; + private static final String ENABLE_IME_COMMAND = "ime enable " + SIMPLE_IME_ID; + private static final String SET_IME_COMMAND = "ime set " + SIMPLE_IME_ID; + private static final String DISABLE_IME_COMMAND = "ime disable " + SIMPLE_IME_ID; + private static final String RESET_IME_COMMAND = "ime reset"; + + @NonNull private final Instrumentation mInstrumentation; + @NonNull private final UiDevice mUiDevice; + // Whether the screen orientation is set to portrait. + private boolean mIsPortrait; + // Whether to use a simple test Ime or system default Ime for test. + private final boolean mUseSimpleTestIme; + + public ImeStressTestRule(boolean useSimpleTestIme) { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + mUiDevice = UiDevice.getInstance(mInstrumentation); + // Default is portrait mode + mIsPortrait = true; + mUseSimpleTestIme = useSimpleTestIme; + } + + public void setIsPortrait(boolean isPortrait) { + mIsPortrait = isPortrait; + } + + @Override + protected void starting(Description description) { + disableLockScreen(); + setOrientation(); + mUiDevice.pressHome(); + if (mUseSimpleTestIme) { + enableSimpleIme(); + } else { + resetImeToDefault(); + } + + mInstrumentation.waitForIdleSync(); + } + + @Override + protected void finished(Description description) { + if (mUseSimpleTestIme) { + disableSimpleIme(); + } + unfreezeRotation(); + restoreLockScreen(); + } + + private void disableLockScreen() { + try { + executeShellCommand(LOCK_SCREEN_OFF_COMMAND); + } catch (IOException e) { + throw new RuntimeException("Could not disable lock screen.", e); + } + } + + private void restoreLockScreen() { + try { + executeShellCommand(LOCK_SCREEN_ON_COMMAND); + } catch (IOException e) { + throw new RuntimeException("Could not enable lock screen.", e); + } + } + + private void setOrientation() { + try { + mUiDevice.freezeRotation(); + executeShellCommand( + mIsPortrait ? SET_PORTRAIT_MODE_COMMAND : SET_LANDSCAPE_MODE_COMMAND); + } catch (IOException e) { + throw new RuntimeException("Could not set screen orientation.", e); + } catch (RemoteException e) { + throw new RuntimeException("Could not freeze rotation.", e); + } + } + + private void unfreezeRotation() { + try { + mUiDevice.unfreezeRotation(); + } catch (RemoteException e) { + throw new RuntimeException("Could not unfreeze screen rotation.", e); + } + } + + private void enableSimpleIme() { + try { + executeShellCommand(ENABLE_IME_COMMAND); + executeShellCommand(SET_IME_COMMAND); + } catch (IOException e) { + throw new RuntimeException("Could not enable SimpleTestIme.", e); + } + } + + private void disableSimpleIme() { + try { + executeShellCommand(DISABLE_IME_COMMAND); + } catch (IOException e) { + throw new RuntimeException("Could not disable SimpleTestIme.", e); + } + } + + private void resetImeToDefault() { + try { + executeShellCommand(RESET_IME_COMMAND); + } catch (IOException e) { + throw new RuntimeException("Could not reset Ime to default.", e); + } + } + + private @NonNull String executeShellCommand(@NonNull String cmd) throws IOException { + return mUiDevice.executeShellCommand(cmd); + } +} diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java index ba2ba3c75bc2..f3c81942ae42 100644 --- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java @@ -16,15 +16,38 @@ package com.android.inputmethod.stresstest; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP; + import static com.android.compatibility.common.util.SystemUtil.eventually; import static com.google.common.truth.Truth.assertWithMessage; +import android.app.Activity; +import android.app.Instrumentation; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; import android.view.View; import android.view.WindowInsets; +import android.view.WindowInsetsAnimation; +import android.view.WindowInsetsController; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.LinearLayout; +import androidx.annotation.Nullable; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.compatibility.common.util.ThrowingRunnable; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -32,27 +55,96 @@ import java.util.concurrent.atomic.AtomicReference; /** Utility methods for IME stress test. */ public final class ImeStressTestUtil { - private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5); + private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(3); + + private ImeStressTestUtil() {} + + private static final int[] WINDOW_FOCUS_FLAGS = + new int[] { + LayoutParams.FLAG_NOT_FOCUSABLE, + LayoutParams.FLAG_ALT_FOCUSABLE_IM, + LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_ALT_FOCUSABLE_IM, + LayoutParams.FLAG_LOCAL_FOCUS_MODE + }; + + private static final int[] SOFT_INPUT_VISIBILITY_FLAGS = + new int[] { + LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED, + LayoutParams.SOFT_INPUT_STATE_UNCHANGED, + LayoutParams.SOFT_INPUT_STATE_HIDDEN, + LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN, + LayoutParams.SOFT_INPUT_STATE_VISIBLE, + LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE, + }; - private ImeStressTestUtil() { + private static final int[] SOFT_INPUT_ADJUST_FLAGS = + new int[] { + LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED, + LayoutParams.SOFT_INPUT_ADJUST_RESIZE, + LayoutParams.SOFT_INPUT_ADJUST_PAN, + LayoutParams.SOFT_INPUT_ADJUST_NOTHING + }; + + public static final String SOFT_INPUT_FLAGS = "soft_input_flags"; + public static final String WINDOW_FLAGS = "window_flags"; + public static final String UNFOCUSABLE_VIEW = "unfocusable_view"; + public static final String REQUEST_FOCUS_ON_CREATE = "request_focus_on_create"; + public static final String INPUT_METHOD_MANAGER_SHOW_ON_CREATE = + "input_method_manager_show_on_create"; + public static final String INPUT_METHOD_MANAGER_HIDE_ON_CREATE = + "input_method_manager_hide_on_create"; + public static final String WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE = + "window_insets_controller_show_on_create"; + public static final String WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE = + "window_insets_controller_hide_on_create"; + + /** Parameters for show/hide ime parameterized tests. */ + public static ArrayList<Object[]> getWindowAndSoftInputFlagParameters() { + ArrayList<Object[]> params = new ArrayList<>(); + + // Set different window focus flags and keep soft input flags as default values (4 cases) + for (int windowFocusFlags : WINDOW_FOCUS_FLAGS) { + params.add( + new Object[] { + windowFocusFlags, + LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED, + LayoutParams.SOFT_INPUT_ADJUST_RESIZE + }); + } + // Set the combinations of different softInputVisibility, softInputAdjustment flags, + // keep the window focus flag as default value ( 6 * 4 = 24 cases) + for (int softInputVisibility : SOFT_INPUT_VISIBILITY_FLAGS) { + for (int softInputAdjust : SOFT_INPUT_ADJUST_FLAGS) { + params.add( + new Object[] { + 0x0 /* No window focus flags */, softInputVisibility, softInputAdjust + }); + } + } + return params; } /** Checks if the IME is shown on the window that the given view belongs to. */ public static boolean isImeShown(View view) { WindowInsets insets = view.getRootWindowInsets(); + if (insets == null) { + return false; + } return insets.isVisible(WindowInsets.Type.ime()); } /** Calls the callable on the main thread and returns the result. */ public static <V> V callOnMainSync(Callable<V> callable) { AtomicReference<V> result = new AtomicReference<>(); - InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { - try { - result.set(callable.call()); - } catch (Exception e) { - throw new RuntimeException("Exception was thrown", e); - } - }); + InstrumentationRegistry.getInstrumentation() + .runOnMainSync( + () -> { + try { + result.set(callable.call()); + } catch (Exception e) { + throw new RuntimeException("Exception was thrown", e); + } + }); return result.get(); } @@ -67,14 +159,335 @@ public final class ImeStressTestUtil { /** Waits until IME is shown, or throws on timeout. */ public static void waitOnMainUntilImeIsShown(View view) { - eventually(() -> assertWithMessage("IME should be shown").that( - callOnMainSync(() -> isImeShown(view))).isTrue(), TIMEOUT); + eventually( + () -> + assertWithMessage("IME should be shown") + .that(callOnMainSync(() -> isImeShown(view))) + .isTrue(), + TIMEOUT); } /** Waits until IME is hidden, or throws on timeout. */ public static void waitOnMainUntilImeIsHidden(View view) { - //eventually(() -> assertThat(callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT); - eventually(() -> assertWithMessage("IME should be hidden").that( - callOnMainSync(() -> isImeShown(view))).isFalse(), TIMEOUT); + eventually( + () -> + assertWithMessage("IME should be hidden") + .that(callOnMainSync(() -> isImeShown(view))) + .isFalse(), + TIMEOUT); + } + + /** Waits until window get focus, or throws on timeout. */ + public static void waitOnMainUntilWindowGainsFocus(View view) { + eventually( + () -> + assertWithMessage("Window should gain focus") + .that(callOnMainSync(view::hasWindowFocus)) + .isTrue(), + TIMEOUT); + } + + /** Waits until view get focus, or throws on timeout. */ + public static void waitOnMainUntilViewGainsFocus(View view) { + eventually( + () -> + assertWithMessage("View should gain focus") + .that(callOnMainSync(view::hasFocus)) + .isTrue(), + TIMEOUT); + } + + /** Verify IME is always hidden within the given time duration. */ + public static void verifyImeIsAlwaysHidden(View view) { + always( + () -> + assertWithMessage("IME should be hidden") + .that(callOnMainSync(() -> isImeShown(view))) + .isFalse(), + TIMEOUT); + } + + /** Verify the window never gains focus within the given time duration. */ + public static void verifyWindowNeverGainsFocus(View view) { + always( + () -> + assertWithMessage("window should never gain focus") + .that(callOnMainSync(view::hasWindowFocus)) + .isFalse(), + TIMEOUT); + } + + /** Verify the view never gains focus within the given time duration. */ + public static void verifyViewNeverGainsFocus(View view) { + always( + () -> + assertWithMessage("view should never gain ime focus") + .that(callOnMainSync(view::hasFocus)) + .isFalse(), + TIMEOUT); + } + + /** + * Make sure that a {@link Runnable} always finishes without throwing a {@link Exception} in the + * given duration + * + * @param r The {@link Runnable} to run. + * @param timeoutMillis The number of milliseconds to wait for {@code r} to not throw + */ + public static void always(ThrowingRunnable r, long timeoutMillis) { + long start = System.currentTimeMillis(); + + while (true) { + try { + r.run(); + if (System.currentTimeMillis() - start >= timeoutMillis) { + return; + } + try { + Thread.sleep(100); + } catch (InterruptedException ignored) { + // Do nothing + } + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + } + + public static boolean hasUnfocusableWindowFlags(Activity activity) { + int windowFlags = activity.getWindow().getAttributes().flags; + return (windowFlags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0 + || (windowFlags & LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0 + || (windowFlags & LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0; + } + + public static void verifyWindowAndViewFocus( + View view, boolean expectWindowFocus, boolean expectViewFocus) { + if (expectWindowFocus) { + waitOnMainUntilWindowGainsFocus(view); + } else { + verifyWindowNeverGainsFocus(view); + } + if (expectViewFocus) { + waitOnMainUntilViewGainsFocus(view); + } else { + verifyViewNeverGainsFocus(view); + } + } + + public static void verifyImeAlwaysHiddenWithWindowFlagSet(TestActivity activity) { + int windowFlags = activity.getWindow().getAttributes().flags; + View view = activity.getEditText(); + if ((windowFlags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0) { + // When FLAG_NOT_FOCUSABLE is set true, the view will never gain window focus. The IME + // will always be hidden even though the view can get focus itself. + verifyWindowAndViewFocus(view, /*expectWindowFocus*/ false, /*expectViewFocus*/ true); + verifyImeIsAlwaysHidden(view); + } else if ((windowFlags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0 + || (windowFlags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0) { + // When FLAG_ALT_FOCUSABLE_IM or FLAG_LOCAL_FOCUS_MODE is set, the view can gain both + // window focus and view focus but not IME focus. The IME will always be hidden. + verifyWindowAndViewFocus(view, /*expectWindowFocus*/ true, /*expectViewFocus*/ true); + verifyImeIsAlwaysHidden(view); + } + } + + /** Activity to help test show/hide behavior of IME. */ + public static class TestActivity extends Activity { + private static final String TAG = "ImeStressTestUtil.TestActivity"; + private EditText mEditText; + private boolean mIsAnimating; + private static WeakReference<TestActivity> sLastCreatedInstance = + new WeakReference<>(null); + + private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback = + new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { + @Override + public WindowInsetsAnimation.Bounds onStart( + WindowInsetsAnimation animation, WindowInsetsAnimation.Bounds bounds) { + mIsAnimating = true; + return super.onStart(animation, bounds); + } + + @Override + public void onEnd(WindowInsetsAnimation animation) { + super.onEnd(animation); + mIsAnimating = false; + } + + @Override + public WindowInsets onProgress( + WindowInsets insets, List<WindowInsetsAnimation> runningAnimations) { + return insets; + } + }; + + /** Create intent with extras. */ + public static Intent createIntent( + int windowFlags, int softInputFlags, List<String> extras) { + Intent intent = + new Intent() + .putExtra(WINDOW_FLAGS, windowFlags) + .putExtra(SOFT_INPUT_FLAGS, softInputFlags); + for (String extra : extras) { + intent.putExtra(extra, true); + } + return intent; + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.i(TAG, "onCreate()"); + sLastCreatedInstance = new WeakReference<>(this); + boolean isUnfocusableView = getIntent().getBooleanExtra(UNFOCUSABLE_VIEW, false); + boolean requestFocus = getIntent().getBooleanExtra(REQUEST_FOCUS_ON_CREATE, false); + int softInputFlags = getIntent().getIntExtra(SOFT_INPUT_FLAGS, 0); + int windowFlags = getIntent().getIntExtra(WINDOW_FLAGS, 0); + boolean showWithInputMethodManagerOnCreate = + getIntent().getBooleanExtra(INPUT_METHOD_MANAGER_SHOW_ON_CREATE, false); + boolean hideWithInputMethodManagerOnCreate = + getIntent().getBooleanExtra(INPUT_METHOD_MANAGER_HIDE_ON_CREATE, false); + boolean showWithWindowInsetsControllerOnCreate = + getIntent().getBooleanExtra(WINDOW_INSETS_CONTROLLER_SHOW_ON_CREATE, false); + boolean hideWithWindowInsetsControllerOnCreate = + getIntent().getBooleanExtra(WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE, false); + + getWindow().addFlags(windowFlags); + getWindow().setSoftInputMode(softInputFlags); + + LinearLayout rootView = new LinearLayout(this); + rootView.setOrientation(LinearLayout.VERTICAL); + mEditText = new EditText(this); + if (isUnfocusableView) { + mEditText.setFocusableInTouchMode(false); + } + rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); + setContentView(rootView); + + if (requestFocus) { + requestFocus(); + } + if (showWithInputMethodManagerOnCreate) { + showImeWithInputMethodManager(); + } + if (hideWithInputMethodManagerOnCreate) { + hideImeWithInputMethodManager(); + } + if (showWithWindowInsetsControllerOnCreate) { + showImeWithWindowInsetsController(); + } + if (hideWithWindowInsetsControllerOnCreate) { + hideImeWithWindowInsetsController(); + } + } + + /** Get the last created TestActivity instance. */ + @Nullable + public static TestActivity getLastCreatedInstance() { + return sLastCreatedInstance.get(); + } + + /** Show IME with InputMethodManager. */ + public boolean showImeWithInputMethodManager() { + boolean showResult = + getInputMethodManager() + .showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT); + if (showResult) { + Log.i(TAG, "IMM#showSoftInput successfully"); + } else { + Log.i(TAG, "IMM#showSoftInput failed"); + } + return showResult; + } + + /** Hide IME with InputMethodManager. */ + public boolean hideImeWithInputMethodManager() { + boolean hideResult = + getInputMethodManager().hideSoftInputFromWindow(mEditText.getWindowToken(), 0); + if (hideResult) { + Log.i(TAG, "IMM#hideSoftInput successfully"); + } else { + Log.i(TAG, "IMM#hideSoftInput failed"); + } + return hideResult; + } + + /** Show IME with WindowInsetsController */ + public void showImeWithWindowInsetsController() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + return; + } + Log.i(TAG, "showImeWithWIC()"); + WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController(); + assertWithMessage("WindowInsetsController shouldn't be null.") + .that(windowInsetsController) + .isNotNull(); + windowInsetsController.show(WindowInsets.Type.ime()); + } + + /** Hide IME with WindowInsetsController. */ + public void hideImeWithWindowInsetsController() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + return; + } + Log.i(TAG, "hideImeWithWIC()"); + WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController(); + assertWithMessage("WindowInsetsController shouldn't be null.") + .that(windowInsetsController) + .isNotNull(); + windowInsetsController.hide(WindowInsets.Type.ime()); + } + + private InputMethodManager getInputMethodManager() { + return getSystemService(InputMethodManager.class); + } + + public EditText getEditText() { + return mEditText; + } + + /** Start TestActivity with intent. */ + public static TestActivity start(Intent intent) { + Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + intent.setAction(Intent.ACTION_MAIN) + .setClass(instrumentation.getContext(), TestActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + return (TestActivity) instrumentation.startActivitySync(intent); + } + + /** Start the second TestActivity with intent. */ + public TestActivity startSecondTestActivity(Intent intent) { + Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + intent.setClass(TestActivity.this, TestActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return (TestActivity) instrumentation.startActivitySync(intent); + } + + public void enableAnimationMonitoring() { + // Enable WindowInsetsAnimation. + // Note that this has a side effect of disabling InsetsAnimationThreadControlRunner. + InstrumentationRegistry.getInstrumentation() + .runOnMainSync( + () -> { + getWindow().setDecorFitsSystemWindows(false); + mEditText.setWindowInsetsAnimationCallback( + mWindowInsetsAnimationCallback); + }); + } + + public boolean isAnimating() { + return mIsAnimating; + } + + public void requestFocus() { + boolean requestFocusResult = getEditText().requestFocus(); + if (requestFocusResult) { + Log.i(TAG, "Request focus successfully"); + } else { + Log.i(TAG, "Request focus failed"); + } + } } } diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java index 573b3b695a90..f4a04a163ebb 100644 --- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java @@ -42,6 +42,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.uiautomator.By; import androidx.test.uiautomator.BySelector; import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject2; import androidx.test.uiautomator.Until; import org.junit.After; @@ -74,13 +75,12 @@ public final class NotificationTest { // This is for AOSP System UI for phones. When testing customized System UI, please modify here. private static final BySelector REPLY_SEND_BUTTON_SELECTOR = - By.res("com.android.systemui", "remote_input_send"); + By.res("com.android.systemui", "remote_input_send").enabled(true); - @Rule - public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule(); - - @Rule - public ScreenCaptureRule mScreenCaptureRule = + @Rule(order = 0) public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule(); + @Rule(order = 1) public ImeStressTestRule mImeStressTestRule = + new ImeStressTestRule(true /* useSimpleTestIme */); + @Rule(order = 2) public ScreenCaptureRule mScreenCaptureRule = new ScreenCaptureRule("/sdcard/InputMethodStressTest"); private Context mContext; @@ -119,7 +119,15 @@ public final class NotificationTest { mUiDevice.pressKeyCode(KeyEvent.KEYCODE_A); mUiDevice.pressKeyCode(KeyEvent.KEYCODE_B); mUiDevice.pressKeyCode(KeyEvent.KEYCODE_C); - mUiDevice.wait(Until.findObject(REPLY_SEND_BUTTON_SELECTOR.enabled(true)), TIMEOUT).click(); + UiObject2 sendButton = mUiDevice.wait( + Until.findObject(REPLY_SEND_BUTTON_SELECTOR), TIMEOUT); + if (sendButton == null) { + // If the screen is too small, sendButton may be hidden by IME. + // Dismiss IME and try again. + mUiDevice.pressBack(); + sendButton = mUiDevice.wait(Until.findObject(REPLY_SEND_BUTTON_SELECTOR), TIMEOUT); + } + sendButton.click(); // Verify that IME is gone. assertThat(mUiDevice.wait(Until.gone(By.pkg(getImePackage(mContext))), TIMEOUT)).isTrue(); } @@ -132,7 +140,8 @@ public final class NotificationTest { // Post inline reply notification. PendingIntent pendingIntent = PendingIntent.getBroadcast( - mContext, REPLY_REQUEST_CODE, new Intent().setAction(ACTION_REPLY), + mContext, REPLY_REQUEST_CODE, + new Intent().setAction(ACTION_REPLY).setClass(mContext, NotificationTest.class), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); RemoteInput remoteInput = new RemoteInput.Builder(REPLY_INPUT_KEY) .setLabel(REPLY_INPUT_LABEL) diff --git a/tests/Internal/src/android/app/WallpaperColorsTest.java b/tests/Internal/src/android/app/WallpaperColorsTest.java index 9ffb236d3f59..70660a0a117e 100644 --- a/tests/Internal/src/android/app/WallpaperColorsTest.java +++ b/tests/Internal/src/android/app/WallpaperColorsTest.java @@ -48,10 +48,10 @@ public class WallpaperColorsTest { } /** - * Check that white supports dark text and black doesn't + * Check that white surface supports dark text */ @Test - public void colorHintsTest() { + public void whiteSurfaceColorHintsTest() { Bitmap image = Bitmap.createBitmap(30, 30, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(image); @@ -59,28 +59,68 @@ public class WallpaperColorsTest { int hints = WallpaperColors.fromBitmap(image).getColorHints(); boolean supportsDarkText = (hints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0; boolean supportsDarkTheme = (hints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0; - boolean fromBitmap = (hints & WallpaperColors.HINT_FROM_BITMAP) != 0; Assert.assertTrue("White surface should support dark text.", supportsDarkText); Assert.assertFalse("White surface shouldn't support dark theme.", supportsDarkTheme); - Assert.assertTrue("From bitmap should be true if object was created " - + "using WallpaperColors#fromBitmap.", fromBitmap); - - canvas.drawColor(Color.BLACK); - hints = WallpaperColors.fromBitmap(image).getColorHints(); - supportsDarkText = (hints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0; - supportsDarkTheme = (hints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0; - Assert.assertFalse("Black surface shouldn't support dark text.", supportsDarkText); - Assert.assertTrue("Black surface should support dark theme.", supportsDarkTheme); Paint paint = new Paint(); paint.setStyle(Paint.Style.FILL); paint.setColor(Color.BLACK); - canvas.drawColor(Color.WHITE); canvas.drawRect(0, 0, 8, 8, paint); supportsDarkText = (WallpaperColors.fromBitmap(image) .getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0; Assert.assertFalse("Light surface shouldn't support dark text " + "when it contains dark pixels.", supportsDarkText); + } + + /** + * Check that x-small white region supports dark text when max number of dark pixels = 0 + */ + @Test + public void xSmallWhiteSurfaceColorHintsTest() { + Bitmap xsmall_image = Bitmap.createBitmap(1, 5, Bitmap.Config.ARGB_8888); + Canvas xsmall_canvas = new Canvas(xsmall_image); + + xsmall_canvas.drawColor(Color.WHITE); + int hints = WallpaperColors.fromBitmap(xsmall_image).getColorHints(); + boolean supportsDarkText = (hints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0; + boolean supportsDarkTheme = (hints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0; + Assert.assertTrue("X-small white surface should support dark text.", + supportsDarkText); + Assert.assertFalse("X-small white surface shouldn't support dark theme.", + supportsDarkTheme); + } + + /** + * Check that black surface doesn't support dark text + */ + @Test + public void blackSurfaceColorHintsTest() { + Bitmap image = Bitmap.createBitmap(30, 30, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(image); + + canvas.drawColor(Color.BLACK); + int hints = WallpaperColors.fromBitmap(image).getColorHints(); + boolean supportsDarkText = (hints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0; + boolean supportsDarkTheme = (hints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0; + Assert.assertFalse("Black surface shouldn't support dark text.", supportsDarkText); + Assert.assertTrue("Black surface should support dark theme.", supportsDarkTheme); + } + + /** + * Check that bitmap hint properly indicates when object created via WallpaperColors#fromBitmap + * versus WallpaperColors() public constructor + */ + @Test + public void bitmapHintsTest() { + Bitmap image = Bitmap.createBitmap(30, 30, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(image); + + canvas.drawColor(Color.WHITE); + int hints = WallpaperColors.fromBitmap(image).getColorHints(); + + boolean fromBitmap = (hints & WallpaperColors.HINT_FROM_BITMAP) != 0; + Assert.assertTrue("From bitmap should be true if object was created " + + "using WallpaperColors#fromBitmap.", fromBitmap); WallpaperColors colors = new WallpaperColors(Color.valueOf(Color.GREEN), null, null); fromBitmap = (colors.getColorHints() & WallpaperColors.HINT_FROM_BITMAP) != 0; diff --git a/tests/Internal/src/android/service/wallpaper/OWNERS b/tests/Internal/src/android/service/wallpaper/OWNERS new file mode 100644 index 000000000000..5a26d0e1f62b --- /dev/null +++ b/tests/Internal/src/android/service/wallpaper/OWNERS @@ -0,0 +1,4 @@ +dupin@google.com +santie@google.com +pomini@google.com +poultney@google.com
\ No newline at end of file diff --git a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java index 153ca79e346b..0c5e8d481131 100644 --- a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java +++ b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java @@ -85,4 +85,17 @@ public class WallpaperServiceTest { assertEquals("onAmbientModeChanged should have been called", 2, zoomChangedCount[0]); } + @Test + public void testNotifyColorsOfDestroyedEngine_doesntCrash() { + WallpaperService service = new WallpaperService() { + @Override + public Engine onCreateEngine() { + return new Engine(); + } + }; + WallpaperService.Engine engine = service.onCreateEngine(); + engine.detach(); + + engine.notifyColorsChanged(); + } } diff --git a/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java b/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java new file mode 100644 index 000000000000..d16e90e26aaa --- /dev/null +++ b/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java @@ -0,0 +1,267 @@ +/* + * 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.android.internal.app; + +import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyObject; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.os.LocaleList; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.app.LocaleStore.LocaleInfo; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +/** + * Unit tests for the {@link AppLocaleCollector}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AppLocaleCollectorTest { + private static final String TAG = "AppLocaleCollectorTest"; + private AppLocaleCollector mAppLocaleCollector; + private LocaleStore.LocaleInfo mAppCurrentLocale; + private Set<LocaleInfo> mAllAppActiveLocales; + private Set<LocaleInfo> mImeLocales; + private Set<LocaleInfo> mSystemCurrentLocales; + private Set<LocaleInfo> mSystemSupportedLocales; + private AppLocaleStore.AppLocaleResult mResult; + private static final String PKG1 = "pkg1"; + private static final int NONE = LocaleInfo.SUGGESTION_TYPE_NONE; + private static final int SIM = LocaleInfo.SUGGESTION_TYPE_SIM; + private static final int CFG = LocaleInfo.SUGGESTION_TYPE_CFG; + private static final int SIM_CFG = SIM | CFG; + private static final int CURRENT = LocaleInfo.SUGGESTION_TYPE_CURRENT; + private static final int SYSTEM = LocaleInfo.SUGGESTION_TYPE_SYSTEM_LANGUAGE; + private static final int OTHERAPP = LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE; + private static final int IME = LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE; + private static final int SYSTEM_AVAILABLE = + LocaleInfo.SUGGESTION_TYPE_SYSTEM_AVAILABLE_LANGUAGE; + + @Before + public void setUp() throws Exception { + mAppLocaleCollector = spy( + new AppLocaleCollector(InstrumentationRegistry.getContext(), PKG1)); + } + + @Test + public void testGetSystemCurrentLocales() { + LocaleList.setDefault( + LocaleList.forLanguageTags("en-US-u-mu-fahrenhe,ar-JO-u-mu-fahrenhe-nu-latn")); + + Set<LocaleStore.LocaleInfo> list = + mAppLocaleCollector.getSystemCurrentLocales(); + + LocaleList expected = LocaleList.forLanguageTags("en-US,ar-JO-u-nu-latn"); + assertEquals(list.size(), expected.size()); + for (LocaleStore.LocaleInfo info : list) { + assertTrue(expected.indexOf(info.getLocale()) != -1); + } + } + + @Test + public void testGetSupportedLocaleList_filterNonAppsupportedSystemLanguage() { + mAppCurrentLocale = createLocaleInfo("en-US", CURRENT); + + // App supports five locales + HashSet<Locale> appSupported = + getAppSupportedLocales(new String[] { + "en-US", + "fr", + "ar", + "es", + "bn" + }); + // There are six locales in system current locales. + mSystemCurrentLocales = getSystemCurrentLocales(new String[] { + "en-US", + "fr-FR", + "ar-JO", + "ca-AD", + "da-DK", + "es-US" + }); + mAllAppActiveLocales = Collections.emptySet(); + mImeLocales = Collections.emptySet(); + mSystemSupportedLocales = Collections.emptySet(); + mResult = new AppLocaleStore.AppLocaleResult(GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG, + appSupported); + + doReturn(mAppCurrentLocale).when(mAppLocaleCollector).getAppCurrentLocale(); + doReturn(mResult).when(mAppLocaleCollector).getAppSupportedLocales(); + doReturn(mAllAppActiveLocales).when(mAppLocaleCollector).getAllAppActiveLocales(); + doReturn(mImeLocales).when(mAppLocaleCollector).getActiveImeLocales(); + doReturn(mSystemSupportedLocales).when(mAppLocaleCollector).getSystemSupportedLocale( + anyObject(), eq(null), eq(true)); + doReturn(mSystemCurrentLocales).when( + mAppLocaleCollector).getSystemCurrentLocales(); + + Set<LocaleInfo> result = mAppLocaleCollector.getSupportedLocaleList(null, true, false); + + // The result would show four rather than six locales in the suggested region. + HashMap<String, Integer> expectedResult = new HashMap<>(); + expectedResult.put("en-US", CURRENT); // The locale current App activates. + expectedResult.put("ar-JO", SYSTEM_AVAILABLE); + expectedResult.put("fr-FR", SYSTEM_AVAILABLE); + expectedResult.put("es-US", SYSTEM_AVAILABLE); + expectedResult.put(createLocaleInfo("", SYSTEM).getId(), SYSTEM); // System language title + + assertEquals(result.size(), expectedResult.size()); + for (LocaleStore.LocaleInfo info: result) { + int suggestionFlags = expectedResult.getOrDefault(info.getId(), -1); + assertEquals(info.mSuggestionFlags, suggestionFlags); + } + } + + @Test + public void testGetSupportedLocaleList_withActiveLocalesFromOtherAppAndIme() { + mAppCurrentLocale = createLocaleInfo("en-US", CURRENT); + mAllAppActiveLocales = initAllAppActivatedLocales(); + mImeLocales = initImeLocales(); + mSystemSupportedLocales = initSystemSupportedLocales(); + mSystemCurrentLocales = initSystemCurrentLocales(); + mResult = new AppLocaleStore.AppLocaleResult(GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG, + initAppSupportedLocale()); + + doReturn(mAppCurrentLocale).when(mAppLocaleCollector).getAppCurrentLocale(); + doReturn(mResult).when(mAppLocaleCollector).getAppSupportedLocales(); + doReturn(mAllAppActiveLocales).when(mAppLocaleCollector).getAllAppActiveLocales(); + doReturn(mImeLocales).when(mAppLocaleCollector).getActiveImeLocales(); + doReturn(mSystemSupportedLocales).when(mAppLocaleCollector).getSystemSupportedLocale( + anyObject(), eq(null), eq(true)); + doReturn(mSystemCurrentLocales).when( + mAppLocaleCollector).getSystemCurrentLocales(); + + Set<LocaleInfo> result = mAppLocaleCollector.getSupportedLocaleList(null, true, false); + + HashMap<String, Integer> expectedResult = getExpectedResult(); + assertEquals(result.size(), expectedResult.size()); + for (LocaleInfo source : result) { + int suggestionFlags = expectedResult.getOrDefault(source.getId(), -1); + assertEquals(source.mSuggestionFlags, suggestionFlags); + } + } + + private HashMap<String, Integer> getExpectedResult() { + HashMap<String, Integer> map = new HashMap<>(); + map.put("en-US", CURRENT); // The locale current App activates. + map.put("fr", NONE); // The locale App and system support. + map.put("zu", NONE); // The locale App and system support. + map.put("en", NONE); // Use en because System supports en while APP supports en-CA, en-GB. + map.put("ko", NONE); // The locale App and system support. + map.put("en-AU", OTHERAPP); // The locale other App activates and current App supports. + map.put("en-CA", OTHERAPP); // The locale other App activates and current App supports. + map.put("en-IN", IME); // The locale IME supports. + map.put("ja-JP", + OTHERAPP | SYSTEM_AVAILABLE | IME); // The locale exists in OTHERAPP, SYSTEM and IME + map.put("zh-Hant-TW", SYSTEM_AVAILABLE); // The locale system activates. + map.put(createLocaleInfo("", SYSTEM).getId(), SYSTEM); // System language title + return map; + } + + private Set<LocaleInfo> initSystemSupportedLocales() { + return Set.of( + createLocaleInfo("en", NONE), + createLocaleInfo("fr", NONE), + createLocaleInfo("zu", NONE), + createLocaleInfo("ko", NONE), + // will be filtered because current App doesn't support. + createLocaleInfo("es-US", SIM_CFG) + ); + } + + private Set<LocaleInfo> initSystemCurrentLocales() { + return Set.of(createLocaleInfo("zh-Hant-TW", SYSTEM_AVAILABLE), + createLocaleInfo("ja-JP", SYSTEM_AVAILABLE), + // will be filtered because current App activates this locale. + createLocaleInfo("en-US", SYSTEM_AVAILABLE)); + } + + private Set<LocaleInfo> initAllAppActivatedLocales() { + return Set.of( + createLocaleInfo("en-CA", OTHERAPP), + createLocaleInfo("en-AU", OTHERAPP), + createLocaleInfo("ja-JP", OTHERAPP), + // will be filtered because current App activates this locale. + createLocaleInfo("en-US", OTHERAPP)); + } + + private Set<LocaleInfo> initImeLocales() { + return Set.of( + // will be filtered because system activates zh-Hant-TW. + createLocaleInfo("zh-TW", IME), + // will be filtered because current App's activats this locale. + createLocaleInfo("en-US", IME), + createLocaleInfo("ja-JP", IME), + createLocaleInfo("en-IN", IME)); + } + + private HashSet<Locale> initAppSupportedLocale() { + HashSet<Locale> hs = new HashSet(); + hs.add(Locale.forLanguageTag("en-US")); + hs.add(Locale.forLanguageTag("en-CA")); + hs.add(Locale.forLanguageTag("en-GB")); + hs.add(Locale.forLanguageTag("zh-TW")); + hs.add(Locale.forLanguageTag("ja")); + hs.add(Locale.forLanguageTag("fr")); + hs.add(Locale.forLanguageTag("zu")); + hs.add(Locale.forLanguageTag("ko")); + // will be filtered because it's not in the system language. + hs.add(Locale.forLanguageTag("mn")); + return hs; + } + + private Set<LocaleStore.LocaleInfo> getSystemCurrentLocales(String []languageTags) { + HashSet<LocaleStore.LocaleInfo> hs = new HashSet<>(languageTags.length); + for (String tag:languageTags) { + hs.add(createLocaleInfo(tag, SYSTEM_AVAILABLE)); + } + return hs; + } + + private HashSet<Locale> getAppSupportedLocales(String []languageTags) { + HashSet<Locale> hs = new HashSet<>(languageTags.length); + for (String language:languageTags) { + hs.add(Locale.forLanguageTag(language)); + } + return hs; + } + + private LocaleInfo createLocaleInfo(String languageTag, int suggestionFlag) { + LocaleInfo localeInfo = LocaleStore.fromLocale(Locale.forLanguageTag(languageTag)); + localeInfo.mSuggestionFlags = suggestionFlag; + localeInfo.setTranslated(true); + return localeInfo; + } +} diff --git a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java new file mode 100644 index 000000000000..f6568816b4f7 --- /dev/null +++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java @@ -0,0 +1,263 @@ +/* + * 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.android.internal.app; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.os.LocaleList; +import android.view.inputmethod.InputMethodSubtype; +import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.app.LocaleStore.LocaleInfo; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IllformedLocaleException; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +/** Unit tests for the {@link LocaleStore}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class LocaleStoreTest { + @Test + public void testTransformImeLanguageTagToLocaleInfo() { + List<InputMethodSubtype> list = List.of( + new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(), + new InputMethodSubtypeBuilder().setLanguageTag("zh-TW").build(), + new InputMethodSubtypeBuilder().setLanguageTag("ja-JP").build()); + + Set<LocaleInfo> localeSet = LocaleStore.transformImeLanguageTagToLocaleInfo(list); + + Set<String> expectedLanguageTag = Set.of("en-US", "zh-TW", "ja-JP"); + assertEquals(localeSet.size(), expectedLanguageTag.size()); + for (LocaleInfo info : localeSet) { + assertEquals(info.mSuggestionFlags, LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE); + assertTrue(expectedLanguageTag.contains(info.getId())); + } + } + + @Test + public void convertExplicitLocales_noExplicitLcoales_returnEmptyHashMap() { + Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales(); + + HashMap<String, LocaleInfo> result = + LocaleStore.convertExplicitLocales( + LocaleList.getEmptyLocaleList(), supportedLocale); + + assertTrue(result.isEmpty()); + } + + @Test + public void convertExplicitLocales_hasEmptyLocale_receiveException() { + Locale[] locales = {Locale.forLanguageTag(""), Locale.forLanguageTag("en-US")}; + LocaleList localelist = new LocaleList(locales); + Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales(); + + boolean isReceiveException = false; + try { + LocaleStore.convertExplicitLocales(localelist, supportedLocale); + } catch (IllformedLocaleException e) { + isReceiveException = true; + } + + assertTrue(isReceiveException); + } + + @Test + public void convertExplicitLocales_hasSameLocale_returnNonSameLocales() { + LocaleList locales = LocaleList.forLanguageTags("en-US,en-US"); + Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales(); + + HashMap<String, LocaleInfo> result = + LocaleStore.convertExplicitLocales(locales, supportedLocale); + + // Only has "en" and "en-US". + assertTrue(result.size() == 2); + } + + @Test + public void convertExplicitLocales_hasEnUs_resultHasParentEn() { + LocaleList locales = LocaleList.forLanguageTags("en-US,ja-JP"); + Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales(); + + HashMap<String, LocaleInfo> result = + LocaleStore.convertExplicitLocales(locales, supportedLocale); + + assertEquals(result.get("en").getId(), "en"); + } + + @Test + public void convertExplicitLocales_hasZhTw_resultZhHantTw() { + LocaleList locales = LocaleList.forLanguageTags("zh-TW,en-US,en"); + Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales(); + + HashMap<String, LocaleInfo> result = + LocaleStore.convertExplicitLocales(locales, supportedLocale); + + assertEquals("zh-Hant-TW", result.get("zh-Hant-TW").getId()); + } + + @Test + public void convertExplicitLocales_nonRegularFormat_resultEmptyContry() { + LocaleList locales = LocaleList.forLanguageTags("de-1996,de-1901"); + Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales(); + + HashMap<String, LocaleInfo> result = + LocaleStore.convertExplicitLocales(locales, supportedLocale); + + assertEquals("de-1996", result.get("de-1996").getId()); + assertTrue(result.get("de-1996").getLocale().getCountry().isEmpty()); + } + + @Test + public void convertExplicitLocales_differentEnFormat() { + LocaleList locales = LocaleList.forLanguageTags("en-Latn-US,en-US,en"); + Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales(); + + HashMap<String, LocaleInfo> result = + LocaleStore.convertExplicitLocales(locales, supportedLocale); + assertEquals("en", result.get("en").getId()); + assertEquals("en-US", result.get("en-US").getId()); + assertNull(result.get("en-Latn-US")); + } + + @Test + public void getLevelLocales_languageTier_returnAllSupportLanguages() { + LocaleList testSupportedLocales = + LocaleList.forLanguageTags( + "en-US,zh-Hant-TW,ja-JP,en-GB,bn-IN-u-nu-arab,ks-Arab-IN,bn-IN"); + + Set<String> ignorableLocales = new HashSet<>(); + ignorableLocales.add("zh-Hant-HK"); + LocaleInfo parent = null; + + Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales( + null, ignorableLocales, parent, false, testSupportedLocales); + + assertEquals(5, localeInfos.size()); + localeInfos.forEach(localeInfo -> { + assertTrue(localeInfo.getLocale().getCountry().isEmpty()); + }); + assertTrue(localeInfos.stream().anyMatch( + info -> info.getLocale().toLanguageTag().equals("en"))); + assertTrue(localeInfos.stream().anyMatch( + info -> info.getLocale().toLanguageTag().equals("zh-Hant"))); + assertTrue(localeInfos.stream().anyMatch( + info -> info.getLocale().toLanguageTag().equals("ja"))); + assertTrue(localeInfos.stream().anyMatch( + info -> info.getLocale().toLanguageTag().equals("bn"))); + assertTrue(localeInfos.stream().anyMatch( + info -> info.getLocale().toLanguageTag().equals("ks-Arab"))); + } + + @Test + public void getLevelLocales_regionTierAndParentIsEn_returnEnLocales() { + LocaleList testSupportedLocales = + LocaleList.forLanguageTags( + "en-US,en-GB,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN"); + Set<String> ignorableLocales = new HashSet<>(); + ignorableLocales.add("zh-Hant-HK"); + LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("en")); + + Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales( + null, ignorableLocales, parent, false, testSupportedLocales); + + assertEquals(3, localeInfos.size()); + localeInfos.forEach(localeInfo -> { + assertEquals("en", localeInfo.getLocale().getLanguage()); + }); + assertTrue(localeInfos.stream().anyMatch( + info -> info.getLocale().toLanguageTag().equals("en-US"))); + assertTrue(localeInfos.stream().anyMatch( + info -> info.getLocale().toLanguageTag().equals("en-GB"))); + assertTrue(localeInfos.stream().anyMatch( + info -> info.getLocale().toLanguageTag().equals("en-ZA"))); + } + + @Test + public void getLevelLocales_numberingTierAndParentIsBnIn_returnBnInLocales() { + LocaleList testSupportedLocales = + LocaleList.forLanguageTags( + "en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm"); + Set<String> ignorableLocales = new HashSet<>(); + ignorableLocales.add("zh-Hant-HK"); + LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn")); + + Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales( + null, ignorableLocales, parent, false, testSupportedLocales); + + assertEquals(1, localeInfos.size()); + assertEquals("bn-IN", localeInfos.iterator().next().getLocale().toLanguageTag()); + } + + @Test + public void getLevelLocales_regionTierAndParentIsBnInAndIgnoreBn_returnEmpty() { + LocaleList testSupportedLocales = + LocaleList.forLanguageTags( + "en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm"); + Set<String> ignorableLocales = new HashSet<>(); + ignorableLocales.add("bn-IN"); + LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn-IN")); + + Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales( + null, ignorableLocales, parent, false, testSupportedLocales); + + assertEquals(0, localeInfos.size()); + } + + @Test + public void getLevelLocales_regionTierAndParentIsBnIn_returnBnLocaleFamily() { + LocaleList testSupportedLocales = + LocaleList.forLanguageTags( + "en-US,zh-Hant-TW,bn-IN-u-nu-arab,ks-Arab-IN,en-ZA,bn-IN,bn-IN-u-nu-adlm"); + Set<String> ignorableLocales = new HashSet<>(); + ignorableLocales.add("en-US"); + LocaleInfo parent = LocaleStore.fromLocale(Locale.forLanguageTag("bn-IN")); + + Set<LocaleInfo> localeInfos = LocaleStore.getLevelLocales( + null, ignorableLocales, parent, false, testSupportedLocales); + + assertEquals(3, localeInfos.size()); + assertTrue(localeInfos.stream().anyMatch( + info -> info.getLocale().toLanguageTag().equals("bn-IN-u-nu-adlm"))); + assertTrue(localeInfos.stream().anyMatch( + info -> info.getLocale().toLanguageTag().equals("bn-IN-u-nu-arab"))); + assertTrue(localeInfos.stream().anyMatch( + info -> info.getLocale().toLanguageTag().equals("bn-IN"))); + } + + private ArrayList<LocaleInfo> getFakeSupportedLocales() { + String[] locales = {"en-US", "zh-Hant-TW", "ja-JP", "en-GB", "en-US-u-nu-arab"}; + ArrayList<LocaleInfo> supportedLocales = new ArrayList<>(); + for (String localeTag : locales) { + supportedLocales.add(LocaleStore.fromLocale(Locale.forLanguageTag(localeTag))); + } + return supportedLocales; + } +} diff --git a/tests/Internal/src/com/android/internal/app/OWNERS b/tests/Internal/src/com/android/internal/app/OWNERS new file mode 100644 index 000000000000..d55dc78b8c0a --- /dev/null +++ b/tests/Internal/src/com/android/internal/app/OWNERS @@ -0,0 +1,2 @@ +# Locale related test +per-file *Locale* = file:/services/core/java/com/android/server/locales/OWNERS diff --git a/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java b/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java new file mode 100644 index 000000000000..7419ee1230d3 --- /dev/null +++ b/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java @@ -0,0 +1,145 @@ +/* + * 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.android.internal.os; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.content.ComponentName; +import android.content.Intent; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link TimeoutRecord}. */ +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class TimeoutRecordTest { + + @Test + public void forBroadcastReceiver_returnsCorrectTimeoutRecord() { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setComponent(new ComponentName("com.example.app", "com.example.app.ExampleClass")); + + TimeoutRecord record = TimeoutRecord.forBroadcastReceiver(intent); + + assertNotNull(record); + assertEquals(record.mKind, TimeoutRecord.TimeoutKind.BROADCAST_RECEIVER); + assertEquals(record.mReason, + "Broadcast of Intent { act=android.intent.action.MAIN cmp=com.example" + + ".app/.ExampleClass }"); + assertTrue(record.mEndTakenBeforeLocks); + } + + @Test + public void forBroadcastReceiver_withPackageAndClass_returnsCorrectTimeoutRecord() { + Intent intent = new Intent(Intent.ACTION_MAIN); + TimeoutRecord record = TimeoutRecord.forBroadcastReceiver(intent, + "com.example.app", "com.example.app.ExampleClass"); + + assertNotNull(record); + assertEquals(record.mKind, TimeoutRecord.TimeoutKind.BROADCAST_RECEIVER); + assertEquals(record.mReason, + "Broadcast of Intent { act=android.intent.action.MAIN cmp=com.example" + + ".app/.ExampleClass }"); + assertTrue(record.mEndTakenBeforeLocks); + } + + @Test + public void forBroadcastReceiver_withTimeoutDurationMs_returnsCorrectTimeoutRecord() { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setComponent(new ComponentName("com.example.app", "com.example.app.ExampleClass")); + + TimeoutRecord record = TimeoutRecord.forBroadcastReceiver(intent, 1000L); + + assertNotNull(record); + assertEquals(record.mKind, TimeoutRecord.TimeoutKind.BROADCAST_RECEIVER); + assertEquals(record.mReason, + "Broadcast of Intent { act=android.intent.action.MAIN cmp=com.example" + + ".app/.ExampleClass }, waited 1000ms"); + assertTrue(record.mEndTakenBeforeLocks); + } + + @Test + public void forInputDispatchNoFocusedWindow_returnsCorrectTimeoutRecord() { + TimeoutRecord record = TimeoutRecord.forInputDispatchNoFocusedWindow("Test ANR reason"); + + assertNotNull(record); + assertEquals(record.mKind, TimeoutRecord.TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW); + assertEquals(record.mReason, + "Test ANR reason"); + assertTrue(record.mEndTakenBeforeLocks); + } + + @Test + public void forInputDispatchWindowUnresponsive_returnsCorrectTimeoutRecord() { + TimeoutRecord record = TimeoutRecord.forInputDispatchWindowUnresponsive("Test ANR reason"); + + assertNotNull(record); + assertEquals(record.mKind, TimeoutRecord.TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE); + assertEquals(record.mReason, "Test ANR reason"); + assertTrue(record.mEndTakenBeforeLocks); + } + + @Test + public void forServiceExec_returnsCorrectTimeoutRecord() { + TimeoutRecord record = TimeoutRecord.forServiceExec("Test ANR reason"); + + assertNotNull(record); + assertEquals(record.mKind, TimeoutRecord.TimeoutKind.SERVICE_EXEC); + assertEquals(record.mReason, "Test ANR reason"); + assertTrue(record.mEndTakenBeforeLocks); + } + + @Test + public void forServiceStartWithEndTime_returnsCorrectTimeoutRecord() { + TimeoutRecord record = TimeoutRecord.forServiceStartWithEndTime("Test ANR reason", 1000L); + + assertNotNull(record); + assertEquals(record.mKind, TimeoutRecord.TimeoutKind.SERVICE_START); + assertEquals(record.mReason, "Test ANR reason"); + assertEquals(record.mEndUptimeMillis, 1000L); + assertTrue(record.mEndTakenBeforeLocks); + } + + @Test + public void forContentProvider_returnsCorrectTimeoutRecord() { + TimeoutRecord record = TimeoutRecord.forContentProvider("Test ANR reason"); + + assertNotNull(record); + assertEquals(record.mKind, TimeoutRecord.TimeoutKind.CONTENT_PROVIDER); + assertEquals(record.mReason, "Test ANR reason"); + assertFalse(record.mEndTakenBeforeLocks); + } + + @Test + public void forApp_returnsCorrectTimeoutRecord() { + TimeoutRecord record = TimeoutRecord.forApp("Test ANR reason"); + + assertNotNull(record); + assertEquals(record.mKind, TimeoutRecord.TimeoutKind.APP_REGISTERED); + assertEquals(record.mReason, "Test ANR reason"); + assertFalse(record.mEndTakenBeforeLocks); + } +} diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java index 3db011683a86..7deb8c73d1fc 100644 --- a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java +++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java @@ -86,7 +86,7 @@ public class ProtoLogImplTest { mFile = testContext.getFileStreamPath("tracing_test.dat"); //noinspection ResultOfMethodCallIgnored mFile.delete(); - mProtoLog = new ProtoLogImpl(mFile, 1024 * 1024, mReader); + mProtoLog = new ProtoLogImpl(mFile, 1024 * 1024, mReader, 1024); } @After @@ -201,24 +201,24 @@ public class ProtoLogImplTest { @Test public void log_logcatEnabledExternalMessage() { - when(mReader.getViewerString(anyInt())).thenReturn("test %b %d %% %o %x %e %g %s %f"); + when(mReader.getViewerString(anyInt())).thenReturn("test %b %d %% 0x%x %s %f"); ProtoLogImpl implSpy = Mockito.spy(mProtoLog); TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true); TestProtoLogGroup.TEST_GROUP.setLogToProto(false); implSpy.log( ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null, - new Object[]{true, 10000, 20000, 30000, 0.0001, 0.00002, "test", 0.000003}); + new Object[]{true, 10000, 30000, "test", 0.000003}); verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq( ProtoLogImpl.LogLevel.INFO), - eq("test true 10000 % 47040 7530 1.000000e-04 2.00000e-05 test 0.000003")); + eq("test true 10000 % 0x7530 test 3.0E-6")); verify(mReader).getViewerString(eq(1234)); } @Test public void log_logcatEnabledInvalidMessage() { - when(mReader.getViewerString(anyInt())).thenReturn("test %b %d %% %o %x %e %g %s %f"); + when(mReader.getViewerString(anyInt())).thenReturn("test %b %d %% %x %s %f"); ProtoLogImpl implSpy = Mockito.spy(mProtoLog); TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true); TestProtoLogGroup.TEST_GROUP.setLogToProto(false); diff --git a/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java b/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java index e20ca3df57c7..9c2f74eabe02 100644 --- a/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java +++ b/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java @@ -36,15 +36,12 @@ import java.util.List; public class LogDataTypeTest { @Test public void parseFormatString() { - String str = "%b %d %o %x %f %e %g %s %%"; + String str = "%b %d %x %f %s %%"; List<Integer> out = LogDataType.parseFormatString(str); assertEquals(Arrays.asList( LogDataType.BOOLEAN, LogDataType.LONG, LogDataType.LONG, - LogDataType.LONG, - LogDataType.DOUBLE, - LogDataType.DOUBLE, LogDataType.DOUBLE, LogDataType.STRING ), out); diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java index 4de51fb57308..43dc9de6c90a 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/HomeActivity.java @@ -140,9 +140,9 @@ public class HomeActivity extends AppCompatActivity implements Button.OnClickLis handleNextBenchmark(); } + @SuppressWarnings("MissingSuperCall") // TODO: Fix me @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - } private void handleNextBenchmark() { diff --git a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java index c16efbda1830..d015a5695ec0 100644 --- a/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java +++ b/tests/JankBench/app/src/main/java/com/android/benchmark/app/RunLocalBenchmarksActivity.java @@ -367,6 +367,7 @@ public class RunLocalBenchmarksActivity extends AppCompatActivity { } } + @SuppressWarnings("MissingSuperCall") // TODO: Fix me @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { diff --git a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java index dd9b294a9596..00fc4982a213 100644 --- a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java +++ b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java @@ -15,7 +15,6 @@ */ package com.android.frameworks.perftests.job; - import android.app.job.JobInfo; import android.content.ComponentName; import android.content.Context; @@ -46,7 +45,8 @@ import java.util.List; public class JobStorePerfTests { private static final String SOURCE_PACKAGE = "com.android.frameworks.perftests.job"; private static final int SOURCE_USER_ID = 0; - private static final int CALLING_UID = 10079; + private static final int BASE_CALLING_UID = 10079; + private static final int MAX_UID_COUNT = 10; private static Context sContext; private static File sTestDir; @@ -65,10 +65,10 @@ public class JobStorePerfTests { sJobStore = JobStore.initAndGetForTesting(sContext, sTestDir); for (int i = 0; i < 50; i++) { - sFewJobs.add(createJobStatus("fewJobs", i)); + sFewJobs.add(createJobStatus("fewJobs", i, BASE_CALLING_UID + (i % MAX_UID_COUNT))); } for (int i = 0; i < 500; i++) { - sManyJobs.add(createJobStatus("manyJobs", i)); + sManyJobs.add(createJobStatus("manyJobs", i, BASE_CALLING_UID + (i % MAX_UID_COUNT))); } } @@ -104,6 +104,64 @@ public class JobStorePerfTests { runPersistedJobWriting(sManyJobs); } + private void runPersistedJobWriting_delta(List<JobStatus> jobList, + List<JobStatus> jobAdditions, List<JobStatus> jobRemovals) { + final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState(); + + long elapsedTimeNs = 0; + while (benchmarkState.keepRunning(elapsedTimeNs)) { + sJobStore.clearForTesting(); + for (JobStatus job : jobList) { + sJobStore.addForTesting(job); + } + sJobStore.writeStatusToDiskForTesting(); + + for (JobStatus job : jobAdditions) { + sJobStore.addForTesting(job); + } + for (JobStatus job : jobRemovals) { + sJobStore.removeForTesting(job); + } + + final long startTime = SystemClock.elapsedRealtimeNanos(); + sJobStore.writeStatusToDiskForTesting(); + final long endTime = SystemClock.elapsedRealtimeNanos(); + elapsedTimeNs = endTime - startTime; + } + } + + @Test + public void testPersistedJobWriting_delta_fewJobs() { + List<JobStatus> additions = new ArrayList<>(); + List<JobStatus> removals = new ArrayList<>(); + final int numModifiedUids = MAX_UID_COUNT / 2; + for (int i = 0; i < sFewJobs.size() / 3; ++i) { + JobStatus job = createJobStatus("fewJobs", i, BASE_CALLING_UID + (i % numModifiedUids)); + if (i % 2 == 0) { + additions.add(job); + } else { + removals.add(job); + } + } + runPersistedJobWriting_delta(sFewJobs, additions, removals); + } + + @Test + public void testPersistedJobWriting_delta_manyJobs() { + List<JobStatus> additions = new ArrayList<>(); + List<JobStatus> removals = new ArrayList<>(); + final int numModifiedUids = MAX_UID_COUNT / 2; + for (int i = 0; i < sManyJobs.size() / 3; ++i) { + JobStatus job = createJobStatus("fewJobs", i, BASE_CALLING_UID + (i % numModifiedUids)); + if (i % 2 == 0) { + additions.add(job); + } else { + removals.add(job); + } + } + runPersistedJobWriting_delta(sManyJobs, additions, removals); + } + private void runPersistedJobReading(List<JobStatus> jobList, boolean rtcIsGood) { final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState(); @@ -144,12 +202,12 @@ public class JobStorePerfTests { runPersistedJobReading(sManyJobs, false); } - private static JobStatus createJobStatus(String testTag, int jobId) { + private static JobStatus createJobStatus(String testTag, int jobId, int callingUid) { JobInfo jobInfo = new JobInfo.Builder(jobId, new ComponentName(sContext, "JobStorePerfTestJobService")) .setPersisted(true) .build(); return JobStatus.createFromJobInfo( - jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag); + jobInfo, callingUid, SOURCE_PACKAGE, SOURCE_USER_ID, null, testTag); } } diff --git a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java index c7e5a5ea3311..e59071bd1d88 100644 --- a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java +++ b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java @@ -29,6 +29,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.Debug.MemoryInfo; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; import android.test.InstrumentationTestCase; @@ -40,6 +41,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; /** * This test is intended to measure the amount of memory applications use when @@ -313,17 +315,19 @@ public class MemoryUsageTest extends InstrumentationTestCase { public void run() { try { - String mimeType = mLaunchIntent.getType(); - if (mimeType == null && mLaunchIntent.getData() != null + AtomicReference<String> mimeType = new AtomicReference<>(mLaunchIntent.getType()); + if (mimeType.get() == null && mLaunchIntent.getData() != null && "content".equals(mLaunchIntent.getData().getScheme())) { - mimeType = mAm.getProviderMimeType(mLaunchIntent.getData(), - UserHandle.USER_CURRENT); + mAm.getMimeTypeFilterAsync(mLaunchIntent.getData(), UserHandle.USER_CURRENT, + new RemoteCallback(result -> { + mimeType.set(result.getPairValue()); + })); } mAtm.startActivityAndWait(null, getInstrumentation().getContext().getBasePackageName(), getInstrumentation().getContext().getAttributionTag(), mLaunchIntent, - mimeType, null, null, 0, mLaunchIntent.getFlags(), null, null, + mimeType.get(), null, null, 0, mLaunchIntent.getFlags(), null, null, UserHandle.USER_CURRENT_OR_SELF); } catch (RemoteException e) { Log.w(TAG, "Error launching app", e); diff --git a/tests/MidiTests/Android.bp b/tests/MidiTests/Android.bp new file mode 100644 index 000000000000..254770d21818 --- /dev/null +++ b/tests/MidiTests/Android.bp @@ -0,0 +1,40 @@ +// +// Copyright (C) 2023 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "MidiTests", + srcs: ["**/*.java"], + static_libs: [ + "androidx.test.rules", + "mockito-target-inline-minus-junit4", + "platform-test-annotations", + "services.midi", + "truth-prebuilt", + ], + jni_libs: ["libdexmakerjvmtiagent"], + certificate: "platform", + platform_apis: true, + test_suites: ["device-tests"], +} diff --git a/tests/MidiTests/AndroidManifest.xml b/tests/MidiTests/AndroidManifest.xml new file mode 100644 index 000000000000..0ee1b4493764 --- /dev/null +++ b/tests/MidiTests/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.midi" > + + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> + <uses-permission android:name="android.permission.MANAGE_USERS" /> + + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.midi" + android:label="MidiTests"/> +</manifest> diff --git a/tests/MidiTests/AndroidTest.xml b/tests/MidiTests/AndroidTest.xml new file mode 100644 index 000000000000..9320f0aac090 --- /dev/null +++ b/tests/MidiTests/AndroidTest.xml @@ -0,0 +1,30 @@ +<!-- Copyright (C) 2023 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. +--> +<configuration description="Runs sample instrumentation test."> + <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="MidiTests.apk"/> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/> + <option name="test-suite-tag" value="apct"/> + <option name="test-tag" value="MidiTests"/> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.server.midi"/> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/tests/MidiTests/OWNERS b/tests/MidiTests/OWNERS new file mode 100644 index 000000000000..af273a6f50e0 --- /dev/null +++ b/tests/MidiTests/OWNERS @@ -0,0 +1 @@ +include /services/midi/OWNERS diff --git a/tests/MidiTests/TEST_MAPPING b/tests/MidiTests/TEST_MAPPING new file mode 100644 index 000000000000..60416a8ab3f9 --- /dev/null +++ b/tests/MidiTests/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "MidiTests" + } + ] +} diff --git a/tests/MidiTests/src/com/android/server/midi/MidiEventMultiSchedulerTest.java b/tests/MidiTests/src/com/android/server/midi/MidiEventMultiSchedulerTest.java new file mode 100644 index 000000000000..1659cc07f021 --- /dev/null +++ b/tests/MidiTests/src/com/android/server/midi/MidiEventMultiSchedulerTest.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2023 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.android.server.midi; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.midi.MidiEventMultiScheduler; +import com.android.internal.midi.MidiEventScheduler; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Random; + +/** + * Unit tests for com.android.internal.midi.MidiEventMultiScheduler. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class MidiEventMultiSchedulerTest { + private byte[] generateRandomByteStream(Random rnd, int size) { + byte[] output = new byte[size]; + rnd.nextBytes(output); + return output; + } + + private void compareByteArrays(byte[] expectedArray, byte[] outputArray) { + assertEquals(expectedArray.length, outputArray.length); + for (int i = 0; i < outputArray.length; i++) { + assertEquals(expectedArray[i], outputArray[i]); + } + } + + private long timeFromNow(long milliseconds) { + return System.nanoTime() + 1000000L * milliseconds; + } + + @Test + public void testMultiScheduler() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3); + assertEquals(3, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + MidiEventScheduler scheduler1 = multiScheduler.getEventScheduler(1); + MidiEventScheduler scheduler2 = multiScheduler.getEventScheduler(2); + + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf0, (byte) 0xf7}, + 0, 2, timeFromNow(100))); + scheduler1.add(scheduler1.createScheduledEvent(new byte[]{(byte) 0xf1, (byte) 0xf2}, + 0, 2, timeFromNow(200))); + scheduler2.add(scheduler2.createScheduledEvent(new byte[]{(byte) 0xf3, (byte) 0xf4}, + 0, 2, timeFromNow(300))); + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf5, (byte) 0xf6}, + 0, 2, timeFromNow(400))); + assertTrue(multiScheduler.waitNextEvent()); + assertNotNull(scheduler0.getNextEvent(System.nanoTime())); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + assertNull(scheduler0.getNextEvent(System.nanoTime())); + assertNotNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + assertNull(scheduler0.getNextEvent(System.nanoTime())); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNotNull(scheduler2.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + assertNotNull(scheduler0.getNextEvent(System.nanoTime())); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testSchedulerLargeData() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1); + assertEquals(1, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + + Random rnd = new Random(42); + + final int arraySize = 1000; + byte[] expectedArray = generateRandomByteStream(rnd, arraySize); + + scheduler0.add(scheduler0.createScheduledEvent(expectedArray, 0, arraySize, + timeFromNow(100))); + assertTrue(multiScheduler.waitNextEvent()); + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + compareByteArrays(expectedArray, event.data); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testSchedulerClose() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1); + assertEquals(1, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + scheduler0.close(); + // After all schedulers are closed, waitNextEvent() should return false. + assertFalse(multiScheduler.waitNextEvent()); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testSchedulerMultiClose() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3); + assertEquals(3, multiScheduler.getNumEventSchedulers()); + multiScheduler.close(); + // After all schedulers are closed, waitNextEvent() should return false. + assertFalse(multiScheduler.waitNextEvent()); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testSchedulerNoPreemptiveClose() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3); + assertEquals(3, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + MidiEventScheduler scheduler1 = multiScheduler.getEventScheduler(1); + MidiEventScheduler scheduler2 = multiScheduler.getEventScheduler(2); + scheduler0.close(); + scheduler1.close(); + scheduler2.add(scheduler2.createScheduledEvent(new byte[]{(byte) 0xf5, (byte) 0xf6}, + 0, 2, timeFromNow(100))); + assertTrue(multiScheduler.waitNextEvent()); + scheduler2.close(); + // After all schedulers are closed, waitNextEvent() should return false. + assertFalse(multiScheduler.waitNextEvent()); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testSchedulerSpamEvents() { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1); + assertEquals(1, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + // Create a msg with size 1 + byte[] msg = new byte[1]; + for (int i = 0; i < 1000; i++) { + msg[0] = (byte) i; + scheduler0.add(scheduler0.createScheduledEvent(msg, 0, 1, timeFromNow(0))); + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals(msg[0], event.data[0]); + } + assertNull(scheduler0.getNextEvent(System.nanoTime())); + } + + @Test + public void testSchedulerSpamEventsPullLater() { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1); + assertEquals(1, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + // Create a msg with size 1 + byte[] msg = new byte[1]; + for (int i = 0; i < 1000; i++) { + msg[0] = (byte) i; + scheduler0.add(scheduler0.createScheduledEvent(msg, 0, 1, timeFromNow(0))); + } + + for (int i = 0; i < 1000; i++) { + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) i, event.data[0]); + } + assertNull(scheduler0.getNextEvent(System.nanoTime())); + } + + @Test + public void testSchedulerSpamEventsCallbackLater() { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(1); + assertEquals(1, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + // Create a msg with size 1 + byte[] msg = new byte[1]; + for (int i = 0; i < 1000; i++) { + msg[0] = (byte) i; + scheduler0.add(scheduler0.createScheduledEvent(msg, 0, 1, timeFromNow(0))); + } + + for (int i = 0; i < 1000; i++) { + try { + assertTrue(multiScheduler.waitNextEvent()); + } catch (InterruptedException ex) { + } + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) i, event.data[0]); + } + assertNull(scheduler0.getNextEvent(System.nanoTime())); + } + + @Test + public void testMultiSchedulerOutOfOrder() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3); + assertEquals(3, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + MidiEventScheduler scheduler1 = multiScheduler.getEventScheduler(1); + MidiEventScheduler scheduler2 = multiScheduler.getEventScheduler(2); + + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf3}, + 0, 1, + timeFromNow(400))); + scheduler2.add(scheduler2.createScheduledEvent(new byte[]{(byte) 0xf2}, + 0, 1, + timeFromNow(300))); + scheduler1.add(scheduler1.createScheduledEvent(new byte[]{(byte) 0xf1}, + 0, 1, + timeFromNow(200))); + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf0}, + 0, 1, + timeFromNow(100))); + + assertTrue(multiScheduler.waitNextEvent()); + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf0, event.data[0]); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + assertNull(scheduler0.getNextEvent(System.nanoTime())); + event = (MidiEventScheduler.MidiEvent) scheduler1.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf1, event.data[0]); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + assertNull(scheduler0.getNextEvent(System.nanoTime())); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + event = (MidiEventScheduler.MidiEvent) scheduler2.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf2, event.data[0]); + assertTrue(multiScheduler.waitNextEvent()); + event = (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf3, event.data[0]); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + } catch (InterruptedException ex) { + + } + } + + @Test + public void testMultiSchedulerOutOfOrderNegativeTime() { + try { + MidiEventMultiScheduler multiScheduler = new MidiEventMultiScheduler(3); + assertEquals(3, multiScheduler.getNumEventSchedulers()); + MidiEventScheduler scheduler0 = multiScheduler.getEventScheduler(0); + MidiEventScheduler scheduler1 = multiScheduler.getEventScheduler(1); + MidiEventScheduler scheduler2 = multiScheduler.getEventScheduler(2); + + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf3}, + 0, 1, + timeFromNow(-100))); + scheduler2.add(scheduler2.createScheduledEvent(new byte[]{(byte) 0xf2}, + 0, 1, + timeFromNow(-200))); + scheduler1.add(scheduler1.createScheduledEvent(new byte[]{(byte) 0xf1}, + 0, 1, + timeFromNow(-300))); + scheduler0.add(scheduler0.createScheduledEvent(new byte[]{(byte) 0xf0}, + 0, 1, + timeFromNow(-400))); + + assertTrue(multiScheduler.waitNextEvent()); + MidiEventScheduler.MidiEvent event = + (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf0, event.data[0]); + assertTrue(multiScheduler.waitNextEvent()); + event = (MidiEventScheduler.MidiEvent) scheduler1.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf1, event.data[0]); + assertTrue(multiScheduler.waitNextEvent()); + event = (MidiEventScheduler.MidiEvent) scheduler2.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf2, event.data[0]); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertTrue(multiScheduler.waitNextEvent()); + event = (MidiEventScheduler.MidiEvent) scheduler0.getNextEvent(System.nanoTime()); + assertNotNull(event); + assertEquals(1, event.count); + assertEquals((byte) 0xf3, event.data[0]); + assertNull(scheduler1.getNextEvent(System.nanoTime())); + assertNull(scheduler2.getNextEvent(System.nanoTime())); + } catch (InterruptedException ex) { + + } + } +} diff --git a/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java b/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java index 8afe8411a790..17fa210a1db6 100644 --- a/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java +++ b/tests/MirrorSurfaceTest/src/com/google/android/test/mirrorsurface/MirrorSurfaceActivity.java @@ -295,8 +295,8 @@ public class MirrorSurfaceActivity extends Activity implements View.OnClickListe private void updateMirror(Rect displayFrame, float scale) { if (displayFrame.isEmpty()) { Rect bounds = mWindowBounds; - int defaultCropW = Math.round(bounds.width() / 2); - int defaultCropH = Math.round(bounds.height() / 2); + int defaultCropW = bounds.width() / 2; + int defaultCropH = bounds.height() / 2; displayFrame.set(0, 0, defaultCropW, defaultCropH); } diff --git a/tests/MotionPrediction/Android.bp b/tests/MotionPrediction/Android.bp new file mode 100644 index 000000000000..6cda8f050987 --- /dev/null +++ b/tests/MotionPrediction/Android.bp @@ -0,0 +1,30 @@ +// +// Copyright (C) 2023 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_app { + name: "MotionPrediction", + srcs: ["**/*.kt"], + sdk_version: "current", +} diff --git a/tests/MotionPrediction/AndroidManifest.xml b/tests/MotionPrediction/AndroidManifest.xml new file mode 100644 index 000000000000..3f8c2f278623 --- /dev/null +++ b/tests/MotionPrediction/AndroidManifest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="test.motionprediction"> + + <application android:allowBackup="false" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:supportsRtl="true" + android:theme="@style/AppTheme"> + <activity android:name=".MainActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/tests/MotionPrediction/OWNERS b/tests/MotionPrediction/OWNERS new file mode 100644 index 000000000000..c88bfe97cab9 --- /dev/null +++ b/tests/MotionPrediction/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/base:/INPUT_OWNERS diff --git a/tests/MotionPrediction/res/layout/activity_main.xml b/tests/MotionPrediction/res/layout/activity_main.xml new file mode 100644 index 000000000000..65dc325befdb --- /dev/null +++ b/tests/MotionPrediction/res/layout/activity_main.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingBottom="@dimen/activity_vertical_margin" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + tools:context="test.motionprediction.MainActivity"> + + <test.motionprediction.DrawingView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/output" /> + +</LinearLayout> diff --git a/tests/MotionPrediction/res/mipmap-hdpi/ic_launcher.png b/tests/MotionPrediction/res/mipmap-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..cde69bcccec6 --- /dev/null +++ b/tests/MotionPrediction/res/mipmap-hdpi/ic_launcher.png diff --git a/tests/MotionPrediction/res/mipmap-mdpi/ic_launcher.png b/tests/MotionPrediction/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..c133a0cbd379 --- /dev/null +++ b/tests/MotionPrediction/res/mipmap-mdpi/ic_launcher.png diff --git a/tests/MotionPrediction/res/mipmap-xhdpi/ic_launcher.png b/tests/MotionPrediction/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..bfa42f0e7b91 --- /dev/null +++ b/tests/MotionPrediction/res/mipmap-xhdpi/ic_launcher.png diff --git a/tests/MotionPrediction/res/mipmap-xxhdpi/ic_launcher.png b/tests/MotionPrediction/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..324e72cdd748 --- /dev/null +++ b/tests/MotionPrediction/res/mipmap-xxhdpi/ic_launcher.png diff --git a/tests/MotionPrediction/res/mipmap-xxxhdpi/ic_launcher.png b/tests/MotionPrediction/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..aee44e138434 --- /dev/null +++ b/tests/MotionPrediction/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/tests/MotionPrediction/res/values-w820dp/dimens.xml b/tests/MotionPrediction/res/values-w820dp/dimens.xml new file mode 100644 index 000000000000..95669e6aa6fa --- /dev/null +++ b/tests/MotionPrediction/res/values-w820dp/dimens.xml @@ -0,0 +1,20 @@ +<!-- Copyright (C) 2023 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. +--> +<resources> + <!-- Example customization of dimensions originally defined in res/values/dimens.xml + (such as screen margins) for screens with more than 820dp of available width. This + would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). --> + <dimen name="activity_horizontal_margin">64dp</dimen> +</resources> diff --git a/tests/MotionPrediction/res/values/colors.xml b/tests/MotionPrediction/res/values/colors.xml new file mode 100644 index 000000000000..139eb1d1303b --- /dev/null +++ b/tests/MotionPrediction/res/values/colors.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 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. +--> +<resources> + <color name="colorPrimary">#3F51B5</color> + <color name="colorPrimaryDark">#303F9F</color> + <color name="colorAccent">#FF4081</color> +</resources> diff --git a/tests/MotionPrediction/res/values/dimens.xml b/tests/MotionPrediction/res/values/dimens.xml new file mode 100644 index 000000000000..d26136f18c29 --- /dev/null +++ b/tests/MotionPrediction/res/values/dimens.xml @@ -0,0 +1,19 @@ +<!-- Copyright (C) 2023 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. +--> +<resources> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> +</resources> diff --git a/tests/MotionPrediction/res/values/strings.xml b/tests/MotionPrediction/res/values/strings.xml new file mode 100644 index 000000000000..16a2bdf34d13 --- /dev/null +++ b/tests/MotionPrediction/res/values/strings.xml @@ -0,0 +1,17 @@ +<!-- Copyright (C) 2023 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. +--> +<resources> + <string name="app_name">Motion Prediction</string> +</resources> diff --git a/tests/MotionPrediction/res/values/styles.xml b/tests/MotionPrediction/res/values/styles.xml new file mode 100644 index 000000000000..cfb5e3d83c0d --- /dev/null +++ b/tests/MotionPrediction/res/values/styles.xml @@ -0,0 +1,23 @@ +<!-- Copyright (C) 2023 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. +--> +<resources> + <!-- Base application theme. --> + <style name="AppTheme" parent="@android:style/Theme.Material.Light.DarkActionBar"> + <!-- Customize your theme here. --> + <item name="android:colorPrimary">@color/colorPrimary</item> + <item name="android:colorPrimaryDark">@color/colorPrimaryDark</item> + <item name="android:colorAccent">@color/colorAccent</item> + </style> +</resources> diff --git a/tests/MotionPrediction/src/test/motionprediction/DrawingView.kt b/tests/MotionPrediction/src/test/motionprediction/DrawingView.kt new file mode 100644 index 000000000000..229d0c8da6e9 --- /dev/null +++ b/tests/MotionPrediction/src/test/motionprediction/DrawingView.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 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 test.motionprediction + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.MotionEvent.ACTION_DOWN +import android.view.MotionPredictor +import android.view.View + +import java.util.Vector + +private fun drawLine(canvas: Canvas, from: MotionEvent, to: MotionEvent, paint: Paint) { + canvas.apply { + val x0 = from.getX() + val y0 = from.getY() + val x1 = to.getX() + val y1 = to.getY() + // TODO: handle historical data + drawLine(x0, y0, x1, y1, paint) + } +} + +/** + * Draw the current stroke and predicted values + */ +class DrawingView(context: Context, attrs: AttributeSet) : View(context, attrs) { + private val TAG = "DrawingView" + + val events: MutableMap<Int, Vector<MotionEvent>> = mutableMapOf<Int, Vector<MotionEvent>>() + + var isPredictionAvailable = false + private val predictor = MotionPredictor(getContext()) + + private var predictionPaint = Paint() + private var realPaint = Paint() + + init { + setBackgroundColor(Color.WHITE) + predictionPaint.color = Color.BLACK + predictionPaint.setStrokeWidth(5f) + realPaint.color = Color.RED + realPaint.setStrokeWidth(5f) + } + + private fun addEvent(event: MotionEvent) { + if (event.getActionMasked() == ACTION_DOWN) { + events.remove(event.deviceId) + } + var vec = events.getOrPut(event.deviceId) { Vector<MotionEvent>() } + vec.add(MotionEvent.obtain(event)) + predictor.record(event) + invalidate() + } + + public override fun onTouchEvent(event: MotionEvent): Boolean { + isPredictionAvailable = predictor.isPredictionAvailable(event.getDeviceId(), + event.getSource()) + addEvent(event) + return true + } + + public override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + if (!isPredictionAvailable) { + canvas.apply { + drawRect(0f, 0f, 200f, 200f, realPaint) + } + } + + var eventTime = 0L + + // Draw real events + for ((_, vec) in events ) { + for (i in 1 until vec.size) { + drawLine(canvas, vec[i - 1], vec[i], realPaint) + } + eventTime = vec.lastElement().eventTime + } + + // Draw predictions. Convert to nanos and hardcode to +20ms into the future + val prediction = predictor.predict(eventTime * 1000000 + 20000000) + if (prediction != null) { + val realEvents = events.get(prediction.deviceId)!! + drawLine(canvas, realEvents[realEvents.size - 1], prediction, predictionPaint) + } + } +} diff --git a/tests/MotionPrediction/src/test/motionprediction/MainActivity.kt b/tests/MotionPrediction/src/test/motionprediction/MainActivity.kt new file mode 100644 index 000000000000..cec2c06157a1 --- /dev/null +++ b/tests/MotionPrediction/src/test/motionprediction/MainActivity.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 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 test.motionprediction + +import android.app.Activity +import android.os.Bundle + +class MainActivity : Activity() { + val TAG = "MotionPrediction" + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + } +} diff --git a/tests/OdmApps/Android.bp b/tests/OdmApps/Android.bp index de86498afd27..a5c6d6513f50 100644 --- a/tests/OdmApps/Android.bp +++ b/tests/OdmApps/Android.bp @@ -26,4 +26,8 @@ java_test_host { srcs: ["src/**/*.java"], libs: ["tradefed"], test_suites: ["device-tests"], + data: [ + ":TestOdmApp", + ":TestOdmPrivApp", + ], } diff --git a/tests/OneMedia/AndroidManifest.xml b/tests/OneMedia/AndroidManifest.xml index 7fc352405212..ddde6dbfefb0 100644 --- a/tests/OneMedia/AndroidManifest.xml +++ b/tests/OneMedia/AndroidManifest.xml @@ -7,6 +7,7 @@ <uses-sdk android:minSdkVersion="19"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> + <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> @@ -27,7 +28,8 @@ </activity> <service android:name="com.android.onemedia.OnePlayerService" android:exported="true" - android:process="com.android.onemedia.service"/> + android:process="com.android.onemedia.service" + android:foregroundServiceType="mediaPlayback"/> </application> </manifest> diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index 96bbf82cfba7..d7fa124623ce 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -44,14 +44,14 @@ import android.os.test.TestLooper; import android.provider.DeviceConfig; import android.util.AtomicFile; import android.util.LongArrayQueue; -import android.util.TypedXmlPullParser; -import android.util.TypedXmlSerializer; import android.util.Xml; import androidx.test.InstrumentationRegistry; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.util.XmlUtils; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; import com.android.server.PackageWatchdog.HealthCheckState; import com.android.server.PackageWatchdog.MonitoredPackage; import com.android.server.PackageWatchdog.PackageHealthObserver; @@ -417,9 +417,9 @@ public class PackageWatchdogTest { int failureReason, int mitigationCount) { if (versionedPackage.getVersionCode() == VERSION_CODE) { // Only rollback for specific versionCode - return PackageHealthObserverImpact.USER_IMPACT_MEDIUM; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; } - return PackageHealthObserverImpact.USER_IMPACT_NONE; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; } }; @@ -442,13 +442,13 @@ public class PackageWatchdogTest { public void testPackageFailureNotifyAllDifferentImpacts() throws Exception { PackageWatchdog watchdog = createWatchdog(); TestObserver observerNone = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_NONE); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_0); TestObserver observerHigh = new TestObserver(OBSERVER_NAME_2, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); TestObserver observerMid = new TestObserver(OBSERVER_NAME_3, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); TestObserver observerLow = new TestObserver(OBSERVER_NAME_4, - PackageHealthObserverImpact.USER_IMPACT_LOW); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); // Start observing for all impact observers watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D), @@ -499,9 +499,9 @@ public class PackageWatchdogTest { public void testPackageFailureNotifyLeastImpactSuccessively() throws Exception { PackageWatchdog watchdog = createWatchdog(); TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_LOW); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); TestObserver observerSecond = new TestObserver(OBSERVER_NAME_2, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); // Start observing for observerFirst and observerSecond with failure handling watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION); @@ -517,7 +517,7 @@ public class PackageWatchdogTest { assertThat(observerSecond.mMitigatedPackages).isEmpty(); // After observerFirst handles failure, next action it has is high impact - observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH; + observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_100; observerFirst.mMitigatedPackages.clear(); observerSecond.mMitigatedPackages.clear(); @@ -531,7 +531,7 @@ public class PackageWatchdogTest { assertThat(observerFirst.mMitigatedPackages).isEmpty(); // After observerSecond handles failure, it has no further actions - observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE; + observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; observerFirst.mMitigatedPackages.clear(); observerSecond.mMitigatedPackages.clear(); @@ -545,7 +545,7 @@ public class PackageWatchdogTest { assertThat(observerSecond.mMitigatedPackages).isEmpty(); // After observerFirst handles failure, it too has no further actions - observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE; + observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; observerFirst.mMitigatedPackages.clear(); observerSecond.mMitigatedPackages.clear(); @@ -566,9 +566,9 @@ public class PackageWatchdogTest { public void testPackageFailureNotifyOneSameImpact() throws Exception { PackageWatchdog watchdog = createWatchdog(); TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); // Start observing for observer1 and observer2 with failure handling watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); @@ -592,11 +592,11 @@ public class PackageWatchdogTest { TestController controller = new TestController(); PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); TestObserver observer3 = new TestObserver(OBSERVER_NAME_3, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); // Start observing with explicit health checks for APP_A and APP_B respectively @@ -645,7 +645,7 @@ public class PackageWatchdogTest { TestController controller = new TestController(); PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); TestObserver observer = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); // Start observing with explicit health checks for APP_A and APP_B controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C)); @@ -711,7 +711,7 @@ public class PackageWatchdogTest { TestController controller = new TestController(); PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); TestObserver observer = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); // Start observing with explicit health checks for APP_A and // package observation duration == LONG_DURATION @@ -742,7 +742,7 @@ public class PackageWatchdogTest { TestController controller = new TestController(); PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); TestObserver observer = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); // Start observing with explicit health checks for APP_A and // package observation duration == SHORT_DURATION / 2 @@ -818,7 +818,7 @@ public class PackageWatchdogTest { // Start observing with failure handling TestObserver observer = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION); // Notify of NetworkStack failure @@ -1073,9 +1073,9 @@ public class PackageWatchdogTest { public void testBootLoopMitigationDoneForLowestUserImpact() { PackageWatchdog watchdog = createWatchdog(); TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1); - bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LOW); + bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2); - bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); watchdog.registerHealthObserver(bootObserver1); watchdog.registerHealthObserver(bootObserver2); for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { @@ -1446,7 +1446,7 @@ public class PackageWatchdogTest { TestObserver(String name) { mName = name; - mImpact = PackageHealthObserverImpact.USER_IMPACT_MEDIUM; + mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; } TestObserver(String name, int impact) { diff --git a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java index 241206d8919b..65b7549f22d1 100644 --- a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java +++ b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java @@ -24,18 +24,14 @@ public class MainActivity extends Activity implements OnItemClickListener { static final String KEY_NAME = "name"; static final String KEY_CLASS = "clazz"; - static Map<String,?> make(String name) { - Map<String,Object> ret = new HashMap<String,Object>(); - ret.put(KEY_NAME, name); - return ret; - } - - @SuppressWarnings("serial") - static final ArrayList<Map<String,?>> SAMPLES = new ArrayList<Map<String,?>>() {{ + static final ArrayList<Map<String, ?>> SAMPLES = new ArrayList<>(); + static { for (int i = 1; i < 25; i++) { - add(make("List Item: " + i)); + Map<String, Object> sample = new HashMap<String, Object>(); + sample.put(KEY_NAME, "List Item: " + i); + SAMPLES.add(sample); } - }}; + } Handler mHandler = new Handler(); diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp index 9f6ce4e8425b..21007ef1396f 100644 --- a/tests/RollbackTest/Android.bp +++ b/tests/RollbackTest/Android.bp @@ -51,6 +51,7 @@ java_test_host { data: [ ":com.android.apex.apkrollback.test_v1", ":test.rebootless_apex_v1", + ":RollbackTest", ], } @@ -61,6 +62,7 @@ java_test_host { static_libs: ["RollbackTestLib", "frameworks-base-hostutils"], test_suites: ["general-tests"], test_config: "NetworkStagedRollbackTest.xml", + data: [":RollbackTest"], } java_test_host { @@ -72,6 +74,7 @@ java_test_host { ], test_suites: ["general-tests"], test_config: "MultiUserRollbackTest.xml", + data : [":RollbackTest"], } java_library_host { diff --git a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java index 35859fe1472e..ec1709cc711d 100644 --- a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java +++ b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java @@ -18,12 +18,14 @@ package com.android.tests.rollback.host; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,6 +37,7 @@ import java.util.concurrent.TimeUnit; */ @RunWith(DeviceJUnit4ClassRunner.class) public class MultiUserRollbackTest extends BaseHostJUnit4Test { + private boolean mSupportMultiUsers; // The user that was running originally when the test starts. private int mOriginalUserId; private int mSecondaryUserId = -1; @@ -46,14 +49,20 @@ public class MultiUserRollbackTest extends BaseHostJUnit4Test { @After public void tearDown() throws Exception { - removeSecondaryUserIfNecessary(); - runPhaseForUsers("cleanUp", mOriginalUserId); - uninstallPackage("com.android.cts.install.lib.testapp.A"); - uninstallPackage("com.android.cts.install.lib.testapp.B"); + if (mSupportMultiUsers) { + removeSecondaryUserIfNecessary(); + runPhaseForUsers("cleanUp", mOriginalUserId); + uninstallPackage("com.android.cts.install.lib.testapp.A"); + uninstallPackage("com.android.cts.install.lib.testapp.B"); + } } @Before public void setup() throws Exception { + assumeTrue("Device does not support multiple users", + getDevice().isMultiUserSupported()); + + mSupportMultiUsers = true; mOriginalUserId = getDevice().getCurrentUser(); createAndStartSecondaryUser(); installPackage("RollbackTest.apk", "--user all"); @@ -90,6 +99,7 @@ public class MultiUserRollbackTest extends BaseHostJUnit4Test { } @Test + @Ignore public void testBadUpdateRollback() throws Exception { // Need to switch user in order to send broadcasts in device tests assertTrue(getDevice().switchUser(mSecondaryUserId)); diff --git a/tests/RollbackTest/SampleRollbackApp/Android.bp b/tests/RollbackTest/SampleRollbackApp/Android.bp new file mode 100644 index 000000000000..074c7bc39155 --- /dev/null +++ b/tests/RollbackTest/SampleRollbackApp/Android.bp @@ -0,0 +1,33 @@ +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_app { + name: "SampleRollbackApp", + srcs: [ + "src/**/*.java", + ], + resource_dirs: ["res"], + certificate: "platform", + sdk_version: "system_current", + min_sdk_version: "29", +} diff --git a/tests/RollbackTest/SampleRollbackApp/AndroidManifest.xml b/tests/RollbackTest/SampleRollbackApp/AndroidManifest.xml new file mode 100644 index 000000000000..7fe4bae2a3fe --- /dev/null +++ b/tests/RollbackTest/SampleRollbackApp/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.sample.rollbackapp" > + <uses-permission android:name="android.permission.MANAGE_ROLLBACKS" /> + <application + android:label="@string/title_activity_main"> + <activity + android:name="com.android.sample.rollbackapp.MainActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tests/RollbackTest/SampleRollbackApp/res/layout/activity_main.xml b/tests/RollbackTest/SampleRollbackApp/res/layout/activity_main.xml new file mode 100644 index 000000000000..3fb987bb539c --- /dev/null +++ b/tests/RollbackTest/SampleRollbackApp/res/layout/activity_main.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <Button + android:id="@+id/trigger_rollback_button" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + style="?android:attr/buttonBarButtonStyle" + android:text="Rollback Selected" /> + + <ListView + android:id="@+id/listView" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:divider="?android:attr/dividerHorizontal" + android:dividerHeight="1dp" /> + +</LinearLayout>
\ No newline at end of file diff --git a/tests/RollbackTest/SampleRollbackApp/res/layout/listitem_rollbackinfo.xml b/tests/RollbackTest/SampleRollbackApp/res/layout/listitem_rollbackinfo.xml new file mode 100644 index 000000000000..f650dd5d2230 --- /dev/null +++ b/tests/RollbackTest/SampleRollbackApp/res/layout/listitem_rollbackinfo.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" +> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingTop="10dp" + android:paddingLeft="10dp" + android:paddingRight="10dp"> + <TextView android:id="@+id/rollback_id" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="20dp"/> + <TextView android:id="@+id/rollback_packages" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="16dp"/> + <CheckBox android:id="@+id/checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Roll Back"/> + </LinearLayout> +</RelativeLayout>
\ No newline at end of file diff --git a/tests/RollbackTest/SampleRollbackApp/res/values/strings.xml b/tests/RollbackTest/SampleRollbackApp/res/values/strings.xml new file mode 100644 index 000000000000..a85b6800a146 --- /dev/null +++ b/tests/RollbackTest/SampleRollbackApp/res/values/strings.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<resources> + <string name="title_activity_main" description="Launcher title">Rollback Sample App</string> +</resources>
\ No newline at end of file diff --git a/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java b/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java new file mode 100644 index 000000000000..157d19762925 --- /dev/null +++ b/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java @@ -0,0 +1,161 @@ +/* + * 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.android.sample.rollbackapp; + +import static android.app.PendingIntent.FLAG_MUTABLE; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.rollback.PackageRollbackInfo; +import android.content.rollback.RollbackInfo; +import android.content.rollback.RollbackManager; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class MainActivity extends Activity { + + List<Integer> mIdsToRollback = new ArrayList<>(); + Button mTriggerRollbackButton; + RollbackManager mRollbackManager; + static final String ROLLBACK_ID_EXTRA = "rollbackId"; + static final String ACTION_NAME = MainActivity.class.getName(); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + ListView rollbackListView = findViewById(R.id.listView); + mRollbackManager = getApplicationContext().getSystemService(RollbackManager.class); + initTriggerRollbackButton(); + + // Populate list of available rollbacks. + List<RollbackInfo> availableRollbacks = mRollbackManager.getAvailableRollbacks(); + CustomAdapter adapter = new CustomAdapter(availableRollbacks); + rollbackListView.setAdapter(adapter); + + // Register receiver for rollback status events. + getApplicationContext().registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, + Intent intent) { + int rollbackId = intent.getIntExtra(ROLLBACK_ID_EXTRA, -1); + int rollbackStatusCode = intent.getIntExtra(RollbackManager.EXTRA_STATUS, + RollbackManager.STATUS_FAILURE); + String rollbackStatus = "FAILED"; + if (rollbackStatusCode == RollbackManager.STATUS_SUCCESS) { + rollbackStatus = "SUCCESS"; + mTriggerRollbackButton.setClickable(false); + } + makeToast("Status for rollback ID " + rollbackId + " is " + rollbackStatus); + }}, new IntentFilter(ACTION_NAME), Context.RECEIVER_NOT_EXPORTED); + } + + private void initTriggerRollbackButton() { + mTriggerRollbackButton = findViewById(R.id.trigger_rollback_button); + mTriggerRollbackButton.setClickable(false); + mTriggerRollbackButton.setOnClickListener(v -> { + // Commits all selected rollbacks. Rollback status events will be sent to our receiver. + for (int i = 0; i < mIdsToRollback.size(); i++) { + Intent intent = new Intent(ACTION_NAME); + intent.putExtra(ROLLBACK_ID_EXTRA, mIdsToRollback.get(i)); + intent.setPackage(getApplicationContext().getPackageName()); + PendingIntent pendingIntent = PendingIntent.getBroadcast( + getApplicationContext(), 0, intent, FLAG_MUTABLE); + mRollbackManager.commitRollback(mIdsToRollback.get(i), + Collections.emptyList(), + pendingIntent.getIntentSender()); + } + }); + } + + + + private void makeToast(String message) { + runOnUiThread(() -> Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show()); + } + + public class CustomAdapter extends BaseAdapter { + List<RollbackInfo> mRollbackInfos; + LayoutInflater mInflater = LayoutInflater.from(getApplicationContext()); + + CustomAdapter(List<RollbackInfo> rollbackInfos) { + mRollbackInfos = rollbackInfos; + } + + @Override + public int getCount() { + return mRollbackInfos.size(); + } + + @Override + public Object getItem(int position) { + return mRollbackInfos.get(position); + } + + @Override + public long getItemId(int position) { + return mRollbackInfos.get(position).getRollbackId(); + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + if (view == null) { + view = mInflater.inflate(R.layout.listitem_rollbackinfo, null); + } + RollbackInfo rollbackInfo = mRollbackInfos.get(position); + TextView rollbackIdView = view.findViewById(R.id.rollback_id); + rollbackIdView.setText("Rollback ID " + rollbackInfo.getRollbackId()); + TextView rollbackPackagesTextView = view.findViewById(R.id.rollback_packages); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < rollbackInfo.getPackages().size(); i++) { + PackageRollbackInfo pkgInfo = rollbackInfo.getPackages().get(i); + sb.append(pkgInfo.getPackageName() + ": " + + pkgInfo.getVersionRolledBackFrom().getLongVersionCode() + " -> " + + pkgInfo.getVersionRolledBackTo().getLongVersionCode() + ","); + } + sb.deleteCharAt(sb.length() - 1); + rollbackPackagesTextView.setText(sb.toString()); + CheckBox checkbox = view.findViewById(R.id.checkbox); + checkbox.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked) { + mIdsToRollback.add(rollbackInfo.getRollbackId()); + } else { + mIdsToRollback.remove(Integer.valueOf(rollbackInfo.getRollbackId())); + } + mTriggerRollbackButton.setClickable(mIdsToRollback.size() > 0); + }); + return view; + } + } +} diff --git a/tests/SharedLibraryLoadingTest/AndroidTest.xml b/tests/SharedLibraryLoadingTest/AndroidTest.xml index 947453d07bd9..ad0584724fdc 100644 --- a/tests/SharedLibraryLoadingTest/AndroidTest.xml +++ b/tests/SharedLibraryLoadingTest/AndroidTest.xml @@ -22,7 +22,6 @@ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> - <option name="cleanup" value="false" /> <option name="remount-system" value="true" /> <option name="push" value="SharedLibraryLoadingTests_StandardSharedLibrary.apk->/product/app/SharedLibraryLoadingTests_StandardSharedLibrary.apk" /> diff --git a/tests/SilkFX/Android.bp b/tests/SilkFX/Android.bp index 088d9a2d7f41..1e467db44545 100644 --- a/tests/SilkFX/Android.bp +++ b/tests/SilkFX/Android.bp @@ -25,13 +25,17 @@ package { android_test { name: "SilkFX", - srcs: ["**/*.java", "**/*.kt"], + srcs: [ + "**/*.java", + "**/*.kt", + ], platform_apis: true, certificate: "platform", - static_libs: [ + static_libs: [ "androidx.core_core", "androidx.appcompat_appcompat", "com.google.android.material_material", "androidx-constraintlayout_constraintlayout", + "subsampling-scale-image-view", ], } diff --git a/tests/SilkFX/AndroidManifest.xml b/tests/SilkFX/AndroidManifest.xml index 21256d8c9d0b..c293589bdbaf 100644 --- a/tests/SilkFX/AndroidManifest.xml +++ b/tests/SilkFX/AndroidManifest.xml @@ -55,5 +55,20 @@ android:exported="true"> </activity> + <activity android:name=".app.HdrImageViewer" + android:label="HDR Gainmap Image Viewer" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.VIEW"/> + <category android:name="android.intent.category.DEFAULT"/> + <data android:mimeType="image/*"/> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.SEND" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:mimeType="image/*" /> + </intent-filter> + </activity> + </application> </manifest> diff --git a/tests/SilkFX/assets/gainmaps/city_night.jpg b/tests/SilkFX/assets/gainmaps/city_night.jpg Binary files differnew file mode 100644 index 000000000000..ba26ed6a5780 --- /dev/null +++ b/tests/SilkFX/assets/gainmaps/city_night.jpg diff --git a/tests/SilkFX/assets/gainmaps/desert_palms.jpg b/tests/SilkFX/assets/gainmaps/desert_palms.jpg Binary files differnew file mode 100644 index 000000000000..048178670a96 --- /dev/null +++ b/tests/SilkFX/assets/gainmaps/desert_palms.jpg diff --git a/tests/SilkFX/assets/gainmaps/desert_sunset.jpg b/tests/SilkFX/assets/gainmaps/desert_sunset.jpg Binary files differnew file mode 100644 index 000000000000..919a1574a4b9 --- /dev/null +++ b/tests/SilkFX/assets/gainmaps/desert_sunset.jpg diff --git a/tests/SilkFX/assets/gainmaps/desert_wanda.jpg b/tests/SilkFX/assets/gainmaps/desert_wanda.jpg Binary files differnew file mode 100644 index 000000000000..f5a2ef9c53ea --- /dev/null +++ b/tests/SilkFX/assets/gainmaps/desert_wanda.jpg diff --git a/tests/SilkFX/assets/gainmaps/fountain_night.jpg b/tests/SilkFX/assets/gainmaps/fountain_night.jpg Binary files differnew file mode 100644 index 000000000000..d8b2d759e4c0 --- /dev/null +++ b/tests/SilkFX/assets/gainmaps/fountain_night.jpg diff --git a/tests/SilkFX/assets/gainmaps/grand_canyon.jpg b/tests/SilkFX/assets/gainmaps/grand_canyon.jpg Binary files differnew file mode 100644 index 000000000000..2f605bbb0a7e --- /dev/null +++ b/tests/SilkFX/assets/gainmaps/grand_canyon.jpg diff --git a/tests/SilkFX/assets/gainmaps/lamps.jpg b/tests/SilkFX/assets/gainmaps/lamps.jpg Binary files differnew file mode 100644 index 000000000000..768665f643cb --- /dev/null +++ b/tests/SilkFX/assets/gainmaps/lamps.jpg diff --git a/tests/SilkFX/assets/gainmaps/mountain_lake.jpg b/tests/SilkFX/assets/gainmaps/mountain_lake.jpg Binary files differnew file mode 100644 index 000000000000..b7981fdca6da --- /dev/null +++ b/tests/SilkFX/assets/gainmaps/mountain_lake.jpg diff --git a/tests/SilkFX/assets/gainmaps/mountains.jpg b/tests/SilkFX/assets/gainmaps/mountains.jpg Binary files differnew file mode 100644 index 000000000000..fe69993e0706 --- /dev/null +++ b/tests/SilkFX/assets/gainmaps/mountains.jpg diff --git a/tests/SilkFX/assets/gainmaps/sunflower.jpg b/tests/SilkFX/assets/gainmaps/sunflower.jpg Binary files differnew file mode 100644 index 000000000000..4b17614d66bf --- /dev/null +++ b/tests/SilkFX/assets/gainmaps/sunflower.jpg diff --git a/tests/SilkFX/assets/gainmaps/train_station_night.jpg b/tests/SilkFX/assets/gainmaps/train_station_night.jpg Binary files differnew file mode 100644 index 000000000000..ecd45ee1e629 --- /dev/null +++ b/tests/SilkFX/assets/gainmaps/train_station_night.jpg diff --git a/tests/SilkFX/res/drawable-nodpi/blue_sweep_gradient.xml b/tests/SilkFX/res/drawable-nodpi/blue_sweep_gradient.xml new file mode 100644 index 000000000000..c183c5deab4f --- /dev/null +++ b/tests/SilkFX/res/drawable-nodpi/blue_sweep_gradient.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <gradient + android:startColor="#000000" + android:endColor="#0000FF" + android:angle="0"/> +</shape>
\ No newline at end of file diff --git a/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml b/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml new file mode 100644 index 000000000000..f20dd424c617 --- /dev/null +++ b/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <gradient + android:startColor="#000000" + android:endColor="#222222" + android:angle="0"/> +</shape>
\ No newline at end of file diff --git a/tests/SilkFX/res/drawable-nodpi/green_sweep_gradient.xml b/tests/SilkFX/res/drawable-nodpi/green_sweep_gradient.xml new file mode 100644 index 000000000000..c600d0f66325 --- /dev/null +++ b/tests/SilkFX/res/drawable-nodpi/green_sweep_gradient.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <gradient + android:startColor="#000000" + android:endColor="#00FF00" + android:angle="0"/> +</shape>
\ No newline at end of file diff --git a/tests/SilkFX/res/drawable-nodpi/grey_sweep_gradient.xml b/tests/SilkFX/res/drawable-nodpi/grey_sweep_gradient.xml new file mode 100644 index 000000000000..d0c17fa2e1b9 --- /dev/null +++ b/tests/SilkFX/res/drawable-nodpi/grey_sweep_gradient.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <gradient + android:startColor="#000000" + android:endColor="#FFFFFF" + android:angle="0"/> +</shape>
\ No newline at end of file diff --git a/tests/SilkFX/res/drawable-nodpi/light_gradient.xml b/tests/SilkFX/res/drawable-nodpi/light_gradient.xml new file mode 100644 index 000000000000..c75f925647e7 --- /dev/null +++ b/tests/SilkFX/res/drawable-nodpi/light_gradient.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <gradient + android:startColor="#E8E8E8" + android:endColor="#FFFFFF" + android:angle="0"/> +</shape>
\ No newline at end of file diff --git a/tests/SilkFX/res/drawable-nodpi/red_sweep_gradient.xml b/tests/SilkFX/res/drawable-nodpi/red_sweep_gradient.xml new file mode 100644 index 000000000000..e3b834a46406 --- /dev/null +++ b/tests/SilkFX/res/drawable-nodpi/red_sweep_gradient.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <gradient + android:startColor="#000000" + android:endColor="#FF0000" + android:angle="0"/> +</shape>
\ No newline at end of file diff --git a/tests/SilkFX/res/layout/color_grid.xml b/tests/SilkFX/res/layout/color_grid.xml new file mode 100644 index 000000000000..37242eee7195 --- /dev/null +++ b/tests/SilkFX/res/layout/color_grid.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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. + --> + +<com.android.test.silkfx.hdr.ColorGrid xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/tests/SilkFX/res/layout/common_base.xml b/tests/SilkFX/res/layout/common_base.xml index 944c6846fbf7..c0eaf9bc1476 100644 --- a/tests/SilkFX/res/layout/common_base.xml +++ b/tests/SilkFX/res/layout/common_base.xml @@ -24,16 +24,6 @@ <FrameLayout android:id="@+id/demo_container" android:layout_width="match_parent" - android:layout_height="wrap_content" /> - - <View - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" /> - - <com.android.test.silkfx.common.HDRIndicator - android:layout_width="match_parent" - android:layout_height="50dp" - android:layout_margin="8dp" /> + android:layout_height="match_parent" /> </LinearLayout>
\ No newline at end of file diff --git a/tests/SilkFX/res/layout/gainmap_decode_test.xml b/tests/SilkFX/res/layout/gainmap_decode_test.xml new file mode 100644 index 000000000000..e7ef61f8dac1 --- /dev/null +++ b/tests/SilkFX/res/layout/gainmap_decode_test.xml @@ -0,0 +1,115 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<com.android.test.silkfx.hdr.GainmapDecodeTest xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <Button + android:id="@+id/decode_full" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="Decode full" /> + + <Button + android:id="@+id/decode_subsampled4" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="Decode subsampled (1/4th)" /> + + <Button + android:id="@+id/decode_scaled66" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="Decode scaled (66%)" /> + + <Button + android:id="@+id/decode_crop" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="Decode croppedSquare" /> + + <Button + android:id="@+id/decode_cropScaled33" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="Decode croppedSquare(33%)" /> + + </LinearLayout> + + <TextView + android:id="@+id/source_info" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal"> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:orientation="vertical" + android:layout_weight="1"> + + <TextView + android:id="@+id/sdr_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <ImageView + android:id="@+id/sdr_source" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="8dp" + android:scaleType="fitStart" /> + </LinearLayout> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:orientation="vertical" + android:layout_weight="1"> + + <TextView + android:id="@+id/gainmap_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <ImageView + android:id="@+id/gainmap" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="8dp" + android:scaleType="fitStart" /> + </LinearLayout> + + </LinearLayout> + +</com.android.test.silkfx.hdr.GainmapDecodeTest>
\ No newline at end of file diff --git a/tests/SilkFX/res/layout/gainmap_image.xml b/tests/SilkFX/res/layout/gainmap_image.xml new file mode 100644 index 000000000000..b0ed9147585e --- /dev/null +++ b/tests/SilkFX/res/layout/gainmap_image.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<com.android.test.silkfx.hdr.GainmapImage xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/gainmap_image"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <RadioGroup android:id="@+id/output_mode" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <RadioButton android:id="@+id/output_sdr" + android:layout_width="wrap_content" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="SDR" /> + + <RadioButton android:id="@+id/output_gainmap" + android:layout_width="wrap_content" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="Gainmap" /> + + <RadioButton android:id="@+id/output_hdr" + android:layout_width="wrap_content" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="HDR" /> + + <RadioButton android:id="@+id/output_hdr_test" + android:layout_width="wrap_content" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="HDR (test)" /> + </RadioGroup> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <Spinner + android:id="@+id/image_selection" + android:layout_width="match_parent" + android:layout_weight="1" + android:layout_height="wrap_content" /> + + <Button + android:id="@+id/gainmap_metadata" + android:layout_width="match_parent" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="Gainmap Metadata..." /> + + </LinearLayout> + + <TextView + android:id="@+id/error_msg" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" /> + + <com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView + android:id="@+id/image" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + </LinearLayout> + +</com.android.test.silkfx.hdr.GainmapImage> diff --git a/tests/SilkFX/res/layout/gainmap_metadata.xml b/tests/SilkFX/res/layout/gainmap_metadata.xml new file mode 100644 index 000000000000..4cc3e0cbdb83 --- /dev/null +++ b/tests/SilkFX/res/layout/gainmap_metadata.xml @@ -0,0 +1,264 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:padding="8dp" + android:orientation="vertical" + android:background="#444444"> + + <TextView + android:id="@+id/gainmap_metadata_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="10dp" + android:text="Metadata for "HDR (test)" (values in linear space):" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="10dp" + android:orientation="horizontal"> + + <TextView + android:id="@+id/gainmap_metadata_gainmapmin_text" + android:layout_width="match_parent" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="Gain Map Min:" /> + + <TextView + android:id="@+id/gainmap_metadata_gainmapmin_val" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ems="4" + android:text="TODO" /> + + <SeekBar + android:id="@+id/gainmap_metadata_gainmapmin" + android:min="0" + android:max="100" + android:layout_width="150dp" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="10dp" + android:orientation="horizontal"> + + <TextView + android:id="@+id/gainmap_metadata_gainmapmax_text" + android:layout_width="match_parent" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="Gain Map Max:" /> + + <TextView + android:id="@+id/gainmap_metadata_gainmapmax_val" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ems="4" + android:text="TODO" /> + + <SeekBar + android:id="@+id/gainmap_metadata_gainmapmax" + android:min="0" + android:max="100" + android:layout_width="150dp" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="10dp" + android:orientation="horizontal"> + + <TextView + android:id="@+id/gainmap_metadata_capacitymin_text" + android:layout_width="match_parent" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="Capacity Min:" /> + + <TextView + android:id="@+id/gainmap_metadata_capacitymin_val" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ems="4" + android:text="TODO" /> + + <SeekBar + android:id="@+id/gainmap_metadata_capacitymin" + android:min="0" + android:max="100" + android:layout_width="150dp" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="10dp" + android:orientation="horizontal"> + + <TextView + android:id="@+id/gainmap_metadata_capacitymax_text" + android:layout_width="match_parent" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="Capacity Max:" /> + + <TextView + android:id="@+id/gainmap_metadata_capacitymax_val" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ems="4" + android:text="TODO" /> + + <SeekBar + android:id="@+id/gainmap_metadata_capacitymax" + android:min="0" + android:max="100" + android:layout_width="150dp" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="10dp" + android:orientation="horizontal"> + + <TextView + android:id="@+id/gainmap_metadata_gamma_text" + android:layout_width="match_parent" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="Gamma:" /> + + <TextView + android:id="@+id/gainmap_metadata_gamma_val" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ems="4" + android:text="TODO" /> + + <SeekBar + android:id="@+id/gainmap_metadata_gamma" + android:min="0" + android:max="100" + android:layout_width="150dp" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="10dp" + android:orientation="horizontal"> + + <TextView + android:id="@+id/gainmap_metadata_offsetsdr_text" + android:layout_width="match_parent" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="Offset SDR:" /> + + <TextView + android:id="@+id/gainmap_metadata_offsetsdr_val" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ems="4" + android:text="TODO" /> + + <SeekBar + android:id="@+id/gainmap_metadata_offsetsdr" + android:min="0" + android:max="100" + android:layout_width="150dp" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="10dp" + android:orientation="horizontal"> + + <TextView + android:id="@+id/gainmap_metadata_offsethdr_text" + android:layout_width="match_parent" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="Offset HDR:" /> + + <TextView + android:id="@+id/gainmap_metadata_offsethdr_val" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ems="4" + android:text="TODO" /> + + <SeekBar + android:id="@+id/gainmap_metadata_offsethdr" + android:min="0" + android:max="100" + android:layout_width="150dp" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <Button + android:id="@+id/gainmap_metadata_reset" + android:layout_width="match_parent" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="Reset" /> + + <Button + android:id="@+id/gainmap_metadata_done" + android:layout_width="match_parent" + android:layout_weight="1" + android:layout_height="wrap_content" + android:text="Done" /> + + </LinearLayout> + + </LinearLayout> + +</RelativeLayout> diff --git a/tests/SilkFX/res/layout/gainmap_transform_test.xml b/tests/SilkFX/res/layout/gainmap_transform_test.xml new file mode 100644 index 000000000000..5aeb53661cbc --- /dev/null +++ b/tests/SilkFX/res/layout/gainmap_transform_test.xml @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<com.android.test.silkfx.hdr.GainmapTransformsTest xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <Button + android:id="@+id/original" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="Original" /> + + <Button + android:id="@+id/scaled" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="Scaled (1/3)" /> + + <Button + android:id="@+id/rotate_90" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="Rotate 90" /> + + <Button + android:id="@+id/rotate_90_scaled" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="Rot90+Scale" /> + + <Button + android:id="@+id/crop" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="Crop" /> + + <Button + android:id="@+id/crop_200" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="Crop 200" /> + + </LinearLayout> + + <TextView + android:id="@+id/source_info" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal"> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:orientation="vertical" + android:layout_weight="1"> + + <TextView + android:id="@+id/sdr_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <ImageView + android:id="@+id/sdr_source" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="8dp" + android:scaleType="fitStart" /> + </LinearLayout> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:orientation="vertical" + android:layout_weight="1"> + + <TextView + android:id="@+id/gainmap_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <ImageView + android:id="@+id/gainmap" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="8dp" + android:scaleType="fitStart" /> + </LinearLayout> + + </LinearLayout> + +</com.android.test.silkfx.hdr.GainmapTransformsTest>
\ No newline at end of file diff --git a/tests/SilkFX/res/layout/gradient_sweep.xml b/tests/SilkFX/res/layout/gradient_sweep.xml new file mode 100644 index 000000000000..261022a40380 --- /dev/null +++ b/tests/SilkFX/res/layout/gradient_sweep.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <View android:background="@drawable/dark_gradient" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + <View android:background="@drawable/light_gradient" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + <View android:background="@drawable/grey_sweep_gradient" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + <View android:background="@drawable/red_sweep_gradient" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + <View android:background="@drawable/green_sweep_gradient" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + <View android:background="@drawable/blue_sweep_gradient" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + +</LinearLayout>
\ No newline at end of file diff --git a/tests/SilkFX/res/layout/hdr_image_viewer.xml b/tests/SilkFX/res/layout/hdr_image_viewer.xml new file mode 100644 index 000000000000..9816430cd915 --- /dev/null +++ b/tests/SilkFX/res/layout/hdr_image_viewer.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <include layout="@layout/color_mode_controls" /> + <include layout="@layout/gainmap_image" /> + +</LinearLayout>
\ No newline at end of file diff --git a/tests/SilkFX/src/com/android/test/silkfx/Main.kt b/tests/SilkFX/src/com/android/test/silkfx/Main.kt index 7132ae8772ea..59a6078376cf 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/Main.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/Main.kt @@ -26,6 +26,7 @@ import android.widget.BaseExpandableListAdapter import android.widget.ExpandableListView import android.widget.TextView import com.android.test.silkfx.app.CommonDemoActivity +import com.android.test.silkfx.app.EXTRA_COMMON_CONTROLS import com.android.test.silkfx.app.EXTRA_LAYOUT import com.android.test.silkfx.app.EXTRA_TITLE import com.android.test.silkfx.hdr.GlowActivity @@ -37,10 +38,11 @@ class Demo(val name: String, val makeIntent: (Context) -> Intent) { constructor(name: String, activity: KClass<out Activity>) : this(name, { context -> Intent(context, activity.java) }) - constructor(name: String, layout: Int) : this(name, { context -> + constructor(name: String, layout: Int, commonControls: Boolean = true) : this(name, { context -> Intent(context, CommonDemoActivity::class.java).apply { putExtra(EXTRA_LAYOUT, layout) putExtra(EXTRA_TITLE, name) + putExtra(EXTRA_COMMON_CONTROLS, commonControls) } }) } @@ -49,7 +51,13 @@ data class DemoGroup(val groupName: String, val demos: List<Demo>) private val AllDemos = listOf( DemoGroup("HDR", listOf( Demo("Glow", GlowActivity::class), - Demo("Blingy Notifications", R.layout.bling_notifications) + Demo("Blingy Notifications", R.layout.bling_notifications), + Demo("Color Grid", R.layout.color_grid), + Demo("Gradient Sweep", R.layout.gradient_sweep), + Demo("Gainmap Image", R.layout.gainmap_image), + Demo("Gainmap Decode Test", R.layout.gainmap_decode_test, commonControls = false), + Demo("Gainmap Transform Test", R.layout.gainmap_transform_test, + commonControls = false) )), DemoGroup("Materials", listOf( Demo("Glass", GlassActivity::class), diff --git a/tests/SilkFX/src/com/android/test/silkfx/app/CommonDemoActivity.kt b/tests/SilkFX/src/com/android/test/silkfx/app/CommonDemoActivity.kt index e0a0a20bc0a0..e56ce40463f4 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/app/CommonDemoActivity.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/app/CommonDemoActivity.kt @@ -22,6 +22,7 @@ import android.view.LayoutInflater const val EXTRA_LAYOUT = "layout" const val EXTRA_TITLE = "title" +const val EXTRA_COMMON_CONTROLS = "common_controls" class CommonDemoActivity : BaseDemoActivity() { @@ -38,8 +39,13 @@ class CommonDemoActivity : BaseDemoActivity() { val title = extras.getString(EXTRA_TITLE, "SilkFX") window.setTitle(title) - setContentView(R.layout.common_base) + if (extras.getBoolean(EXTRA_COMMON_CONTROLS, true)) { + setContentView(R.layout.common_base) + LayoutInflater.from(this).inflate(layout, findViewById(R.id.demo_container), true) + } else { + setContentView(layout) + } + actionBar?.title = title - LayoutInflater.from(this).inflate(layout, findViewById(R.id.demo_container), true) } }
\ No newline at end of file diff --git a/tests/SilkFX/src/com/android/test/silkfx/app/HdrImageViewer.kt b/tests/SilkFX/src/com/android/test/silkfx/app/HdrImageViewer.kt new file mode 100644 index 000000000000..7302169f4d1b --- /dev/null +++ b/tests/SilkFX/src/com/android/test/silkfx/app/HdrImageViewer.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 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.android.test.silkfx.app + +import android.content.Intent +import android.graphics.ImageDecoder +import android.net.Uri +import android.os.Bundle +import android.os.Parcelable +import com.android.test.silkfx.R +import com.android.test.silkfx.hdr.GainmapImage + +class HdrImageViewer : BaseDemoActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.hdr_image_viewer) + + val intent = this.intent ?: return finish() + when (intent.action) { + Intent.ACTION_VIEW -> { + val data = intent.data ?: return finish() + val source = ImageDecoder.createSource(contentResolver, data) + findViewById<GainmapImage>(R.id.gainmap_image)!!.setImageSource(source) + } + Intent.ACTION_SEND -> { + val uri = (intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri) + ?: return finish() + val source = ImageDecoder.createSource(contentResolver, uri) + findViewById<GainmapImage>(R.id.gainmap_image)!!.setImageSource(source) + } + else -> { + finish() + } + } + } +} diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/BaseDrawingView.kt b/tests/SilkFX/src/com/android/test/silkfx/common/BaseDrawingView.kt index 4b85953a24b9..f88e6b01483b 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/common/BaseDrawingView.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/common/BaseDrawingView.kt @@ -23,8 +23,11 @@ import android.util.AttributeSet import android.view.View open class BaseDrawingView : View { + val sRGB = ColorSpace.get(ColorSpace.Named.SRGB) + val displayP3 = ColorSpace.get(ColorSpace.Named.DISPLAY_P3) val scRGB = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB) val bt2020 = ColorSpace.get(ColorSpace.Named.BT2020) + val bt2020_pq = ColorSpace.get(ColorSpace.Named.BT2020_PQ) val lab = ColorSpace.get(ColorSpace.Named.CIE_LAB) val density: Float diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt index b41ee3a9ef2c..56ab755af47b 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt @@ -18,38 +18,24 @@ package com.android.test.silkfx.common import android.content.Context import android.content.pm.ActivityInfo -import android.hardware.display.DisplayManager -import android.os.IBinder import android.util.AttributeSet import android.util.Log -import android.view.SurfaceControl -import android.view.SurfaceControlHdrLayerInfoListener +import android.view.Display +import android.view.View import android.view.Window import android.widget.Button import android.widget.LinearLayout import android.widget.TextView import com.android.test.silkfx.R import com.android.test.silkfx.app.WindowObserver +import java.util.function.Consumer class ColorModeControls : LinearLayout, WindowObserver { - private val COLOR_MODE_HDR10 = 3 - private val SDR_WHITE_POINTS = floatArrayOf(200f, 250f, 300f, 350f, 400f, 100f, 150f) - constructor(context: Context) : this(context, null) - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { - displayManager = context.getSystemService(DisplayManager::class.java)!! - displayId = context.getDisplayId() - displayToken = SurfaceControl.getInternalDisplayToken() - } + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) private var window: Window? = null private var currentModeDisplay: TextView? = null - private val displayManager: DisplayManager - private var targetSdrWhitePointIndex = 0 - private var displayId: Int - private var displayToken: IBinder - - private val whitePoint get() = SDR_WHITE_POINTS[targetSdrWhitePointIndex] override fun onFinishInflate() { super.onFinishInflate() @@ -68,83 +54,54 @@ class ColorModeControls : LinearLayout, WindowObserver { setColorMode(ActivityInfo.COLOR_MODE_HDR) } findViewById<Button>(R.id.mode_hdr10)!!.setOnClickListener { - setColorMode(COLOR_MODE_HDR10) + setColorMode(ActivityInfo.COLOR_MODE_HDR10) } } - private fun setColorMode(newMode: Int) { - val window = window!! - var sdrWhitepointChanged = false - // Need to do this before setting the colorMode, as setting the colorMode will - // trigger the attribute change listener - if (newMode == ActivityInfo.COLOR_MODE_HDR || - newMode == COLOR_MODE_HDR10) { - if (window.colorMode == newMode) { - targetSdrWhitePointIndex = (targetSdrWhitePointIndex + 1) % SDR_WHITE_POINTS.size - sdrWhitepointChanged = true - } - setBrightness(1.0f) - } else { - setBrightness(.4f) - } - window.colorMode = newMode - if (sdrWhitepointChanged) { - threadedRenderer?.setColorMode(newMode, whitePoint) - } - val whitePoint = whitePoint.toInt() - currentModeDisplay?.run { - text = "Current Mode: " + when (newMode) { - ActivityInfo.COLOR_MODE_DEFAULT -> "Default/SRGB" - ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT -> "Wide Gamut" - ActivityInfo.COLOR_MODE_HDR -> "HDR (sdr white point $whitePoint)" - COLOR_MODE_HDR10 -> "HDR10 (sdr white point $whitePoint)" - else -> "Unknown" - } + private val hdrSdrListener = Consumer<Display> { display -> + Log.d("SilkFX", "HDR/SDR changed ${display.hdrSdrRatio}") + post { + updateModeInfoDisplay() } } - override fun setWindow(window: Window) { - this.window = window - } - - private fun setBrightness(level: Float) { - // To keep window state in sync - window?.attributes?.screenBrightness = level - invalidate() - // To force an 'immediate' snap to what we want - // Imperfect, but close enough, synchronization by waiting for frame commit to set the value - viewTreeObserver.registerFrameCommitCallback { - try { - SurfaceControl.setDisplayBrightness(displayToken, level) - displayManager.setTemporaryBrightness(displayId, level) - } catch (ex: Exception) { - // Ignore a permission denied rejection - it doesn't meaningfully change much - } + override fun onAttachedToWindow() { + super.onAttachedToWindow() + val hdrVis = if (display.isHdrSdrRatioAvailable) { + display.registerHdrSdrRatioChangedListener({ it.run() }, hdrSdrListener) + View.VISIBLE + } else { + View.GONE } + findViewById<Button>(R.id.mode_hdr)!!.visibility = hdrVis + findViewById<Button>(R.id.mode_hdr10)!!.visibility = hdrVis } - private val listener = object : SurfaceControlHdrLayerInfoListener() { - override fun onHdrInfoChanged( - displayToken: IBinder?, - numberOfHdrLayers: Int, - maxW: Int, - maxH: Int, - flags: Int - ) { - Log.d("HDRInfo", "onHdrInfoChanged: numLayer = $numberOfHdrLayers ($maxW x $maxH)" + - ", flags = $flags") - } + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + display.unregisterHdrSdrRatioChangedListener(hdrSdrListener) } - override fun onAttachedToWindow() { - super.onAttachedToWindow() + private fun setColorMode(newMode: Int) { + window!!.colorMode = newMode + updateModeInfoDisplay() + } - threadedRenderer?.setColorMode(window!!.colorMode, whitePoint) - listener.register(displayToken) + override fun setWindow(window: Window) { + this.window = window } - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - listener.unregister(displayToken) + private fun updateModeInfoDisplay() { + val sdrHdrRatio = display?.hdrSdrRatio ?: 1.0f + val colormode = window!!.colorMode + currentModeDisplay?.run { + text = "Current Mode: " + when (colormode) { + ActivityInfo.COLOR_MODE_DEFAULT -> "Default/SRGB" + ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT -> "Wide Gamut" + ActivityInfo.COLOR_MODE_HDR -> "HDR (sdr/hdr ratio $sdrHdrRatio)" + ActivityInfo.COLOR_MODE_HDR10 -> "HDR10 (sdr/hdr ratio $sdrHdrRatio)" + else -> "Unknown" + } + } } -}
\ No newline at end of file +} diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/ColorGrid.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/ColorGrid.kt new file mode 100644 index 000000000000..6920f832333f --- /dev/null +++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/ColorGrid.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 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.android.test.silkfx.hdr + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.ColorSpace +import android.graphics.Paint +import android.graphics.Rect +import android.util.AttributeSet +import com.android.test.silkfx.common.BaseDrawingView + +class ColorGrid(context: Context, attrs: AttributeSet?) : BaseDrawingView(context, attrs) { + + init { + isClickable = true + setOnClickListener { + invalidate() + } + } + + fun toMaxColor(color: Int, colorspace: ColorSpace): Long { + val red = (Color.red(color) / 255f) * colorspace.getMaxValue(0) + val green = (Color.green(color) / 255f) * colorspace.getMaxValue(1) + val blue = (Color.blue(color) / 255f) * colorspace.getMaxValue(2) + val alpha = Color.alpha(color) / 255f + return Color.pack(red, green, blue, alpha, colorspace) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + val paint = Paint() + paint.isDither = true + paint.isAntiAlias = true + paint.textSize = 18.dp() + paint.textAlign = Paint.Align.LEFT + + val labels = arrayOf("sRGB", "Display P3", "BT2020_PQ", "scRGB(max)") + val colorSpaces = arrayOf(sRGB, displayP3, bt2020_pq, scRGB) + + val colWidth = width / colorSpaces.size.toFloat() + val rowHeight = minOf((height - 20.dp()) / 4f, colWidth) + + val dest = Rect(0, 0, rowHeight.toInt(), colWidth.toInt()) + + for (colIndex in labels.indices) { + canvas.save() + canvas.translate(colIndex * colWidth, 20.dp()) + + paint.color = Color.WHITE + canvas.drawText(labels[colIndex], 0f, 1f, paint) + + arrayOf(Color.WHITE, Color.RED, Color.BLUE, Color.GREEN).forEach { + paint.setColor(toMaxColor(it, colorSpaces[colIndex])) + canvas.drawRect(dest, paint) + canvas.translate(0f, rowHeight) + } + canvas.restore() + } + } +}
\ No newline at end of file diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt new file mode 100644 index 000000000000..585320aee615 --- /dev/null +++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2023 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.android.test.silkfx.hdr + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.ColorMatrixColorFilter +import android.graphics.Gainmap +import android.graphics.ImageDecoder +import android.graphics.Paint +import android.graphics.Rect +import android.util.AttributeSet +import android.widget.Button +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.android.test.silkfx.R + +enum class DecodeMode { + Full, + Subsampled4, + Scaled66, + CropedSquared, + CropedSquaredScaled33 +} + +fun gainmapVisualizer(gainmap: Gainmap): Bitmap { + val map = gainmap.gainmapContents + val gainmapVisualizer = Bitmap.createBitmap(map.width, map.height, + Bitmap.Config.ARGB_8888) + val canvas = Canvas(gainmapVisualizer!!) + val paint = Paint() + paint.colorFilter = ColorMatrixColorFilter( + floatArrayOf( + 0f, 0f, 0f, 1f, 0f, + 0f, 0f, 0f, 1f, 0f, + 0f, 0f, 0f, 1f, 0f, + 0f, 0f, 0f, 0f, 255f + ) + ) + canvas.drawBitmap(map, 0f, 0f, paint) + canvas.setBitmap(null) + return gainmapVisualizer +} + +class GainmapDecodeTest(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) { + + private fun decode(mode: DecodeMode) { + val source = ImageDecoder.createSource(resources.assets, + "gainmaps/${context.assets.list("gainmaps")!![0]}") + + val sourceInfo = findViewById<TextView>(R.id.source_info)!! + + val gainmapImage = ImageDecoder.decodeBitmap(source) { decoder, info, source -> + decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE + sourceInfo.text = + "Original size ${info.size.width}x${info.size.height}; mime-type = ${info.mimeType}" + + when (mode) { + DecodeMode.Full -> {} + DecodeMode.Subsampled4 -> { + decoder.setTargetSampleSize(4) + } + DecodeMode.Scaled66 -> { + val size = info.size + decoder.setTargetSize((size.width * .66).toInt(), (size.height * .66).toInt()) + } + DecodeMode.CropedSquared -> { + val dimen = minOf(info.size.width, info.size.height) + decoder.crop = Rect(50, 50, dimen - 100, dimen - 100) + } + DecodeMode.CropedSquaredScaled33 -> { + val size = info.size + val targetWidth = (size.width * .33).toInt() + val targetHeight = (size.height * .33).toInt() + decoder.setTargetSize(targetWidth, targetHeight) + val dimen = minOf(targetWidth, targetHeight) + decoder.crop = Rect(50, 50, dimen - 100, dimen - 100) + } + } + } + + val gainmapContents = gainmapImage.gainmap?.let { gainmapVisualizer(it) } + val sdrBitmap = gainmapImage.also { it.gainmap = null } + + findViewById<ImageView>(R.id.sdr_source)!!.setImageBitmap(sdrBitmap) + findViewById<TextView>(R.id.sdr_label)!!.text = + "SDR Size: ${sdrBitmap.width}x${sdrBitmap.height}" + + findViewById<ImageView>(R.id.gainmap)!!.setImageBitmap(gainmapContents) + findViewById<TextView>(R.id.gainmap_label)!!.text = + "Gainmap Size: ${gainmapContents?.width ?: 0}x${gainmapContents?.height ?: 0}" + } + + override fun onFinishInflate() { + super.onFinishInflate() + decode(DecodeMode.Full) + + findViewById<Button>(R.id.decode_full)!!.setOnClickListener { + decode(DecodeMode.Full) + } + findViewById<Button>(R.id.decode_subsampled4)!!.setOnClickListener { + decode(DecodeMode.Subsampled4) + } + findViewById<Button>(R.id.decode_scaled66)!!.setOnClickListener { + decode(DecodeMode.Scaled66) + } + findViewById<Button>(R.id.decode_crop)!!.setOnClickListener { + decode(DecodeMode.CropedSquared) + } + findViewById<Button>(R.id.decode_cropScaled33)!!.setOnClickListener { + decode(DecodeMode.CropedSquaredScaled33) + } + } +}
\ No newline at end of file diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt new file mode 100644 index 000000000000..7cf69b7780d9 --- /dev/null +++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapImage.kt @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2023 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.android.test.silkfx.hdr + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.ColorMatrixColorFilter +import android.graphics.Gainmap +import android.graphics.ImageDecoder +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Button +import android.widget.FrameLayout +import android.widget.RadioGroup +import android.widget.Spinner +import android.widget.TextView +import com.android.test.silkfx.R +import com.davemorrissey.labs.subscaleview.ImageSource +import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView + +class GainmapImage(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) { + + private val gainmapImages: Array<String> + private var selectedImage = -1 + private var outputMode = R.id.output_hdr + private var bitmap: Bitmap? = null + private var gainmap: Gainmap? = null + private var gainmapVisualizer: Bitmap? = null + private lateinit var imageView: SubsamplingScaleImageView + private lateinit var gainmapMetadataEditor: GainmapMetadataEditor + + init { + gainmapImages = context.assets.list("gainmaps")!! + } + + fun setImageSource(source: ImageDecoder.Source) { + findViewById<Spinner>(R.id.image_selection)!!.visibility = View.GONE + doDecode(source) + } + + override fun onFinishInflate() { + super.onFinishInflate() + + imageView = findViewById(R.id.image)!! + gainmapMetadataEditor = GainmapMetadataEditor(this, imageView) + + findViewById<RadioGroup>(R.id.output_mode)!!.also { + it.check(outputMode) + it.setOnCheckedChangeListener { _, checkedId -> + val previousMode = outputMode + outputMode = checkedId + if (previousMode == R.id.output_sdr && checkedId == R.id.output_hdr) { + animateToHdr() + } else if (previousMode == R.id.output_hdr && checkedId == R.id.output_sdr) { + animateToSdr() + } else { + updateDisplay() + } + } + } + + val spinner = findViewById<Spinner>(R.id.image_selection)!! + val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, gainmapImages) + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + spinner.adapter = adapter + spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long + ) { + setImage(position) + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + } + } + + findViewById<Button>(R.id.gainmap_metadata)!!.setOnClickListener { + gainmapMetadataEditor.openEditor() + } + + setImage(0) + + imageView.apply { + isClickable = true + setOnClickListener { + animate().alpha(.5f).withEndAction { + animate().alpha(1f).start() + }.start() + } + } + } + + private fun setImage(position: Int) { + if (selectedImage == position) return + selectedImage = position + val source = ImageDecoder.createSource(resources.assets, + "gainmaps/${gainmapImages[position]}") + doDecode(source) + } + + private fun doDecode(source: ImageDecoder.Source) { + gainmap = null + bitmap = ImageDecoder.decodeBitmap(source) { decoder, info, source -> + decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE + } + if (!bitmap!!.hasGainmap()) { + outputMode = R.id.output_sdr + findViewById<TextView>(R.id.error_msg)!!.also { + it.visibility = View.VISIBLE + it.text = "Image doesn't have a gainmap, only showing in SDR" + } + findViewById<RadioGroup>(R.id.output_mode)!!.also { + it.check(R.id.output_sdr) + it.visibility = View.GONE + } + } else { + findViewById<TextView>(R.id.error_msg)!!.visibility = View.GONE + findViewById<RadioGroup>(R.id.output_mode)!!.visibility = View.VISIBLE + + gainmap = bitmap!!.gainmap + gainmapMetadataEditor.setGainmap(gainmap) + val map = gainmap!!.gainmapContents + if (map.config != Bitmap.Config.ALPHA_8) { + gainmapVisualizer = map + } else { + gainmapVisualizer = Bitmap.createBitmap(map.width, map.height, + Bitmap.Config.ARGB_8888) + val canvas = Canvas(gainmapVisualizer!!) + val paint = Paint() + paint.colorFilter = ColorMatrixColorFilter( + floatArrayOf( + 0f, 0f, 0f, 1f, 0f, + 0f, 0f, 0f, 1f, 0f, + 0f, 0f, 0f, 1f, 0f, + 0f, 0f, 0f, 0f, 255f + ) + ) + canvas.drawBitmap(map, 0f, 0f, paint) + canvas.setBitmap(null) + } + } + + updateDisplay() + } + + private fun animateToHdr() { + if (bitmap == null || gainmap == null) return + + // TODO: Trigger an animation + updateDisplay() + } + + private fun animateToSdr() { + if (bitmap == null) return + + // TODO: Trigger an animation + updateDisplay() + } + + private fun updateDisplay() { + if (bitmap == null) return + + imageView.setImage(ImageSource.cachedBitmap(when (outputMode) { + R.id.output_hdr -> { + gainmapMetadataEditor.useOriginalMetadata() + bitmap!!.gainmap = gainmap + bitmap!! + } + + R.id.output_hdr_test -> { + gainmapMetadataEditor.useEditMetadata() + bitmap!!.gainmap = gainmap + bitmap!! + } + + R.id.output_sdr -> { + bitmap!!.gainmap = null; bitmap!! + } + + R.id.output_gainmap -> gainmapVisualizer!! + else -> throw IllegalStateException() + })) + } +} diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt new file mode 100644 index 000000000000..8a653045c97b --- /dev/null +++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapMetadataEditor.kt @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2023 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.android.test.silkfx.hdr + +import android.graphics.Gainmap +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.PopupWindow +import android.widget.SeekBar +import android.widget.TextView +import com.android.test.silkfx.R + +data class GainmapMetadata( + var ratioMin: Float, + var ratioMax: Float, + var capacityMin: Float, + var capacityMax: Float, + var gamma: Float, + var offsetSdr: Float, + var offsetHdr: Float +) + +class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) { + private var gainmap: Gainmap? = null + private var showingEdits = false + + private var metadataPopup: PopupWindow? = null + + private var originalMetadata: GainmapMetadata = GainmapMetadata( + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f) + private var currentMetadata: GainmapMetadata = originalMetadata.copy() + + private val maxProgress = 100.0f + + private val minRatioMin = .001f + private val maxRatioMin = 1.0f + private val minRatioMax = 1.0f + private val maxRatioMax = 16.0f + private val minCapacityMin = 1.0f + private val maxCapacityMin = maxRatioMax + private val minCapacityMax = 1.001f + private val maxCapacityMax = maxRatioMax + private val minGamma = 0.1f + private val maxGamma = 3.0f + // Min and max offsets are 0.0 and 1.0 respectively + + fun setGainmap(newGainmap: Gainmap?) { + gainmap = newGainmap + originalMetadata = GainmapMetadata(gainmap!!.getRatioMin()[0], + gainmap!!.getRatioMax()[0], gainmap!!.getMinDisplayRatioForHdrTransition(), + gainmap!!.getDisplayRatioForFullHdr(), gainmap!!.getGamma()[0], + gainmap!!.getEpsilonSdr()[0], gainmap!!.getEpsilonHdr()[0]) + currentMetadata = originalMetadata.copy() + } + + fun useOriginalMetadata() { + showingEdits = false + applyMetadata(originalMetadata) + } + + fun useEditMetadata() { + showingEdits = true + applyMetadata(currentMetadata) + } + + fun closeEditor() { + metadataPopup?.let { + it.dismiss() + metadataPopup = null + } + } + + fun openEditor() { + if (metadataPopup != null) return + + val view = LayoutInflater.from(parent.getContext()).inflate(R.layout.gainmap_metadata, null) + + metadataPopup = PopupWindow(view, ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT) + metadataPopup!!.showAtLocation(view, Gravity.CENTER, 0, 0) + + (view.getParent() as ViewGroup).removeView(view) + parent.addView(view) + + view.findViewById<Button>(R.id.gainmap_metadata_done)!!.setOnClickListener { + closeEditor() + } + + view.findViewById<Button>(R.id.gainmap_metadata_reset)!!.setOnClickListener { + resetGainmapMetadata() + } + + updateMetadataUi() + + val gainmapMinSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmin) + val gainmapMaxSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmax) + val capacityMinSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymin) + val capacityMaxSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymax) + val gammaSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_gamma) + val offsetSdrSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr) + val offsetHdrSeek = view.findViewById<SeekBar>(R.id.gainmap_metadata_offsethdr) + arrayOf(gainmapMinSeek, gainmapMaxSeek, capacityMinSeek, capacityMaxSeek, gammaSeek, + offsetSdrSeek, offsetHdrSeek).forEach { + it.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{ + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + if (!fromUser) return + val normalized = progress.toFloat() / maxProgress + when (seekBar) { + gainmapMinSeek -> updateGainmapMin(normalized) + gainmapMaxSeek -> updateGainmapMax(normalized) + capacityMinSeek -> updateCapacityMin(normalized) + capacityMaxSeek -> updateCapacityMax(normalized) + gammaSeek -> updateGamma(normalized) + offsetSdrSeek -> updateOffsetSdr(normalized) + offsetHdrSeek -> updateOffsetHdr(normalized) + } + } + + override fun onStartTrackingTouch(seekBar: SeekBar) {} + override fun onStopTrackingTouch(seekBar: SeekBar) {} + }) + } + } + + private fun updateMetadataUi() { + val gainmapMinSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmin) + val gainmapMaxSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_gainmapmax) + val capacityMinSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymin) + val capacityMaxSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_capacitymax) + val gammaSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_gamma) + val offsetSdrSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr) + val offsetHdrSeek = parent.findViewById<SeekBar>(R.id.gainmap_metadata_offsethdr) + + gainmapMinSeek.setProgress( + ((currentMetadata.ratioMin - minRatioMin) / maxRatioMin * maxProgress).toInt()) + gainmapMaxSeek.setProgress( + ((currentMetadata.ratioMax - minRatioMax) / maxRatioMax * maxProgress).toInt()) + capacityMinSeek.setProgress( + ((currentMetadata.capacityMin - minCapacityMin) / maxCapacityMin * maxProgress).toInt()) + capacityMaxSeek.setProgress( + ((currentMetadata.capacityMax - minCapacityMax) / maxCapacityMax * maxProgress).toInt()) + gammaSeek.setProgress( + ((currentMetadata.gamma - minGamma) / maxGamma * maxProgress).toInt()) + // Log base 3 via: log_b(x) = log_y(x) / log_y(b) + offsetSdrSeek.setProgress( + ((1.0 - Math.log(currentMetadata.offsetSdr.toDouble() / Math.log(3.0)) / -11.0) + .toFloat() * maxProgress).toInt()) + offsetHdrSeek.setProgress( + ((1.0 - Math.log(currentMetadata.offsetHdr.toDouble() / Math.log(3.0)) / -11.0) + .toFloat() * maxProgress).toInt()) + + parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val)!!.setText( + "%.3f".format(currentMetadata.ratioMin)) + parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val)!!.setText( + "%.3f".format(currentMetadata.ratioMax)) + parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymin_val)!!.setText( + "%.3f".format(currentMetadata.capacityMin)) + parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymax_val)!!.setText( + "%.3f".format(currentMetadata.capacityMax)) + parent.findViewById<TextView>(R.id.gainmap_metadata_gamma_val)!!.setText( + "%.3f".format(currentMetadata.gamma)) + parent.findViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val)!!.setText( + "%.5f".format(currentMetadata.offsetSdr)) + parent.findViewById<TextView>(R.id.gainmap_metadata_offsethdr_val)!!.setText( + "%.5f".format(currentMetadata.offsetHdr)) + } + + private fun resetGainmapMetadata() { + currentMetadata = originalMetadata.copy() + applyMetadata(currentMetadata) + updateMetadataUi() + } + + private fun applyMetadata(newMetadata: GainmapMetadata) { + gainmap!!.setRatioMin(newMetadata.ratioMin, newMetadata.ratioMin, newMetadata.ratioMin) + gainmap!!.setRatioMax(newMetadata.ratioMax, newMetadata.ratioMax, newMetadata.ratioMax) + gainmap!!.setMinDisplayRatioForHdrTransition(newMetadata.capacityMin) + gainmap!!.setDisplayRatioForFullHdr(newMetadata.capacityMax) + gainmap!!.setGamma(newMetadata.gamma, newMetadata.gamma, newMetadata.gamma) + gainmap!!.setEpsilonSdr(newMetadata.offsetSdr, newMetadata.offsetSdr, newMetadata.offsetSdr) + gainmap!!.setEpsilonHdr(newMetadata.offsetHdr, newMetadata.offsetHdr, newMetadata.offsetHdr) + renderView.invalidate() + } + + private fun updateGainmapMin(normalized: Float) { + val newValue = minRatioMin + normalized * (maxRatioMin - minRatioMin) + parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val)!!.setText( + "%.3f".format(newValue)) + currentMetadata.ratioMin = newValue + if (showingEdits) { + gainmap!!.setRatioMin(newValue, newValue, newValue) + renderView.invalidate() + } + } + + private fun updateGainmapMax(normalized: Float) { + val newValue = minRatioMax + normalized * (maxRatioMax - minRatioMax) + parent.findViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val)!!.setText( + "%.3f".format(newValue)) + currentMetadata.ratioMax = newValue + if (showingEdits) { + gainmap!!.setRatioMax(newValue, newValue, newValue) + renderView.invalidate() + } + } + + private fun updateCapacityMin(normalized: Float) { + val newValue = minCapacityMin + normalized * (maxCapacityMin - minCapacityMin) + parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymin_val)!!.setText( + "%.3f".format(newValue)) + currentMetadata.capacityMin = newValue + if (showingEdits) { + gainmap!!.setMinDisplayRatioForHdrTransition(newValue) + renderView.invalidate() + } + } + + private fun updateCapacityMax(normalized: Float) { + val newValue = minCapacityMax + normalized * (maxCapacityMax - minCapacityMax) + parent.findViewById<TextView>(R.id.gainmap_metadata_capacitymax_val)!!.setText( + "%.3f".format(newValue)) + currentMetadata.capacityMax = newValue + if (showingEdits) { + gainmap!!.setDisplayRatioForFullHdr(newValue) + renderView.invalidate() + } + } + + private fun updateGamma(normalized: Float) { + val newValue = minGamma + normalized * (maxGamma - minGamma) + parent.findViewById<TextView>(R.id.gainmap_metadata_gamma_val)!!.setText( + "%.3f".format(newValue)) + currentMetadata.gamma = newValue + if (showingEdits) { + gainmap!!.setGamma(newValue, newValue, newValue) + renderView.invalidate() + } + } + + private fun updateOffsetSdr(normalized: Float) { + var newValue = 0.0f + if (normalized > 0.0f ) { + newValue = Math.pow(3.0, (1.0 - normalized.toDouble()) * -11.0).toFloat() + } + parent.findViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val)!!.setText( + "%.5f".format(newValue)) + currentMetadata.offsetSdr = newValue + if (showingEdits) { + gainmap!!.setEpsilonSdr(newValue, newValue, newValue) + renderView.invalidate() + } + } + + private fun updateOffsetHdr(normalized: Float) { + var newValue = 0.0f + if (normalized > 0.0f ) { + newValue = Math.pow(3.0, (1.0 - normalized.toDouble()) * -11.0).toFloat() + } + parent.findViewById<TextView>(R.id.gainmap_metadata_offsethdr_val)!!.setText( + "%.5f".format(newValue)) + currentMetadata.offsetHdr = newValue + if (showingEdits) { + gainmap!!.setEpsilonHdr(newValue, newValue, newValue) + renderView.invalidate() + } + } +} diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapTransformsTest.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapTransformsTest.kt new file mode 100644 index 000000000000..20984fae2133 --- /dev/null +++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapTransformsTest.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2023 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.android.test.silkfx.hdr + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.ImageDecoder +import android.graphics.Matrix +import android.util.AttributeSet +import android.widget.Button +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.android.test.silkfx.R + +class GainmapTransformsTest(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) { + + private val sourceImage = loadSample() + + private fun loadSample(): Bitmap { + val source = ImageDecoder.createSource(resources.assets, + "gainmaps/${context.assets.list("gainmaps")!![0]}") + + return ImageDecoder.decodeBitmap(source) { decoder, info, source -> + decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE + } + } + + private fun process(transform: (Bitmap) -> Bitmap) { + val result = transform(sourceImage) + + val gainmapContents = result.gainmap?.let { gainmapVisualizer(it) } + val sdrBitmap = result.also { it.gainmap = null } + + findViewById<ImageView>(R.id.sdr_source)!!.setImageBitmap(sdrBitmap) + findViewById<TextView>(R.id.sdr_label)!!.text = + "SDR Size: ${sdrBitmap.width}x${sdrBitmap.height}" + + findViewById<ImageView>(R.id.gainmap)!!.setImageBitmap(gainmapContents) + findViewById<TextView>(R.id.gainmap_label)!!.text = + "Gainmap Size: ${gainmapContents?.width ?: 0}x${gainmapContents?.height ?: 0}" + } + + override fun onFinishInflate() { + super.onFinishInflate() + val sourceInfo = findViewById<TextView>(R.id.source_info)!! + sourceInfo.text = "Original size ${sourceImage.width}x${sourceImage.height}" + process { it.copy(Bitmap.Config.ARGB_8888, false) } + + findViewById<Button>(R.id.original)!!.setOnClickListener { + process { it.copy(Bitmap.Config.ARGB_8888, false) } + } + + findViewById<Button>(R.id.scaled)!!.setOnClickListener { + process { Bitmap.createScaledBitmap(it, it.width / 3, it.height / 3, true) } + } + + findViewById<Button>(R.id.rotate_90)!!.setOnClickListener { + process { + val width: Int = it.width + val height: Int = it.height + + val m = Matrix() + m.setRotate(90.0f, (width / 2).toFloat(), (height / 2).toFloat()) + Bitmap.createBitmap(it, 0, 0, width, height, m, false) + } + } + + findViewById<Button>(R.id.rotate_90_scaled)!!.setOnClickListener { + process { + val width: Int = it.width + val height: Int = it.height + + val m = Matrix() + m.setRotate(90.0f, (width / 2).toFloat(), (height / 2).toFloat()) + m.preScale(.3f, .3f) + Bitmap.createBitmap(it, 0, 0, width, height, m, false) + } + } + + findViewById<Button>(R.id.crop)!!.setOnClickListener { + process { + val width: Int = it.width + val height: Int = it.height + Bitmap.createBitmap(it, width / 2, height / 2, + width / 4, height / 4, null, false) + } + } + + findViewById<Button>(R.id.crop_200)!!.setOnClickListener { + process { + val width: Int = it.width + val height: Int = it.height + + val m = Matrix() + m.setRotate(200.0f, (width / 2).toFloat(), (height / 2).toFloat()) + Bitmap.createBitmap(it, width / 2, height / 2, + width / 4, height / 4, m, false) + } + } + } +}
\ No newline at end of file diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/RadialGlow.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/RadialGlow.kt index 599585e9d125..20acb4919c78 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/hdr/RadialGlow.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/RadialGlow.kt @@ -26,9 +26,7 @@ import android.util.AttributeSet import com.android.test.silkfx.common.BaseDrawingView import kotlin.math.min -class RadialGlow : BaseDrawingView { - - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) +class RadialGlow(context: Context, attrs: AttributeSet?) : BaseDrawingView(context, attrs) { var glowToggle = false diff --git a/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java index c11b0f3acf79..f85fb0f267d5 100644 --- a/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java +++ b/tests/SmokeTestApps/src/com/android/smoketest/triggers/CrashyApp.java @@ -30,6 +30,7 @@ public class CrashyApp extends Activity { setContentView(tv); } + @SuppressWarnings("ReturnValueIgnored") @Override public void onResume() { ((String) null).length(); diff --git a/tests/SoundTriggerTestApp/OWNERS b/tests/SoundTriggerTestApp/OWNERS index 9db19a37812b..a0fcfc52704d 100644 --- a/tests/SoundTriggerTestApp/OWNERS +++ b/tests/SoundTriggerTestApp/OWNERS @@ -1,2 +1,2 @@ -include /core/java/android/media/soundtrigger/OWNERS +include /media/java/android/media/soundtrigger/OWNERS mdooley@google.com diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java index 6d4ffcff7d45..3567c08d0285 100644 --- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java +++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java @@ -92,7 +92,7 @@ public class SoundTriggerTestService extends Service { super.onCreate(); IntentFilter filter = new IntentFilter(); filter.addAction(INTENT_ACTION); - registerReceiver(mBroadcastReceiver, filter); + registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED); // Make sure the data directory exists, and we're the owner of it. try { diff --git a/tests/SoundTriggerTests/Android.mk b/tests/SoundTriggerTests/Android.mk deleted file mode 100644 index cc0fa1cd0840..000000000000 --- a/tests/SoundTriggerTests/Android.mk +++ /dev/null @@ -1,39 +0,0 @@ -# -# Copyright (C) 2014 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. -# -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -ifeq ($(SOUND_TRIGGER_USE_STUB_MODULE), 1) - LOCAL_SRC_FILES := $(call all-subdir-java-files) - LOCAL_PRIVILEGED_MODULE := true - LOCAL_CERTIFICATE := platform - TARGET_OUT_DATA_APPS_PRIVILEGED := $(TARGET_OUT_DATA)/priv-app -else - LOCAL_SRC_FILES := src/android/hardware/soundtrigger/SoundTriggerTest.java -endif - -LOCAL_STATIC_JAVA_LIBRARIES := mockito-target -LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base - -LOCAL_PACKAGE_NAME := SoundTriggerTests -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE -LOCAL_PRIVATE_PLATFORM_APIS := true - -include $(BUILD_PACKAGE) diff --git a/tests/SoundTriggerTests/OWNERS b/tests/SoundTriggerTests/OWNERS deleted file mode 100644 index 816bc6bba639..000000000000 --- a/tests/SoundTriggerTests/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /core/java/android/media/soundtrigger/OWNERS diff --git a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java deleted file mode 100644 index e36f398c53ea..000000000000 --- a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/SoundTriggerTest.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Copyright (C) 2014 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 android.hardware.soundtrigger; - -import android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel; -import android.hardware.soundtrigger.SoundTrigger.Keyphrase; -import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; -import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra; -import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; -import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; -import android.media.AudioFormat; -import android.os.Parcel; -import android.test.InstrumentationTestCase; -import android.test.suitebuilder.annotation.LargeTest; -import android.test.suitebuilder.annotation.SmallTest; - -import java.util.Arrays; -import java.util.Locale; -import java.util.Random; -import java.util.UUID; - -public class SoundTriggerTest extends InstrumentationTestCase { - private Random mRandom = new Random(); - - @SmallTest - public void testKeyphraseParcelUnparcel_noUsers() throws Exception { - Keyphrase keyphrase = new Keyphrase(1, 0, - Locale.forLanguageTag("en-US"), "hello", null); - - // Write to a parcel - Parcel parcel = Parcel.obtain(); - keyphrase.writeToParcel(parcel, 0); - - // Read from it - parcel.setDataPosition(0); - Keyphrase unparceled = Keyphrase.CREATOR.createFromParcel(parcel); - - // Verify that they are the same - assertEquals(keyphrase.getId(), unparceled.getId()); - assertNull(unparceled.getUsers()); - assertEquals(keyphrase.getLocale(), unparceled.getLocale()); - assertEquals(keyphrase.getText(), unparceled.getText()); - } - - @SmallTest - public void testKeyphraseParcelUnparcel_zeroUsers() throws Exception { - Keyphrase keyphrase = new Keyphrase(1, 0, - Locale.forLanguageTag("en-US"), "hello", new int[0]); - - // Write to a parcel - Parcel parcel = Parcel.obtain(); - keyphrase.writeToParcel(parcel, 0); - - // Read from it - parcel.setDataPosition(0); - Keyphrase unparceled = Keyphrase.CREATOR.createFromParcel(parcel); - - // Verify that they are the same - assertEquals(keyphrase.getId(), unparceled.getId()); - assertTrue(Arrays.equals(keyphrase.getUsers(), unparceled.getUsers())); - assertEquals(keyphrase.getLocale(), unparceled.getLocale()); - assertEquals(keyphrase.getText(), unparceled.getText()); - } - - @SmallTest - public void testKeyphraseParcelUnparcel_pos() throws Exception { - Keyphrase keyphrase = new Keyphrase(1, 0, - Locale.forLanguageTag("en-US"), "hello", new int[] {1, 2, 3, 4, 5}); - - // Write to a parcel - Parcel parcel = Parcel.obtain(); - keyphrase.writeToParcel(parcel, 0); - - // Read from it - parcel.setDataPosition(0); - Keyphrase unparceled = Keyphrase.CREATOR.createFromParcel(parcel); - - // Verify that they are the same - assertEquals(keyphrase.getId(), unparceled.getId()); - assertTrue(Arrays.equals(keyphrase.getUsers(), unparceled.getUsers())); - assertEquals(keyphrase.getLocale(), unparceled.getLocale()); - assertEquals(keyphrase.getText(), unparceled.getText()); - } - - @SmallTest - public void testKeyphraseSoundModelParcelUnparcel_noData() throws Exception { - Keyphrase[] keyphrases = new Keyphrase[2]; - keyphrases[0] = new Keyphrase(1, 0, Locale.forLanguageTag("en-US"), - "hello", new int[] {0}); - keyphrases[1] = new Keyphrase(2, 0, Locale.forLanguageTag("fr-FR"), - "there", new int[] {1, 2}); - KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(), - null, keyphrases); - - // Write to a parcel - Parcel parcel = Parcel.obtain(); - ksm.writeToParcel(parcel, 0); - - // Read from it - parcel.setDataPosition(0); - KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel); - - // Verify that they are the same - assertEquals(ksm.getUuid(), unparceled.getUuid()); - assertNull(unparceled.getData()); - assertEquals(ksm.getType(), unparceled.getType()); - assertTrue(Arrays.equals(keyphrases, unparceled.getKeyphrases())); - } - - @SmallTest - public void testKeyphraseSoundModelParcelUnparcel_zeroData() throws Exception { - Keyphrase[] keyphrases = new Keyphrase[2]; - keyphrases[0] = new Keyphrase(1, 0, Locale.forLanguageTag("en-US"), - "hello", new int[] {0}); - keyphrases[1] = new Keyphrase(2, 0, Locale.forLanguageTag("fr-FR"), - "there", new int[] {1, 2}); - KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(), - new byte[0], keyphrases); - - // Write to a parcel - Parcel parcel = Parcel.obtain(); - ksm.writeToParcel(parcel, 0); - - // Read from it - parcel.setDataPosition(0); - KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel); - - // Verify that they are the same - assertEquals(ksm.getUuid(), unparceled.getUuid()); - assertEquals(ksm.getType(), unparceled.getType()); - assertTrue(Arrays.equals(ksm.getKeyphrases(), unparceled.getKeyphrases())); - assertTrue(Arrays.equals(ksm.getData(), unparceled.getData())); - } - - @SmallTest - public void testKeyphraseSoundModelParcelUnparcel_noKeyphrases() throws Exception { - byte[] data = new byte[10]; - mRandom.nextBytes(data); - KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(), - data, null); - - // Write to a parcel - Parcel parcel = Parcel.obtain(); - ksm.writeToParcel(parcel, 0); - - // Read from it - parcel.setDataPosition(0); - KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel); - - // Verify that they are the same - assertEquals(ksm.getUuid(), unparceled.getUuid()); - assertEquals(ksm.getType(), unparceled.getType()); - assertNull(unparceled.getKeyphrases()); - assertTrue(Arrays.equals(ksm.getData(), unparceled.getData())); - } - - @SmallTest - public void testKeyphraseSoundModelParcelUnparcel_zeroKeyphrases() throws Exception { - byte[] data = new byte[10]; - mRandom.nextBytes(data); - KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(), - data, new Keyphrase[0]); - - // Write to a parcel - Parcel parcel = Parcel.obtain(); - ksm.writeToParcel(parcel, 0); - - // Read from it - parcel.setDataPosition(0); - KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel); - - // Verify that they are the same - assertEquals(ksm.getUuid(), unparceled.getUuid()); - assertEquals(ksm.getType(), unparceled.getType()); - assertTrue(Arrays.equals(ksm.getKeyphrases(), unparceled.getKeyphrases())); - assertTrue(Arrays.equals(ksm.getData(), unparceled.getData())); - } - - @LargeTest - public void testKeyphraseSoundModelParcelUnparcel_largeData() throws Exception { - Keyphrase[] keyphrases = new Keyphrase[2]; - keyphrases[0] = new Keyphrase(1, 0, Locale.forLanguageTag("en-US"), - "hello", new int[] {0}); - keyphrases[1] = new Keyphrase(2, 0, Locale.forLanguageTag("fr-FR"), - "there", new int[] {1, 2}); - byte[] data = new byte[200 * 1024]; - mRandom.nextBytes(data); - KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(), - data, keyphrases); - - // Write to a parcel - Parcel parcel = Parcel.obtain(); - ksm.writeToParcel(parcel, 0); - - // Read from it - parcel.setDataPosition(0); - KeyphraseSoundModel unparceled = KeyphraseSoundModel.CREATOR.createFromParcel(parcel); - - // Verify that they are the same - assertEquals(ksm.getUuid(), unparceled.getUuid()); - assertEquals(ksm.getType(), unparceled.getType()); - assertTrue(Arrays.equals(ksm.getData(), unparceled.getData())); - assertTrue(Arrays.equals(ksm.getKeyphrases(), unparceled.getKeyphrases())); - } - - @SmallTest - public void testRecognitionEventParcelUnparcel_noData() throws Exception { - RecognitionEvent re = new RecognitionEvent(SoundTrigger.RECOGNITION_STATUS_SUCCESS, 1, - true, 2, 3, 4, false, null, null); - - // Write to a parcel - Parcel parcel = Parcel.obtain(); - re.writeToParcel(parcel, 0); - - // Read from it - parcel.setDataPosition(0); - RecognitionEvent unparceled = RecognitionEvent.CREATOR.createFromParcel(parcel); - - // Verify that they are the same - assertEquals(re, unparceled); - } - - @SmallTest - public void testRecognitionEventParcelUnparcel_zeroData() throws Exception { - RecognitionEvent re = new RecognitionEvent(SoundTrigger.RECOGNITION_STATUS_FAILURE, 1, - true, 2, 3, 4, false, null, new byte[1]); - - // Write to a parcel - Parcel parcel = Parcel.obtain(); - re.writeToParcel(parcel, 0); - - // Read from it - parcel.setDataPosition(0); - RecognitionEvent unparceled = RecognitionEvent.CREATOR.createFromParcel(parcel); - - // Verify that they are the same - assertEquals(re, unparceled); - } - - @SmallTest - public void testRecognitionEventParcelUnparcel_largeData() throws Exception { - byte[] data = new byte[200 * 1024]; - mRandom.nextBytes(data); - RecognitionEvent re = new RecognitionEvent(SoundTrigger.RECOGNITION_STATUS_ABORT, 1, - false, 2, 3, 4, false, null, data); - - // Write to a parcel - Parcel parcel = Parcel.obtain(); - re.writeToParcel(parcel, 0); - - // Read from it - parcel.setDataPosition(0); - RecognitionEvent unparceled = RecognitionEvent.CREATOR.createFromParcel(parcel); - - // Verify that they are the same - assertEquals(re, unparceled); - } - - @SmallTest - public void testRecognitionEventParcelUnparcel_largeAudioData() throws Exception { - byte[] data = new byte[200 * 1024]; - mRandom.nextBytes(data); - RecognitionEvent re = new RecognitionEvent(SoundTrigger.RECOGNITION_STATUS_ABORT, 1, - false, 2, 3, 4, true, - (new AudioFormat.Builder()) - .setChannelMask(AudioFormat.CHANNEL_IN_MONO) - .setEncoding(AudioFormat.ENCODING_PCM_16BIT) - .setSampleRate(16000) - .build(), - data); - - // Write to a parcel - Parcel parcel = Parcel.obtain(); - re.writeToParcel(parcel, 0); - - // Read from it - parcel.setDataPosition(0); - RecognitionEvent unparceled = RecognitionEvent.CREATOR.createFromParcel(parcel); - - // Verify that they are the same - assertEquals(re, unparceled); - } - - @SmallTest - public void testKeyphraseRecognitionEventParcelUnparcel_noKeyphrases() throws Exception { - KeyphraseRecognitionEvent re = new KeyphraseRecognitionEvent( - SoundTrigger.RECOGNITION_STATUS_SUCCESS, 1, true, 2, 3, 4, false, null, null, null); - - // Write to a parcel - Parcel parcel = Parcel.obtain(); - re.writeToParcel(parcel, 0); - - // Read from it - parcel.setDataPosition(0); - KeyphraseRecognitionEvent unparceled = - KeyphraseRecognitionEvent.CREATOR.createFromParcel(parcel); - - // Verify that they are the same - assertEquals(re, unparceled); - } - - @SmallTest - public void testKeyphraseRecognitionEventParcelUnparcel_zeroData() throws Exception { - KeyphraseRecognitionExtra[] kpExtra = new KeyphraseRecognitionExtra[0]; - KeyphraseRecognitionEvent re = new KeyphraseRecognitionEvent( - SoundTrigger.RECOGNITION_STATUS_FAILURE, 2, true, 2, 3, 4, false, null, new byte[1], - kpExtra); - - // Write to a parcel - Parcel parcel = Parcel.obtain(); - re.writeToParcel(parcel, 0); - - // Read from it - parcel.setDataPosition(0); - KeyphraseRecognitionEvent unparceled = - KeyphraseRecognitionEvent.CREATOR.createFromParcel(parcel); - - // Verify that they are the same - assertEquals(re, unparceled); - } - - @LargeTest - public void testKeyphraseRecognitionEventParcelUnparcel_largeData() throws Exception { - byte[] data = new byte[200 * 1024]; - mRandom.nextBytes(data); - KeyphraseRecognitionExtra[] kpExtra = new KeyphraseRecognitionExtra[4]; - ConfidenceLevel cl1 = new ConfidenceLevel(1, 90); - ConfidenceLevel cl2 = new ConfidenceLevel(2, 30); - kpExtra[0] = new KeyphraseRecognitionExtra(1, - SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION, 0, - new ConfidenceLevel[] {cl1, cl2}); - kpExtra[1] = new KeyphraseRecognitionExtra(1, - SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER, 0, - new ConfidenceLevel[] {cl2}); - kpExtra[2] = new KeyphraseRecognitionExtra(1, - SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER, 0, null); - kpExtra[3] = new KeyphraseRecognitionExtra(1, - SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER, 0, - new ConfidenceLevel[0]); - - KeyphraseRecognitionEvent re = new KeyphraseRecognitionEvent( - SoundTrigger.RECOGNITION_STATUS_FAILURE, 1, true, 2, 3, 4, false, null, data, - kpExtra); - - // Write to a parcel - Parcel parcel = Parcel.obtain(); - re.writeToParcel(parcel, 0); - - // Read from it - parcel.setDataPosition(0); - KeyphraseRecognitionEvent unparceled = - KeyphraseRecognitionEvent.CREATOR.createFromParcel(parcel); - - // Verify that they are the same - assertEquals(re, unparceled); - } -} diff --git a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java b/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java deleted file mode 100644 index 2c3592c640bc..000000000000 --- a/tests/SoundTriggerTests/src/android/hardware/soundtrigger/stubhal/GenericSoundModelTest.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2014 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 android.hardware.soundtrigger; - -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.verify; - -import android.content.Context; -import android.hardware.soundtrigger.SoundTrigger.GenericRecognitionEvent; -import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; -import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; -import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; -import android.media.soundtrigger.SoundTriggerManager; -import android.os.ParcelUuid; -import android.os.ServiceManager; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.LargeTest; -import android.test.suitebuilder.annotation.SmallTest; - -import com.android.internal.app.ISoundTriggerService; - -import org.mockito.MockitoAnnotations; - -import java.io.DataOutputStream; -import java.net.InetAddress; -import java.net.Socket; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Random; -import java.util.UUID; - -public class GenericSoundModelTest extends AndroidTestCase { - static final int MSG_DETECTION_ERROR = -1; - static final int MSG_DETECTION_RESUME = 0; - static final int MSG_DETECTION_PAUSE = 1; - static final int MSG_KEYPHRASE_TRIGGER = 2; - static final int MSG_GENERIC_TRIGGER = 4; - - private Random random = new Random(); - private HashSet<UUID> loadedModelUuids; - private ISoundTriggerService soundTriggerService; - private SoundTriggerManager soundTriggerManager; - - @Override - public void setUp() throws Exception { - super.setUp(); - MockitoAnnotations.initMocks(this); - - Context context = getContext(); - soundTriggerService = ISoundTriggerService.Stub.asInterface( - ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE)); - soundTriggerManager = (SoundTriggerManager) context.getSystemService( - Context.SOUND_TRIGGER_SERVICE); - - loadedModelUuids = new HashSet<UUID>(); - } - - @Override - public void tearDown() throws Exception { - for (UUID modelUuid : loadedModelUuids) { - soundTriggerService.deleteSoundModel(new ParcelUuid(modelUuid)); - } - super.tearDown(); - } - - GenericSoundModel new_sound_model() { - // Create sound model - byte[] data = new byte[1024]; - random.nextBytes(data); - UUID modelUuid = UUID.randomUUID(); - UUID mVendorUuid = UUID.randomUUID(); - return new GenericSoundModel(modelUuid, mVendorUuid, data); - } - - @SmallTest - public void testUpdateGenericSoundModel() throws Exception { - GenericSoundModel model = new_sound_model(); - - // Update sound model - soundTriggerService.updateSoundModel(model); - loadedModelUuids.add(model.getUuid()); - - // Confirm it was updated - GenericSoundModel returnedModel = - soundTriggerService.getSoundModel(new ParcelUuid(model.getUuid())); - assertEquals(model, returnedModel); - } - - @SmallTest - public void testDeleteGenericSoundModel() throws Exception { - GenericSoundModel model = new_sound_model(); - - // Update sound model - soundTriggerService.updateSoundModel(model); - loadedModelUuids.add(model.getUuid()); - - // Delete sound model - soundTriggerService.deleteSoundModel(new ParcelUuid(model.getUuid())); - loadedModelUuids.remove(model.getUuid()); - - // Confirm it was deleted - GenericSoundModel returnedModel = - soundTriggerService.getSoundModel(new ParcelUuid(model.getUuid())); - assertEquals(null, returnedModel); - } - - @LargeTest - public void testStartStopGenericSoundModel() throws Exception { - GenericSoundModel model = new_sound_model(); - - boolean captureTriggerAudio = true; - boolean allowMultipleTriggers = true; - RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, - null, null); - TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback()); - - // Update and start sound model recognition - soundTriggerService.updateSoundModel(model); - loadedModelUuids.add(model.getUuid()); - int r = soundTriggerService.startRecognition(new ParcelUuid(model.getUuid()), spyCallback, - config); - assertEquals("Could Not Start Recognition with code: " + r, - android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); - - // Stop recognition - r = soundTriggerService.stopRecognition(new ParcelUuid(model.getUuid()), spyCallback); - assertEquals("Could Not Stop Recognition with code: " + r, - android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); - } - - @LargeTest - public void testTriggerGenericSoundModel() throws Exception { - GenericSoundModel model = new_sound_model(); - - boolean captureTriggerAudio = true; - boolean allowMultipleTriggers = true; - RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, - null, null); - TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback()); - - // Update and start sound model - soundTriggerService.updateSoundModel(model); - loadedModelUuids.add(model.getUuid()); - soundTriggerService.startRecognition(new ParcelUuid(model.getUuid()), spyCallback, config); - - // Send trigger to stub HAL - Socket socket = new Socket(InetAddress.getLocalHost(), 14035); - DataOutputStream out = new DataOutputStream(socket.getOutputStream()); - out.writeBytes("trig " + model.getUuid().toString() + "\r\n"); - out.flush(); - socket.close(); - - // Verify trigger was received - verify(spyCallback, timeout(100)).onGenericSoundTriggerDetected(any()); - } - - /** - * Tests a more complicated pattern of loading, unloading, triggering, starting and stopping - * recognition. Intended to find unexpected errors that occur in unexpected states. - */ - @LargeTest - public void testFuzzGenericSoundModel() throws Exception { - int numModels = 2; - - final int STATUS_UNLOADED = 0; - final int STATUS_LOADED = 1; - final int STATUS_STARTED = 2; - - class ModelInfo { - int status; - GenericSoundModel model; - - public ModelInfo(GenericSoundModel model, int status) { - this.status = status; - this.model = model; - } - } - - Random predictableRandom = new Random(100); - - ArrayList modelInfos = new ArrayList<ModelInfo>(); - for(int i=0; i<numModels; i++) { - // Create sound model - byte[] data = new byte[1024]; - predictableRandom.nextBytes(data); - UUID modelUuid = UUID.randomUUID(); - UUID mVendorUuid = UUID.randomUUID(); - GenericSoundModel model = new GenericSoundModel(modelUuid, mVendorUuid, data); - ModelInfo modelInfo = new ModelInfo(model, STATUS_UNLOADED); - modelInfos.add(modelInfo); - } - - boolean captureTriggerAudio = true; - boolean allowMultipleTriggers = true; - RecognitionConfig config = new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, - null, null); - TestRecognitionStatusCallback spyCallback = spy(new TestRecognitionStatusCallback()); - - - int numOperationsToRun = 100; - for(int i=0; i<numOperationsToRun; i++) { - // Select a random model - int modelInfoIndex = predictableRandom.nextInt(modelInfos.size()); - ModelInfo modelInfo = (ModelInfo) modelInfos.get(modelInfoIndex); - - // Perform a random operation - int operation = predictableRandom.nextInt(5); - - if (operation == 0 && modelInfo.status == STATUS_UNLOADED) { - // Update and start sound model - soundTriggerService.updateSoundModel(modelInfo.model); - loadedModelUuids.add(modelInfo.model.getUuid()); - modelInfo.status = STATUS_LOADED; - } else if (operation == 1 && modelInfo.status == STATUS_LOADED) { - // Start the sound model - int r = soundTriggerService.startRecognition(new ParcelUuid( - modelInfo.model.getUuid()), - spyCallback, config); - assertEquals("Could Not Start Recognition with code: " + r, - android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); - modelInfo.status = STATUS_STARTED; - } else if (operation == 2 && modelInfo.status == STATUS_STARTED) { - // Send trigger to stub HAL - Socket socket = new Socket(InetAddress.getLocalHost(), 14035); - DataOutputStream out = new DataOutputStream(socket.getOutputStream()); - out.writeBytes("trig " + modelInfo.model.getUuid() + "\r\n"); - out.flush(); - socket.close(); - - // Verify trigger was received - verify(spyCallback, timeout(100)).onGenericSoundTriggerDetected(any()); - reset(spyCallback); - } else if (operation == 3 && modelInfo.status == STATUS_STARTED) { - // Stop recognition - int r = soundTriggerService.stopRecognition(new ParcelUuid( - modelInfo.model.getUuid()), - spyCallback); - assertEquals("Could Not Stop Recognition with code: " + r, - android.hardware.soundtrigger.SoundTrigger.STATUS_OK, r); - modelInfo.status = STATUS_LOADED; - } else if (operation == 4 && modelInfo.status != STATUS_UNLOADED) { - // Delete sound model - soundTriggerService.deleteSoundModel(new ParcelUuid(modelInfo.model.getUuid())); - loadedModelUuids.remove(modelInfo.model.getUuid()); - - // Confirm it was deleted - GenericSoundModel returnedModel = soundTriggerService.getSoundModel( - new ParcelUuid(modelInfo.model.getUuid())); - assertEquals(null, returnedModel); - modelInfo.status = STATUS_UNLOADED; - } - } - } - - public class TestRecognitionStatusCallback extends IRecognitionStatusCallback.Stub { - @Override - public void onGenericSoundTriggerDetected(GenericRecognitionEvent recognitionEvent) { - } - - @Override - public void onKeyphraseDetected(KeyphraseRecognitionEvent recognitionEvent) { - } - - @Override - public void onError(int status) { - } - - @Override - public void onRecognitionPaused() { - } - - @Override - public void onRecognitionResumed() { - } - } -} diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp index cce0dde9e6b9..23efe548c82a 100644 --- a/tests/StagedInstallTest/Android.bp +++ b/tests/StagedInstallTest/Android.bp @@ -55,8 +55,11 @@ java_test_host { "cts-install-lib-host", ], data: [ + ":StagedInstallInternalTestApp", ":apex.apexd_test", ":com.android.apex.apkrollback.test_v1", + ":com.android.apex.apkrollback.test_v2", + ":StagedInstallInternalTestApp", ":StagedInstallTestApexV2", ":StagedInstallTestApexV2_WrongSha", ":TestAppAv1", diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java index 7490d3f2b50c..0375f66069c3 100644 --- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java @@ -66,6 +66,7 @@ import java.util.function.Consumer; @RunWith(JUnit4.class) public class StagedInstallInternalTest { private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; + private static final String REBOOTLESS_APEX_PACKAGE_NAME = "test.apex.rebootless"; private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2", APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex"); private static final TestApp APEX_WRONG_SHA_V2 = new TestApp( @@ -114,6 +115,63 @@ public class StagedInstallInternalTest { Uninstall.packages(TestApp.A, TestApp.B); } + private boolean isSystem(PackageInfo info) { + return (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + + private boolean isUpdatedSystem(PackageInfo info) { + return (info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; + } + + @Test + public void testUpdateSystemApp_InstallV2() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); + + PackageManager pm = + InstrumentationRegistry.getInstrumentation().getContext().getPackageManager(); + PackageInfo info; + // Check factory version + info = pm.getPackageInfo(TestApp.A, + PackageManager.PackageInfoFlags.of(PackageManager.MATCH_FACTORY_ONLY)); + assertThat(isSystem(info)).isTrue(); + assertThat(isUpdatedSystem(info)).isFalse(); + // Check active version + info = pm.getPackageInfo(TestApp.A, PackageManager.PackageInfoFlags.of(0)); + assertThat(isSystem(info)).isTrue(); + assertThat(isUpdatedSystem(info)).isFalse(); + + Install.single(TestApp.A2).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + + // Check factory version + info = pm.getPackageInfo(TestApp.A, + PackageManager.PackageInfoFlags.of(PackageManager.MATCH_FACTORY_ONLY)); + assertThat(isSystem(info)).isTrue(); + assertThat(isUpdatedSystem(info)).isFalse(); + // Check active version + info = pm.getPackageInfo(TestApp.A, PackageManager.PackageInfoFlags.of(0)); + assertThat(isSystem(info)).isTrue(); + assertThat(isUpdatedSystem(info)).isTrue(); + } + + @Test + public void testUpdateSystemApp_PostInstallV2() throws Exception { + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + + PackageManager pm = + InstrumentationRegistry.getInstrumentation().getContext().getPackageManager(); + PackageInfo info; + // Check factory version + info = pm.getPackageInfo(TestApp.A, + PackageManager.PackageInfoFlags.of(PackageManager.MATCH_FACTORY_ONLY)); + assertThat(isSystem(info)).isTrue(); + assertThat(isUpdatedSystem(info)).isFalse(); + // Check active version + info = pm.getPackageInfo(TestApp.A, PackageManager.PackageInfoFlags.of(0)); + assertThat(isSystem(info)).isTrue(); + assertThat(isUpdatedSystem(info)).isTrue(); + } + @Test public void testDuplicateApkInApexShouldFail_Commit() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); @@ -403,7 +461,8 @@ public class StagedInstallInternalTest { { PackageInfo apex = pm.getPackageInfo("test.apex.rebootless", PackageManager.MATCH_APEX); assertThat(apex.getLongVersionCode()).isEqualTo(1); - assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM).isEqualTo(0); + assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) + .isEqualTo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP); assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) .isEqualTo(ApplicationInfo.FLAG_INSTALLED); assertThat(apex.applicationInfo.sourceDir).startsWith("/data/apex/active"); @@ -414,7 +473,8 @@ public class StagedInstallInternalTest { assertThat(apex.getLongVersionCode()).isEqualTo(1); assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) .isEqualTo(ApplicationInfo.FLAG_SYSTEM); - assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED).isEqualTo(0); + assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) + .isEqualTo(ApplicationInfo.FLAG_INSTALLED); assertThat(apex.applicationInfo.sourceDir).startsWith("/system/apex"); } @@ -425,7 +485,8 @@ public class StagedInstallInternalTest { { PackageInfo apex = pm.getPackageInfo("test.apex.rebootless", PackageManager.MATCH_APEX); assertThat(apex.getLongVersionCode()).isEqualTo(2); - assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM).isEqualTo(0); + assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) + .isEqualTo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP); assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) .isEqualTo(ApplicationInfo.FLAG_INSTALLED); assertThat(apex.applicationInfo.sourceDir).startsWith("/data/apex/active"); @@ -436,7 +497,8 @@ public class StagedInstallInternalTest { assertThat(apex.getLongVersionCode()).isEqualTo(1); assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) .isEqualTo(ApplicationInfo.FLAG_SYSTEM); - assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED).isEqualTo(0); + assertThat(apex.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) + .isEqualTo(ApplicationInfo.FLAG_INSTALLED); assertThat(apex.applicationInfo.sourceDir).startsWith("/system/apex"); } } @@ -533,6 +595,28 @@ public class StagedInstallInternalTest { assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(1); } + @Test + public void testVendorApex_VerifyFactory() throws Exception { + final PackageManager pm = + InstrumentationRegistry.getInstrumentation().getContext().getPackageManager(); + PackageInfo pi = pm.getPackageInfo(REBOOTLESS_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX); + assertThat(pi.getLongVersionCode()).isEqualTo(1); + assertThat(pi.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) + .isEqualTo(ApplicationInfo.PRIVATE_FLAG_VENDOR); + assertThat(pi.applicationInfo.sourceDir).startsWith("/vendor/apex"); + } + + @Test + public void testVendorApex_VerifyData() throws Exception { + final PackageManager pm = + InstrumentationRegistry.getInstrumentation().getContext().getPackageManager(); + PackageInfo pi = pm.getPackageInfo(REBOOTLESS_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX); + assertThat(pi.getLongVersionCode()).isEqualTo(2); + assertThat(pi.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) + .isEqualTo(ApplicationInfo.PRIVATE_FLAG_VENDOR); + assertThat(pi.applicationInfo.sourceDir).startsWith("/data/apex"); + } + private IPackageManagerNative getPackageManagerNative() { IBinder binder = ServiceManager.waitForService("package_native"); assertThat(binder).isNotNull(); diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java index f60b4d6aad1e..f1fc503c8556 100644 --- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java @@ -31,6 +31,7 @@ import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; import com.android.ddmlib.Log; import com.android.tests.rollback.host.AbandonSessionsRule; import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.PackageInfo; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; import com.android.tradefed.util.CommandResult; @@ -63,6 +64,8 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; private static final String APEXD_TEST_APEX = "apex.apexd_test.apex"; private static final String FAKE_APEX_SYSTEM_SERVER_APEX = "test_com.android.server.apex"; + private static final String REBOOTLESS_V1 = "test.rebootless_apex_v1.apex"; + private static final String REBOOTLESS_V2 = "test.rebootless_apex_v2.apex"; private static final String TEST_VENDOR_APEX_ALLOW_LIST = "/vendor/etc/sysconfig/test-vendor-apex-allow-list.xml"; @@ -94,7 +97,9 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", "/data/apex/active/" + SHIM_APEX_PACKAGE_NAME + "*.apex", "/system/apex/test.rebootless_apex_v*.apex", + "/vendor/apex/test.rebootless_apex_v*.apex", "/data/apex/active/test.apex.rebootless*.apex", + "/system/app/TestApp/TestAppAv1.apk", TEST_VENDOR_APEX_ALLOW_LIST); } @@ -118,16 +123,27 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { } boolean found = false; + boolean remountSystem = false; + boolean remountVendor = false; for (String file : files) { CommandResult result = getDevice().executeShellV2Command("ls " + file); if (result.getStatus() == CommandStatus.SUCCESS) { found = true; - break; + if (file.startsWith("/system")) { + remountSystem = true; + } else if (file.startsWith("/vendor")) { + remountVendor = true; + } } } if (found) { - getDevice().remountSystemWritable(); + if (remountSystem) { + getDevice().remountSystemWritable(); + } + if (remountVendor) { + getDevice().remountVendorWritable(); + } for (String file : files) { getDevice().executeShellCommand("rm -rf " + file); } @@ -136,20 +152,34 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { } private void pushTestApex(String fileName) throws Exception { + pushTestApex(fileName, "system"); + } + + private void pushTestApex(String fileName, String partition) throws Exception { CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild()); final File apex = buildHelper.getTestFile(fileName); if (!getDevice().isAdbRoot()) { getDevice().enableAdbRoot(); } - getDevice().remountSystemWritable(); - assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName)); + if ("system".equals(partition)) { + getDevice().remountSystemWritable(); + } else if ("vendor".equals(partition)) { + getDevice().remountVendorWritable(); + } + assertTrue(getDevice().pushFile(apex, "/" + partition + "/apex/" + fileName)); + } + + private void installTestApex(String fileName, String... extraArgs) throws Exception { + CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild()); + final File apex = buildHelper.getTestFile(fileName); + getDevice().installPackage(apex, false, extraArgs); } private void pushTestVendorApexAllowList(String installerPackageName) throws Exception { if (!getDevice().isAdbRoot()) { getDevice().enableAdbRoot(); } - getDevice().remountSystemWritable(); + getDevice().remountVendorWritable(); File file = File.createTempFile("test-vendor-apex-allow-list", ".xml"); try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { final String fmt = @@ -163,6 +193,25 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { } /** + * Tests app info flags are set correctly when updating a system app. + */ + @Test + public void testUpdateSystemApp() throws Exception { + // Push TestAppAv1.apk to /system + final File apkFile = mHostUtils.getTestFile(APK_A); + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + getDevice().remountSystemWritable(); + assertTrue(getDevice().pushFile(apkFile, "/system/app/TestApp/" + apkFile.getName())); + getDevice().reboot(); + + runPhase("testUpdateSystemApp_InstallV2"); + getDevice().reboot(); + runPhase("testUpdateSystemApp_PostInstallV2"); + } + + /** * Tests that duplicate packages in apk-in-apex and apk should fail to install. */ @Test @@ -176,6 +225,28 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { runPhase("testDuplicateApkInApexShouldFail_Verify"); } + /** + * Tests the cache of apk-in-apex is pruned and rebuilt correctly. + */ + @Test + @LargeTest + public void testApkInApexPruneCache() throws Exception { + final String apkInApexPackageName = "com.android.cts.install.lib.testapp.A"; + + pushTestApex(APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"); + getDevice().reboot(); + PackageInfo pi = getDevice().getAppPackageInfo(apkInApexPackageName); + assertThat(Integer.parseInt(pi.getVersionCode())).isEqualTo(1); + assertThat(pi.getCodePath()).startsWith("/apex/" + APK_IN_APEX_TESTAPEX_NAME); + + installPackage(APK_IN_APEX_TESTAPEX_NAME + "_v2.apex", "--staged"); + getDevice().reboot(); + pi = getDevice().getAppPackageInfo(apkInApexPackageName); + // The version code of apk-in-apex will be stale if the cache is not rebuilt + assertThat(Integer.parseInt(pi.getVersionCode())).isEqualTo(2); + assertThat(pi.getCodePath()).startsWith("/apex/" + APK_IN_APEX_TESTAPEX_NAME); + } + @Test public void testSystemServerRestartDoesNotAffectStagedSessions() throws Exception { runPhase("testSystemServerRestartDoesNotAffectStagedSessions_Commit"); @@ -477,6 +548,33 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { runPhase("testVendorApexCorrectInstaller_nonStaged"); } + /** + * Tests correctness of {@link android.content.pm.ApplicationInfo} for APEXes on /vendor. + */ + @Test + @LargeTest + public void testVendorApex_Staged() throws Exception { + pushTestApex(REBOOTLESS_V1, "vendor"); + getDevice().reboot(); + runPhase("testVendorApex_VerifyFactory"); + installTestApex(REBOOTLESS_V2, "--staged"); + getDevice().reboot(); + runPhase("testVendorApex_VerifyData"); + } + + /** + * Tests correctness of {@link android.content.pm.ApplicationInfo} for APEXes on /vendor. + */ + @Test + @LargeTest + public void testVendorApex_NonStaged() throws Exception { + pushTestApex(REBOOTLESS_V1, "vendor"); + getDevice().reboot(); + runPhase("testVendorApex_VerifyFactory"); + installTestApex(REBOOTLESS_V2, "--force-non-staged"); + runPhase("testVendorApex_VerifyData"); + } + @Test public void testRebootlessUpdates() throws Exception { pushTestApex("test.rebootless_apex_v1.apex"); diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java index ef324e7c1377..6c89e49a0e6e 100644 --- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java +++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java @@ -1156,12 +1156,12 @@ public class NotificationTestList extends TestActivity private PendingIntent makeIntent() { Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_HOME); - return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED); + return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE); } private PendingIntent makeIntent2() { Intent intent = new Intent(this, StatusBarTest.class); - return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED); + return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE); } diff --git a/tests/SurfaceControlViewHostTest/Android.bp b/tests/SurfaceControlViewHostTest/Android.bp index 0127ba559500..99567b9a10b4 100644 --- a/tests/SurfaceControlViewHostTest/Android.bp +++ b/tests/SurfaceControlViewHostTest/Android.bp @@ -25,7 +25,10 @@ package { android_test { name: "SurfaceControlViewHostTest", - srcs: ["**/*.java"], + srcs: [ + "**/*.aidl", + "**/*.java", + ], platform_apis: true, certificate: "platform", } diff --git a/tests/SurfaceControlViewHostTest/AndroidManifest.xml b/tests/SurfaceControlViewHostTest/AndroidManifest.xml index 7e9a04dfa82c..e50cbc52a5b8 100644 --- a/tests/SurfaceControlViewHostTest/AndroidManifest.xml +++ b/tests/SurfaceControlViewHostTest/AndroidManifest.xml @@ -24,6 +24,16 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity android:name="SurfaceControlViewHostSyncTest" + android:label="View Embedding Test Sync" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <service android:name=".EmbeddedWindowService" + android:process="com.android.test.viewembed.embedded_process"/> </application> diff --git a/tests/SurfaceControlViewHostTest/OWNERS b/tests/SurfaceControlViewHostTest/OWNERS new file mode 100644 index 000000000000..0862c05e0ee4 --- /dev/null +++ b/tests/SurfaceControlViewHostTest/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/wm/OWNERS diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java new file mode 100644 index 000000000000..abc15b49ad98 --- /dev/null +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java @@ -0,0 +1,109 @@ +/* + * 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.android.test.viewembed; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; + +import android.annotation.Nullable; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.hardware.display.DisplayManager; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.view.Display; +import android.view.Gravity; +import android.view.SurfaceControlViewHost; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.TextView; + +public class EmbeddedWindowService extends Service { + private static final String TAG = "EmbeddedWindowService"; + private SurfaceControlViewHost mVr; + + private Handler mHandler; + + @Override + public void onCreate() { + super.onCreate(); + mHandler = new Handler(Looper.getMainLooper()); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + // Return the interface + return new AttachEmbeddedWindow(); + } + + public static class SlowView extends TextView { + + public SlowView(Context context) { + super(context); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + try { + Thread.sleep(250); + } catch (InterruptedException e) { + } + } + } + + private class AttachEmbeddedWindow extends IAttachEmbeddedWindow.Stub { + @Override + public void attachEmbedded(IBinder hostToken, int width, int height, + IAttachEmbeddedWindowCallback callback) { + mHandler.post(() -> { + Context context = EmbeddedWindowService.this; + Display display = getApplicationContext().getSystemService( + DisplayManager.class).getDisplay(DEFAULT_DISPLAY); + mVr = new SurfaceControlViewHost(context, display, hostToken); + FrameLayout content = new FrameLayout(context); + + SlowView slowView = new SlowView(context); + slowView.setText("INSIDE TEXT"); + slowView.setGravity(Gravity.CENTER); + slowView.setTextColor(Color.BLACK); + slowView.setBackgroundColor(Color.CYAN); + content.addView(slowView); + WindowManager.LayoutParams lp = + new WindowManager.LayoutParams(width, height, TYPE_APPLICATION, + 0, PixelFormat.OPAQUE); + lp.setTitle("EmbeddedWindow"); + + mVr.setView(content, lp); + try { + callback.onEmbeddedWindowAttached(mVr.getSurfacePackage()); + } catch (RemoteException e) { + } + }); + } + @Override + public void relayout(WindowManager.LayoutParams lp) { + mHandler.post(() -> mVr.relayout(lp)); + } + } +} diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl new file mode 100644 index 000000000000..9e9faf03ba1c --- /dev/null +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl @@ -0,0 +1,26 @@ +/* + * 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.android.test.viewembed; + +import android.os.IBinder; +import com.android.test.viewembed.IAttachEmbeddedWindowCallback; +import android.view.WindowManager.LayoutParams; + +interface IAttachEmbeddedWindow { + void attachEmbedded(IBinder hostToken, int width, int height, in IAttachEmbeddedWindowCallback callback); + void relayout(in LayoutParams lp); +}
\ No newline at end of file diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindowCallback.aidl index 5ae55215f696..c45c24d4b872 100644 --- a/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindowCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -13,7 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.content.componentalias.tests.b; -public class Target03 extends BaseReceiver { -} +package com.android.test.viewembed; + +import android.view.SurfaceControlViewHost.SurfacePackage; + +interface IAttachEmbeddedWindowCallback { + void onEmbeddedWindowAttached(in SurfacePackage surfacePackage); +}
\ No newline at end of file diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java new file mode 100644 index 000000000000..359eb35384c7 --- /dev/null +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java @@ -0,0 +1,215 @@ +/* + * 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.android.test.viewembed; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.Gravity; +import android.view.SurfaceControlViewHost.SurfacePackage; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.Switch; +import android.window.SurfaceSyncGroup; + +public class SurfaceControlViewHostSyncTest extends Activity implements SurfaceHolder.Callback { + private static final String TAG = "SurfaceControlViewHostSyncTest"; + private SurfaceView mSv; + + private final Object mLock = new Object(); + private boolean mIsAttached; + private boolean mSurfaceCreated; + + private IAttachEmbeddedWindow mIAttachEmbeddedWindow; + private SurfacePackage mSurfacePackage; + + private final Point[] mSizes = new Point[]{new Point(500, 500), new Point(700, 400), + new Point(300, 800), new Point(200, 200)}; + private int mLastSizeIndex = 0; + + private boolean mSync = true; + + private final ServiceConnection mConnection = new ServiceConnection() { + // Called when the connection with the service is established + public void onServiceConnected(ComponentName className, IBinder service) { + Log.d(TAG, "Service Connected"); + synchronized (mLock) { + mIAttachEmbeddedWindow = IAttachEmbeddedWindow.Stub.asInterface(service); + } + loadEmbedded(); + } + + public void onServiceDisconnected(ComponentName className) { + Log.d(TAG, "Service Disconnected"); + mIAttachEmbeddedWindow = null; + } + }; + + protected void onCreate(Bundle savedInstanceState) { + FrameLayout content = new FrameLayout(this); + super.onCreate(savedInstanceState); + mSv = new SurfaceView(this); + Button button = new Button(this); + Switch enableSyncButton = new Switch(this); + content.addView(mSv, new FrameLayout.LayoutParams( + mSizes[0].x, mSizes[0].y, Gravity.CENTER_HORIZONTAL | Gravity.TOP)); + content.addView(button, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM)); + content.addView(enableSyncButton, + new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.RIGHT | Gravity.BOTTOM)); + setContentView(content); + + mSv.setZOrderOnTop(false); + mSv.getHolder().addCallback(this); + + button.setText("Change Size"); + enableSyncButton.setText("Enable Sync"); + enableSyncButton.setChecked(true); + button.setOnClickListener(v -> { + resize(); + }); + + enableSyncButton.setOnCheckedChangeListener((buttonView, isChecked) -> { + mSync = isChecked; + }); + + Intent intent = new Intent(this, EmbeddedWindowService.class); + intent.setAction(IAttachEmbeddedWindow.class.getName()); + Log.d(TAG, "bindService"); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + } + + private void resize() { + if (mSurfacePackage == null) { + return; + } + Point size = mSizes[mLastSizeIndex % mSizes.length]; + + Runnable svResizeRunnable = () -> { + mSv.getLayoutParams().width = size.x; + mSv.getLayoutParams().height = size.y; + mSv.requestLayout(); + }; + + Runnable resizeRunnable = () -> { + try { + final WindowManager.LayoutParams lp = + new WindowManager.LayoutParams(size.x, size.y, + WindowManager.LayoutParams.TYPE_APPLICATION, 0, + PixelFormat.TRANSPARENT); + mIAttachEmbeddedWindow.relayout(lp); + } catch (RemoteException e) { + } + }; + + if (mSync) { + SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG); + syncGroup.add(getWindow().getRootSurfaceControl(), svResizeRunnable); + syncGroup.add(mSurfacePackage, resizeRunnable); + syncGroup.markSyncReady(); + } else { + svResizeRunnable.run(); + resizeRunnable.run(); + } + + mLastSizeIndex++; + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + synchronized (mLock) { + mSurfaceCreated = true; + } + attachEmbedded(); + } + + private boolean isReadyToAttach() { + synchronized (mLock) { + if (!mSurfaceCreated) { + Log.d(TAG, "surface is not created"); + } + if (mIAttachEmbeddedWindow == null) { + Log.d(TAG, "Service is not attached"); + } + if (mIsAttached) { + Log.d(TAG, "Already attached"); + } + + return mSurfaceCreated && mIAttachEmbeddedWindow != null && !mIsAttached + && mSurfacePackage != null; + } + } + + private void loadEmbedded() { + try { + mIAttachEmbeddedWindow.attachEmbedded(mSv.getHostToken(), mSizes[0].x, mSizes[0].y, + new IAttachEmbeddedWindowCallback.Stub() { + @Override + public void onEmbeddedWindowAttached(SurfacePackage surfacePackage) { + getMainThreadHandler().post(() -> { + mSurfacePackage = surfacePackage; + attachEmbedded(); + }); + } + }); + mLastSizeIndex++; + } catch (RemoteException e) { + } + } + + private void attachEmbedded() { + if (!isReadyToAttach()) { + return; + } + + synchronized (mLock) { + mIsAttached = true; + } + mSv.setChildSurfacePackage(mSurfacePackage); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + synchronized (mLock) { + mSurfaceCreated = false; + } + } + + @Override + protected void onStart() { + super.onStart(); + Log.d(TAG, "onStart"); + resize(); + } +} diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt index b67dc380efab..97398dc4e334 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt @@ -15,7 +15,7 @@ */ package com.android.test -import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat +import android.tools.common.flicker.subject.layers.LayersTraceSubject import junit.framework.Assert.assertEquals import junit.framework.Assert.assertTrue import org.junit.Test @@ -37,7 +37,7 @@ class BufferPresentationTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase 1000 /* ms */)) } - assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames) + LayersTraceSubject(trace).hasFrameSequence("SurfaceView", 1..numFrames) } @Test @@ -51,7 +51,7 @@ class BufferPresentationTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase assertEquals(0, activity.mSurfaceProxy.waitUntilBufferDisplayed(2, 5000 /* ms */)) } - assertThat(trace).hasFrameSequence("SurfaceView", 1..2L) + LayersTraceSubject(trace).hasFrameSequence("SurfaceView", 1..2L) } @Test @@ -69,7 +69,7 @@ class BufferPresentationTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase 5000 /* ms */)) } - assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames) + LayersTraceSubject(trace).hasFrameSequence("SurfaceView", 1..numFrames) } @Test @@ -92,7 +92,7 @@ class BufferPresentationTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase 5000 /* ms */)) } - assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames) + LayersTraceSubject(trace).hasFrameSequence("SurfaceView", 1..numFrames) } @Test @@ -170,4 +170,4 @@ class BufferPresentationTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase assertTrue(failures) } } -}
\ No newline at end of file +} diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt index e9e0246a207d..0cc18d657cf5 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/BufferRejectionTests.kt @@ -16,10 +16,11 @@ package com.android.test import android.graphics.Point -import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat +import android.tools.common.flicker.subject.layers.LayersTraceSubject import com.android.test.SurfaceViewBufferTestBase.Companion.ScalingMode import com.android.test.SurfaceViewBufferTestBase.Companion.Transform import junit.framework.Assert.assertEquals +import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -45,10 +46,11 @@ class BufferRejectionTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(us activity.mSurfaceProxy.waitUntilBufferDisplayed(3, 500 /* ms */) } // Verify we reject buffers since scaling mode == NATIVE_WINDOW_SCALING_MODE_FREEZE - assertThat(trace).layer("SurfaceView", 2).doesNotExist() - + Assert.assertThrows(AssertionError::class.java) { + LayersTraceSubject(trace).layer("SurfaceView", 2) + } // Verify the next buffer is submitted with the correct size - assertThat(trace).layer("SurfaceView", 3).also { + LayersTraceSubject(trace).layer("SurfaceView", 3).also { it.hasBufferSize(defaultBufferSize) // scaling mode is not passed down to the layer for blast if (useBlastAdapter) { @@ -81,9 +83,11 @@ class BufferRejectionTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(us } // verify buffer size is reset to default buffer size - assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) - assertThat(trace).layer("SurfaceView", 2).doesNotExist() - assertThat(trace).layer("SurfaceView", 3).hasBufferSize(bufferSize) + LayersTraceSubject(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) + Assert.assertThrows(AssertionError::class.java) { + LayersTraceSubject(trace).layer("SurfaceView", 2) + } + LayersTraceSubject(trace).layer("SurfaceView", 3).hasBufferSize(bufferSize) } @Test @@ -109,10 +113,13 @@ class BufferRejectionTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(us } // verify buffer size is reset to default buffer size - assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) - assertThat(trace).layer("SurfaceView", 2).doesNotExist() - assertThat(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize) - assertThat(trace).layer("SurfaceView", 3).hasBufferOrientation(Transform.ROT_90.value) + LayersTraceSubject(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) + Assert.assertThrows(AssertionError::class.java) { + LayersTraceSubject(trace).layer("SurfaceView", 2) + } + LayersTraceSubject(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize) + LayersTraceSubject(trace).layer("SurfaceView", 3) + .hasBufferOrientation(Transform.ROT_90.value) } @Test @@ -141,12 +148,13 @@ class BufferRejectionTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(us } for (count in 0 until 5) { - assertThat(trace).layer("SurfaceView", (count * 3) + 1L) + LayersTraceSubject(trace).layer("SurfaceView", (count * 3) + 1L) .hasBufferSize(defaultBufferSize) - assertThat(trace).layer("SurfaceView", (count * 3) + 2L) - .doesNotExist() - assertThat(trace).layer("SurfaceView", (count * 3) + 3L) + Assert.assertThrows(AssertionError::class.java) { + LayersTraceSubject(trace).layer("SurfaceView", (count * 3) + 2L) + } + LayersTraceSubject(trace).layer("SurfaceView", (count * 3) + 3L) .hasBufferSize(bufferSize) } } -}
\ No newline at end of file +} diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt index 0802990beeab..6f4d11c3aa1b 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/GeometryTests.kt @@ -19,11 +19,12 @@ import android.graphics.Color import android.graphics.Point import android.graphics.Rect import android.os.SystemClock -import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat +import android.tools.common.flicker.subject.layers.LayersTraceSubject import com.android.test.SurfaceViewBufferTestBase.Companion.ScalingMode import com.android.test.SurfaceViewBufferTestBase.Companion.Transform import junit.framework.Assert.assertEquals import junit.framework.Assert.assertTrue +import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -43,7 +44,7 @@ class GeometryTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastA } // verify buffer size is reset to default buffer size - assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) + LayersTraceSubject(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) } @Test @@ -56,7 +57,7 @@ class GeometryTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastA activity.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */) } - assertThat(trace).layer("SurfaceView", 1).also { + LayersTraceSubject(trace).layer("SurfaceView", 1).also { it.hasBufferSize(bufferSize) it.hasLayerSize(defaultBufferSize) it.hasScalingMode(ScalingMode.SCALE_TO_WINDOW.ordinal) @@ -73,7 +74,7 @@ class GeometryTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastA activity.mSurfaceProxy.waitUntilBufferDisplayed(1, 500 /* ms */) } - assertThat(trace).layer("SurfaceView", 1).also { + LayersTraceSubject(trace).layer("SurfaceView", 1).also { it.hasBufferSize(bufferSize) it.hasLayerSize(defaultBufferSize) it.hasScalingMode(ScalingMode.SCALE_TO_WINDOW.ordinal) @@ -102,9 +103,11 @@ class GeometryTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastA } // verify buffer size is reset to default buffer size - assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) - assertThat(trace).layer("SurfaceView", 2).doesNotExist() - assertThat(trace).layer("SurfaceView", 3).hasBufferSize(bufferSize) + LayersTraceSubject(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) + Assert.assertThrows(AssertionError::class.java) { + LayersTraceSubject(trace).layer("SurfaceView", 2) + } + LayersTraceSubject(trace).layer("SurfaceView", 3).hasBufferSize(bufferSize) } @Test @@ -118,7 +121,7 @@ class GeometryTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastA activity.mSurfaceProxy.waitUntilBufferDisplayed(index + 1L, 500 /* ms */) } - assertThat(trace).layer("SurfaceView", index + 1L).also { + LayersTraceSubject(trace).layer("SurfaceView", index + 1L).also { it.hasBufferSize(defaultBufferSize) it.hasLayerSize(defaultBufferSize) it.hasBufferOrientation(transform.value) @@ -145,7 +148,7 @@ class GeometryTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastA } // check that the layer and buffer starts with the default size - assertThat(trace).layer("SurfaceView", 1).also { + LayersTraceSubject(trace).layer("SurfaceView", 1).also { it.hasBufferSize(defaultBufferSize) it.hasLayerSize(defaultBufferSize) } @@ -169,7 +172,7 @@ class GeometryTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastA checkPixels(svBounds, Color.BLUE) } - assertThat(trace).layer("SurfaceView", 1).also { + LayersTraceSubject(trace).layer("SurfaceView", 1).also { it.hasLayerSize(newSize) it.hasBufferSize(defaultBufferSize) } @@ -193,7 +196,7 @@ class GeometryTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastA } // check that the layer and buffer starts with the default size - assertThat(trace).layer("SurfaceView", 1).also { + LayersTraceSubject(trace).layer("SurfaceView", 1).also { it.hasBufferSize(defaultBufferSize) it.hasLayerSize(defaultBufferSize) } @@ -216,9 +219,9 @@ class GeometryTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(useBlastA checkPixels(svBounds, Color.BLUE) } - assertThat(trace).layer("SurfaceView", 1).also { + LayersTraceSubject(trace).layer("SurfaceView", 1).also { it.hasLayerSize(defaultBufferSize) it.hasBufferSize(defaultBufferSize) } } -}
\ No newline at end of file +} diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt index 69012bdd7d7c..e722ba537a8e 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/InverseDisplayTransformTests.kt @@ -16,9 +16,10 @@ package com.android.test import android.graphics.Point -import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat +import android.tools.common.flicker.subject.layers.LayersTraceSubject import com.android.test.SurfaceViewBufferTestBase.Companion.Transform import junit.framework.Assert.assertEquals +import org.junit.Assert import org.junit.Assume.assumeFalse import org.junit.Before import org.junit.Test @@ -69,8 +70,10 @@ class InverseDisplayTransformTests(useBlastAdapter: Boolean) : } // verify buffer size is reset to default buffer size - assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) - assertThat(trace).layer("SurfaceView", 2).doesNotExist() - assertThat(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize) + LayersTraceSubject(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize) + Assert.assertThrows(AssertionError::class.java) { + LayersTraceSubject(trace).layer("SurfaceView", 2) + } + LayersTraceSubject(trace).layer("SurfaceView", 3).hasBufferSize(rotatedBufferSize) } -}
\ No newline at end of file +} diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt index 996a1d3d79da..7378476554ec 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeScreenRecordTests.kt @@ -18,7 +18,6 @@ package com.android.test import android.graphics.Color import android.graphics.Rect import android.os.SystemClock -import android.view.cts.surfacevalidator.PixelColor import junit.framework.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test @@ -53,7 +52,7 @@ class SharedBufferModeScreenRecordTests(useBlastAdapter: Boolean) : SystemClock.sleep(4000) } - val result = withScreenRecording(svBounds, PixelColor.RED) { + val result = withScreenRecording(svBounds, Color.RED) { it.mSurfaceProxy.drawBuffer(0, Color.RED) } val failRatio = 1.0f * result.failFrames / (result.failFrames + result.passFrames) diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt index ee41d7941470..be3ed715d4e2 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/SharedBufferModeTests.kt @@ -17,7 +17,7 @@ package com.android.test import android.graphics.Color import android.graphics.Rect -import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat +import android.tools.common.flicker.subject.layers.LayersTraceSubject import junit.framework.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith @@ -39,7 +39,7 @@ class SharedBufferModeTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(u } } - assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames) + LayersTraceSubject(trace).hasFrameSequence("SurfaceView", 1..numFrames) } /** Submit buffers as fast as possible testing that we are not blocked when dequeuing the buffer @@ -57,7 +57,7 @@ class SharedBufferModeTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(u 5000 /* ms */)) } - assertThat(trace).hasFrameSequence("SurfaceView", numFrames..numFrames) + LayersTraceSubject(trace).hasFrameSequence("SurfaceView", numFrames..numFrames) } /** Keep overwriting the buffer without queuing buffers and check that we present the latest @@ -87,4 +87,4 @@ class SharedBufferModeTests(useBlastAdapter: Boolean) : SurfaceTracingTestBase(u checkPixels(svBounds, Color.BLUE) } } -}
\ No newline at end of file +} diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt index 6383da5a0a98..cf4cb8c97ea1 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt @@ -21,9 +21,11 @@ import android.graphics.Color import android.graphics.Rect import android.util.Log import androidx.test.ext.junit.rules.ActivityScenarioRule -import com.android.server.wm.flicker.monitor.LayersTraceMonitor -import com.android.server.wm.flicker.monitor.withSFTracing -import com.android.server.wm.traces.common.layers.LayersTrace +import android.tools.common.flicker.subject.layers.LayerSubject +import android.tools.common.traces.surfaceflinger.LayersTrace +import android.tools.device.traces.io.ResultWriter +import android.tools.device.traces.monitors.surfaceflinger.LayersTraceMonitor +import android.tools.device.traces.monitors.withSFTracing import junit.framework.Assert import org.junit.After import org.junit.Before @@ -52,8 +54,7 @@ open class SurfaceTracingTestBase(useBlastAdapter: Boolean) : } fun withTrace(predicate: (it: MainActivity) -> Unit): LayersTrace { - return withSFTracing(TRACE_FLAGS, - outputDir = instrumentation.targetContext.dataDir.toPath()) { + return withSFTracing(TRACE_FLAGS) { scenarioRule.getScenario().onActivity { predicate(it) } @@ -61,8 +62,7 @@ open class SurfaceTracingTestBase(useBlastAdapter: Boolean) : } fun withTrace(predicate: () -> Unit): LayersTrace { - return withSFTracing(TRACE_FLAGS, - outputDir = instrumentation.targetContext.dataDir.toPath()) { + return withSFTracing(TRACE_FLAGS) { predicate() } } @@ -84,8 +84,7 @@ open class SurfaceTracingTestBase(useBlastAdapter: Boolean) : } private fun stopLayerTrace() { - val tmpDir = instrumentation.targetContext.dataDir.toPath() - LayersTraceMonitor(tmpDir).stop() + LayersTraceMonitor().stop(ResultWriter()) } fun checkPixels(bounds: Rect, @ColorInt color: Int) { @@ -117,4 +116,4 @@ open class SurfaceTracingTestBase(useBlastAdapter: Boolean) : private const val TRACE_FLAGS = (1 shl 0) or (1 shl 5) or (1 shl 6) // TRACE_CRITICAL | TRACE_BUFFERS | TRACE_SYNC } -}
\ No newline at end of file +} diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt index 093c3125f253..bba967815ba5 100644 --- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt +++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceViewBufferTestBase.kt @@ -18,6 +18,8 @@ package com.android.test import android.app.Instrumentation import android.graphics.Point import android.provider.Settings +import android.tools.common.datatypes.Size +import android.tools.common.flicker.subject.layers.LayerSubject import androidx.test.InstrumentationRegistry import org.junit.After import org.junit.Before @@ -69,6 +71,10 @@ open class SurfaceViewBufferTestBase(val useBlastAdapter: Boolean) { const val R8G8B8A8_UNORM = 1 val defaultBufferSize = Point(640, 480) + fun LayerSubject.hasBufferSize(point: Point) = hasBufferSize(Size.from(point.x, point.y)) + + fun LayerSubject.hasLayerSize(point: Point) = hasLayerSize(Size.from(point.x, point.y)) + // system/window.h definitions enum class ScalingMode() { FREEZE, // = 0 @@ -94,4 +100,4 @@ open class SurfaceViewBufferTestBase(val useBlastAdapter: Boolean) { INVERSE_DISPLAY(0x08) } } -}
\ No newline at end of file +} diff --git a/tests/SurfaceViewSyncTest/src/com/android/test/SurfaceViewSyncActivity.java b/tests/SurfaceViewSyncTest/src/com/android/test/SurfaceViewSyncActivity.java index ab7f24a8d326..d1f2112f649d 100644 --- a/tests/SurfaceViewSyncTest/src/com/android/test/SurfaceViewSyncActivity.java +++ b/tests/SurfaceViewSyncTest/src/com/android/test/SurfaceViewSyncActivity.java @@ -35,11 +35,11 @@ import android.view.WindowMetrics; import android.widget.Button; import android.widget.LinearLayout; import android.widget.Switch; -import android.window.SurfaceSyncer; +import android.window.SurfaceSyncGroup; /** * Test app that allows the user to resize the SurfaceView and have the new buffer sync with the - * main window. This tests that {@link SurfaceSyncer} is working correctly. + * main window. This tests that {@link SurfaceSyncGroup} is working correctly. */ public class SurfaceViewSyncActivity extends Activity implements SurfaceHolder.Callback { private static final String TAG = "SurfaceViewSyncActivity"; @@ -49,12 +49,10 @@ public class SurfaceViewSyncActivity extends Activity implements SurfaceHolder.C private RenderingThread mRenderingThread; - private final SurfaceSyncer mSurfaceSyncer = new SurfaceSyncer(); - private Button mExpandButton; private Switch mEnableSyncSwitch; - private int mLastSyncId = -1; + private SurfaceSyncGroup mSyncGroup; @Override protected void onCreate(Bundle savedInstanceState) { @@ -76,7 +74,7 @@ public class SurfaceViewSyncActivity extends Activity implements SurfaceHolder.C } private void updateSurfaceViewSize(Rect bounds, View container) { - if (mLastSyncId >= 0) { + if (mSyncGroup != null) { return; } @@ -91,8 +89,8 @@ public class SurfaceViewSyncActivity extends Activity implements SurfaceHolder.C mLastExpanded = !mLastExpanded; if (mEnableSyncSwitch.isChecked()) { - mLastSyncId = mSurfaceSyncer.setupSync(() -> { }); - mSurfaceSyncer.addToSync(mLastSyncId, container); + mSyncGroup = new SurfaceSyncGroup(TAG); + mSyncGroup.add(container.getRootSurfaceControl(), null /* runnable */); } ViewGroup.LayoutParams svParams = mSurfaceView.getLayoutParams(); @@ -112,14 +110,14 @@ public class SurfaceViewSyncActivity extends Activity implements SurfaceHolder.C @Override public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) { if (mEnableSyncSwitch.isChecked()) { - if (mLastSyncId < 0) { + if (mSyncGroup == null) { mRenderingThread.renderFrame(null, width, height); return; } - mSurfaceSyncer.addToSync(mLastSyncId, mSurfaceView, frameCallback -> + mSyncGroup.add(mSurfaceView, frameCallback -> mRenderingThread.renderFrame(frameCallback, width, height)); - mSurfaceSyncer.markSyncReady(mLastSyncId); - mLastSyncId = -1; + mSyncGroup.markSyncReady(); + mSyncGroup = null; } else { mRenderingThread.renderFrame(null, width, height); } @@ -133,7 +131,7 @@ public class SurfaceViewSyncActivity extends Activity implements SurfaceHolder.C private static class RenderingThread extends HandlerThread { private final SurfaceHolder mSurfaceHolder; private Handler mHandler; - private SurfaceSyncer.SurfaceViewFrameCallback mFrameCallback; + private SurfaceSyncGroup.SurfaceViewFrameCallback mFrameCallback; private final Point mSurfaceSize = new Point(); int mColorValue = 0; @@ -147,7 +145,7 @@ public class SurfaceViewSyncActivity extends Activity implements SurfaceHolder.C mPaint.setTextSize(100); } - public void renderFrame(SurfaceSyncer.SurfaceViewFrameCallback frameCallback, int width, + public void renderFrame(SurfaceSyncGroup.SurfaceViewFrameCallback frameCallback, int width, int height) { if (mHandler != null) { mHandler.post(() -> { diff --git a/tests/SystemMemoryTest/host/Android.bp b/tests/SystemMemoryTest/host/Android.bp index 79744625b752..cc8bc45a7411 100644 --- a/tests/SystemMemoryTest/host/Android.bp +++ b/tests/SystemMemoryTest/host/Android.bp @@ -26,4 +26,7 @@ java_test_host { srcs: ["src/**/*.java"], libs: ["tradefed"], test_suites: ["general-tests"], + data: [ + ":SystemMemoryTestDevice", + ], } diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt b/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt index 03b43cc5b18c..6f4f7b13af66 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt @@ -16,14 +16,15 @@ package com.android.test.taskembed import android.app.Instrumentation -import android.graphics.Point import android.graphics.Rect import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.platform.app.InstrumentationRegistry import androidx.test.runner.AndroidJUnit4 -import com.android.server.wm.flicker.monitor.LayersTraceMonitor -import com.android.server.wm.flicker.monitor.withSFTracing -import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat +import android.tools.common.datatypes.Size +import android.tools.common.flicker.subject.layers.LayersTraceSubject +import android.tools.device.traces.io.ResultWriter +import android.tools.device.traces.monitors.surfaceflinger.LayersTraceMonitor +import android.tools.device.traces.monitors.withSFTracing import org.junit.After import org.junit.Before import org.junit.FixMethodOrder @@ -46,8 +47,10 @@ class ResizeTasksSyncTest { @Before fun setup() { - val tmpDir = instrumentation.targetContext.dataDir.toPath() - LayersTraceMonitor(tmpDir).stop() + val monitor = LayersTraceMonitor() + if (monitor.isEnabled) { + monitor.stop(ResultWriter()) + } val firstTaskBounds = Rect(0, 0, 1080, 1000) val secondTaskBounds = Rect(0, 1000, 1080, 2000) @@ -68,8 +71,7 @@ class ResizeTasksSyncTest { val firstBounds = Rect(0, 0, 1080, 800) val secondBounds = Rect(0, 1000, 1080, 1800) - val trace = withSFTracing(TRACE_FLAGS, - outputDir = instrumentation.targetContext.dataDir.toPath()) { + val trace = withSFTracing(TRACE_FLAGS) { lateinit var resizeReadyLatch: CountDownLatch scenarioRule.getScenario().onActivity { resizeReadyLatch = it.resizeTaskView(firstBounds, secondBounds) @@ -90,14 +92,14 @@ class ResizeTasksSyncTest { secondBounds.offsetTo(0, 0) // verify buffer size should be changed to expected values. - assertThat(trace).layer(FIRST_ACTIVITY, frame.toLong()).also { - val firstTaskSize = Point(firstBounds.width(), firstBounds.height()) + LayersTraceSubject(trace).layer(FIRST_ACTIVITY, frame.toLong()).also { + val firstTaskSize = Size.from(firstBounds.width(), firstBounds.height()) it.hasLayerSize(firstTaskSize) it.hasBufferSize(firstTaskSize) } - assertThat(trace).layer(SECOND_ACTIVITY, frame.toLong()).also { - val secondTaskSize = Point(secondBounds.width(), secondBounds.height()) + LayersTraceSubject(trace).layer(SECOND_ACTIVITY, frame.toLong()).also { + val secondTaskSize = Size.from(secondBounds.width(), secondBounds.height()) it.hasLayerSize(secondTaskSize) it.hasBufferSize(secondTaskSize) } @@ -108,4 +110,4 @@ class ResizeTasksSyncTest { private const val FIRST_ACTIVITY = "Activity1" private const val SECOND_ACTIVITY = "Activity2" } -}
\ No newline at end of file +} diff --git a/tests/TelephonyCommonTests/Android.bp b/tests/TelephonyCommonTests/Android.bp index a9fbfd97225d..81ec265c2c29 100644 --- a/tests/TelephonyCommonTests/Android.bp +++ b/tests/TelephonyCommonTests/Android.bp @@ -47,7 +47,7 @@ android_test { // Uncomment this and comment out the jarjar rule if you want to attach a debugger and step // through the renamed classes. - // platform_apis: true, + platform_apis: true, libs: [ "android.test.runner", diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java index 052ce3a902c1..adefac64dbae 100644 --- a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java +++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java @@ -44,6 +44,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.res.Resources; import android.net.Uri; import android.os.Handler; import android.os.UserHandle; @@ -75,9 +76,12 @@ import java.util.stream.Collectors; public class SmsApplicationTest { private static final ComponentName TEST_COMPONENT_NAME = ComponentName.unflattenFromString("com.android.test/.TestSmsApp"); + public static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth.services"; private static final String MMS_RECEIVER_NAME = "TestMmsReceiver"; private static final String RESPOND_VIA_SMS_NAME = "TestRespondViaSmsHandler"; private static final String SEND_TO_NAME = "TestSendTo"; + private static final String EXTERNAL_PROVIDER_CHANGE_NAME = "TestExternalProviderChangeHandler"; + private static final String SIM_FULL_NAME = "TestSimFullHandler"; private static final int SMS_APP_UID = 10001; private static final int FAKE_PHONE_UID = 10002; @@ -102,6 +106,7 @@ public class SmsApplicationTest { }).collect(Collectors.toSet()); @Mock private Context mContext; + @Mock private Resources mResources; @Mock private TelephonyManager mTelephonyManager; @Mock private RoleManager mRoleManager; @Mock private PackageManager mPackageManager; @@ -118,6 +123,9 @@ public class SmsApplicationTest { when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOpsManager); when(mContext.createContextAsUser(isNotNull(), anyInt())).thenReturn(mContext); + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getString(eq(com.android.internal.R.string.config_systemBluetoothStack))) + .thenReturn(BLUETOOTH_PACKAGE_NAME); doAnswer(invocation -> getResolveInfosForIntent(invocation.getArgument(0))) .when(mPackageManager) @@ -146,10 +154,46 @@ public class SmsApplicationTest { } } + @Test - public void testGetDefaultSmsApplication() { + public void testGetDefaultSmsApplicationAsUser() { assertEquals(TEST_COMPONENT_NAME, - SmsApplication.getDefaultSmsApplicationAsUser(mContext, false, 0)); + SmsApplication.getDefaultSmsApplicationAsUser(mContext, false, + UserHandle.SYSTEM)); + } + + + @Test + public void testGetDefaultMmsApplicationAsUser() { + ComponentName componentName = SmsApplication.getDefaultMmsApplicationAsUser(mContext, + false, UserHandle.SYSTEM); + assertEquals(TEST_COMPONENT_NAME.getPackageName(), componentName.getPackageName()); + assertEquals(MMS_RECEIVER_NAME, componentName.getClassName()); + } + + @Test + public void testGetDefaultExternalTelephonyProviderChangedApplicationAsUser() { + ComponentName componentName = SmsApplication + .getDefaultExternalTelephonyProviderChangedApplicationAsUser(mContext, + false, UserHandle.SYSTEM); + assertEquals(TEST_COMPONENT_NAME.getPackageName(), componentName.getPackageName()); + assertEquals(EXTERNAL_PROVIDER_CHANGE_NAME, componentName.getClassName()); + } + + @Test + public void testGetDefaultRespondViaMessageApplicationAsUserAsUser() { + ComponentName componentName = SmsApplication.getDefaultRespondViaMessageApplicationAsUser( + mContext, false, UserHandle.SYSTEM); + assertEquals(TEST_COMPONENT_NAME.getPackageName(), componentName.getPackageName()); + assertEquals(RESPOND_VIA_SMS_NAME, componentName.getClassName()); + } + + @Test + public void testGetDefaultSimFullApplicationAsUser() { + ComponentName componentName = SmsApplication.getDefaultSimFullApplicationAsUser(mContext, + false, UserHandle.SYSTEM); + assertEquals(TEST_COMPONENT_NAME.getPackageName(), componentName.getPackageName()); + assertEquals(SIM_FULL_NAME, componentName.getClassName()); } @Test @@ -160,7 +204,8 @@ public class SmsApplicationTest { setupPackageInfosForCoreApps(); assertEquals(TEST_COMPONENT_NAME, - SmsApplication.getDefaultSmsApplicationAsUser(mContext, true, 0)); + SmsApplication.getDefaultSmsApplicationAsUser(mContext, true, + UserHandle.SYSTEM)); verify(mAppOpsManager, atLeastOnce()).setUidMode(AppOpsManager.OPSTR_READ_SMS, SMS_APP_UID, AppOpsManager.MODE_ALLOWED); } @@ -237,6 +282,10 @@ public class SmsApplicationTest { return Collections.singletonList(makeRespondViaMessageResolveInfo()); case Intent.ACTION_SENDTO: return Collections.singletonList(makeSendToResolveInfo()); + case Telephony.Sms.Intents.ACTION_EXTERNAL_PROVIDER_CHANGE: + return Collections.singletonList(makeExternalProviderChangeResolveInfo()); + case Telephony.Sms.Intents.SIM_FULL_ACTION: + return Collections.singletonList(makeSimFullResolveInfo()); } return Collections.emptyList(); } @@ -294,4 +343,26 @@ public class SmsApplicationTest { info.activityInfo = activityInfo; return info; } + + private ResolveInfo makeExternalProviderChangeResolveInfo() { + ResolveInfo info = new ResolveInfo(); + ActivityInfo activityInfo = new ActivityInfo(); + + activityInfo.packageName = TEST_COMPONENT_NAME.getPackageName(); + activityInfo.name = EXTERNAL_PROVIDER_CHANGE_NAME; + + info.activityInfo = activityInfo; + return info; + } + + private ResolveInfo makeSimFullResolveInfo() { + ResolveInfo info = new ResolveInfo(); + ActivityInfo activityInfo = new ActivityInfo(); + + activityInfo.packageName = TEST_COMPONENT_NAME.getPackageName(); + activityInfo.name = SIM_FULL_NAME; + + info.activityInfo = activityInfo; + return info; + } } diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java new file mode 100644 index 000000000000..a62103e0030b --- /dev/null +++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java @@ -0,0 +1,77 @@ +/* + * 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.android.internal.telephony.tests; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.telephony.SubscriptionManager; + +import com.android.internal.telephony.util.TelephonyUtils; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +public class TelephonyUtilsTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + // Mocked classes + @Mock + private Context mContext; + @Mock + private SubscriptionManager mSubscriptionManager; + + @Before + public void setup() { + doReturn(mSubscriptionManager).when(mContext) + .getSystemService(eq(SubscriptionManager.class)); + } + + + @Test + public void getSubscriptionUserHandle_subId_invalid() { + int invalidSubId = -10; + doReturn(false).when(mSubscriptionManager).isActiveSubscriptionId(eq(invalidSubId)); + + TelephonyUtils.getSubscriptionUserHandle(mContext, invalidSubId); + + // getSubscriptionUserHandle should not be called if subID is inactive. + verify(mSubscriptionManager, never()).getSubscriptionUserHandle(eq(invalidSubId)); + } + + @Test + public void getSubscriptionUserHandle_subId_valid() { + int activeSubId = 1; + doReturn(true).when(mSubscriptionManager).isActiveSubscriptionId(eq(activeSubId)); + + TelephonyUtils.getSubscriptionUserHandle(mContext, activeSubId); + + // getSubscriptionUserHandle should be called if subID is active. + verify(mSubscriptionManager, times(1)).getSubscriptionUserHandle(eq(activeSubId)); + } +} + + diff --git a/tests/TouchLatency/Android.bp b/tests/TouchLatency/Android.bp index 3a9e240d9746..4ef1ead7d9c9 100644 --- a/tests/TouchLatency/Android.bp +++ b/tests/TouchLatency/Android.bp @@ -12,6 +12,7 @@ android_test { manifest: "app/src/main/AndroidManifest.xml", // omit gradle 'build' dir srcs: ["app/src/main/java/**/*.java"], + static_libs: ["com.google.android.material_material"], resource_dirs: ["app/src/main/res"], aaptflags: ["--auto-add-overlay"], sdk_version: "current", diff --git a/tests/TouchLatency/OWNERS b/tests/TouchLatency/OWNERS new file mode 100644 index 000000000000..2b7de2555587 --- /dev/null +++ b/tests/TouchLatency/OWNERS @@ -0,0 +1,2 @@ +include platform/frameworks/base:/graphics/java/android/graphics/OWNERS +include platform/frameworks/native:/services/surfaceflinger/OWNERS
\ No newline at end of file diff --git a/tests/TouchLatency/app/build.gradle b/tests/TouchLatency/app/build.gradle index 04a878896f47..07f6bcabb907 100644 --- a/tests/TouchLatency/app/build.gradle +++ b/tests/TouchLatency/app/build.gradle @@ -1,13 +1,12 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 28 - buildToolsVersion '28.0.3' + compileSdkVersion 33 defaultConfig { applicationId "com.prefabulated.touchlatency" - minSdkVersion 28 - targetSdkVersion 28 + minSdkVersion 30 + targetSdkVersion 33 versionCode 1 versionName "1.0" } @@ -17,4 +16,9 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + dependencies { + implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'com.google.android.material:material:1.6.0' + } } diff --git a/tests/TouchLatency/app/src/main/AndroidManifest.xml b/tests/TouchLatency/app/src/main/AndroidManifest.xml index 98947367bd7b..5743b2586416 100644 --- a/tests/TouchLatency/app/src/main/AndroidManifest.xml +++ b/tests/TouchLatency/app/src/main/AndroidManifest.xml @@ -20,13 +20,13 @@ <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:resizeableActivity="true" > <activity android:name=".TouchLatencyActivity" android:label="@string/app_name" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> diff --git a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java index 1664746f4636..7678633dfa5e 100644 --- a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java +++ b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java @@ -16,218 +16,62 @@ package com.prefabulated.touchlatency; -import android.app.Activity; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Paint.Align; +import android.hardware.display.DisplayManager; import android.os.Bundle; -import android.util.AttributeSet; -import android.util.Log; +import android.os.Handler; +import android.os.Trace; import android.view.Display; import android.view.Display.Mode; import android.view.Menu; import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.os.Trace; import android.view.Window; import android.view.WindowManager; -import java.math.RoundingMode; -import java.text.DecimalFormat; - -class TouchLatencyView extends View implements View.OnTouchListener { - private static final String LOG_TAG = "TouchLatency"; - private static final int BACKGROUND_COLOR = 0xFF400080; - private static final int INNER_RADIUS = 70; - private static final int BALL_DIAMETER = 200; - private static final int SEC_TO_NANOS = 1000000000; - private static final float FPS_UPDATE_THRESHOLD = 20; - private static final long BALL_VELOCITY = 420; - - public TouchLatencyView(Context context, AttributeSet attrs) { - super(context, attrs); - Trace.beginSection("TouchLatencyView constructor"); - setOnTouchListener(this); - setWillNotDraw(false); - mBluePaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mBluePaint.setColor(0xFF0000FF); - mBluePaint.setStyle(Paint.Style.FILL); - mGreenPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mGreenPaint.setColor(0xFF00FF00); - mGreenPaint.setStyle(Paint.Style.FILL); - mYellowPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mYellowPaint.setColor(0xFFFFFF00); - mYellowPaint.setStyle(Paint.Style.FILL); - mRedPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mRedPaint.setColor(0xFFFF0000); - mRedPaint.setStyle(Paint.Style.FILL); - mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mTextPaint.setColor(0xFFFFFFFF); - mTextPaint.setTextSize(100); - mTextPaint.setTextAlign(Align.RIGHT); - - mTouching = false; - - mLastDrawNano = 0; - mFps = 0; - mLastFpsUpdate = 0; - mFrameCount = 0; - - mDf = new DecimalFormat("fps: #.##"); - mDf.setRoundingMode(RoundingMode.HALF_UP); - - Trace.endSection(); - } - - @Override - public boolean onTouch(View view, MotionEvent event) { - Trace.beginSection("TouchLatencyView onTouch"); - int action = event.getActionMasked(); - if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE) { - mTouching = true; - invalidate(); - - mTouchX = event.getX(); - mTouchY = event.getY(); - } else if (action == MotionEvent.ACTION_UP) { - mTouching = false; - invalidate(); - } - Trace.endSection(); - return true; - } - - private void drawTouch(Canvas canvas) { - Trace.beginSection("TouchLatencyView drawTouch"); - - try { - if (!mTouching) { - Log.d(LOG_TAG, "Filling background"); - canvas.drawColor(BACKGROUND_COLOR); - return; - } - - float deltaX = (mTouchX - mLastDrawnX); - float deltaY = (mTouchY - mLastDrawnY); - float scaleFactor = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY) * 1.5f; - mLastDrawnX = mTouchX; - mLastDrawnY = mTouchY; - - canvas.drawColor(BACKGROUND_COLOR); - canvas.drawCircle(mTouchX, mTouchY, INNER_RADIUS + 3 * scaleFactor, mRedPaint); - canvas.drawCircle(mTouchX, mTouchY, INNER_RADIUS + 2 * scaleFactor, mYellowPaint); - canvas.drawCircle(mTouchX, mTouchY, INNER_RADIUS + scaleFactor, mGreenPaint); - canvas.drawCircle(mTouchX, mTouchY, INNER_RADIUS, mBluePaint); - } finally { - Trace.endSection(); - } - } - - private Paint getBallColor() { - if (mFps > 75) - return mGreenPaint; - else if (mFps > 45) - return mYellowPaint; - else - return mRedPaint; - } +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; - private void drawBall(Canvas canvas) { - Trace.beginSection("TouchLatencyView drawBall"); - int width = canvas.getWidth(); - int height = canvas.getHeight(); - float fps = 0f; +import com.google.android.material.slider.RangeSlider; +import com.google.android.material.slider.RangeSlider.OnChangeListener; - long t = System.nanoTime(); - long tDiff = t - mLastDrawNano; - mLastDrawNano = t; - mFrameCount++; +public class TouchLatencyActivity extends AppCompatActivity { + private static final int REFRESH_RATE_SLIDER_MIN = 20; + private static final int REFRESH_RATE_SLIDER_STEP = 1; - if (tDiff < SEC_TO_NANOS) { - fps = 1f * SEC_TO_NANOS / tDiff; - } - - long fDiff = t - mLastFpsUpdate; - if (Math.abs(mFps - fps) > FPS_UPDATE_THRESHOLD) { - mFps = fps; - mLastFpsUpdate = t; - mFrameCount = 0; - } else if (fDiff > SEC_TO_NANOS) { - mFps = 1f * mFrameCount * SEC_TO_NANOS / fDiff; - mLastFpsUpdate = t; - mFrameCount = 0; + private Menu mMenu; + private Mode[] mDisplayModes; + private int mCurrentModeIndex; + private float mSliderPreferredRefreshRate; + private DisplayManager mDisplayManager; + + private final DisplayManager.DisplayListener mDisplayListener = + new DisplayManager.DisplayListener() { + @Override + public void onDisplayAdded(int i) { + updateOptionsMenu(); } - final long pos = t * BALL_VELOCITY / SEC_TO_NANOS; - final long xMax = width - BALL_DIAMETER; - final long yMax = height - BALL_DIAMETER; - long xOffset = pos % xMax; - long yOffset = pos % yMax; - - float left, right, top, bottom; - - if (((pos / xMax) & 1) == 0) { - left = xMax - xOffset; - } else { - left = xOffset; + @Override + public void onDisplayRemoved(int i) { + updateOptionsMenu(); } - right = left + BALL_DIAMETER; - if (((pos / yMax) & 1) == 0) { - top = yMax - yOffset; - } else { - top = yOffset; + @Override + public void onDisplayChanged(int i) { + updateOptionsMenu(); } - bottom = top + BALL_DIAMETER; + }; - // Draw the ball - canvas.drawColor(BACKGROUND_COLOR); - canvas.drawOval(left, top, right, bottom, getBallColor()); - canvas.drawText(mDf.format(mFps), width, 100, mTextPaint); + private final RangeSlider.OnChangeListener mRefreshRateSliderListener = new OnChangeListener() { + @Override + public void onValueChange(@NonNull RangeSlider slider, float value, boolean fromUser) { + if (value == mSliderPreferredRefreshRate) return; - invalidate(); - Trace.endSection(); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - Trace.beginSection("TouchLatencyView onDraw"); - if (mMode == 0) { - drawTouch(canvas); - } else { - drawBall(canvas); + mSliderPreferredRefreshRate = value; + WindowManager.LayoutParams w = getWindow().getAttributes(); + w.preferredRefreshRate = mSliderPreferredRefreshRate; + getWindow().setAttributes(w); } - Trace.endSection(); - } - - public void changeMode(MenuItem item) { - Trace.beginSection("TouchLatencyView changeMode"); - final int NUM_MODES = 2; - final String modes[] = {"Touch", "Ball"}; - mMode = (mMode + 1) % NUM_MODES; - invalidate(); - item.setTitle(modes[mMode]); - Trace.endSection(); - } - - private final Paint mBluePaint, mGreenPaint, mYellowPaint, mRedPaint, mTextPaint; - private int mMode; - - private boolean mTouching; - private float mTouchX, mTouchY; - private float mLastDrawnX, mLastDrawnY; - - private long mLastDrawNano, mLastFpsUpdate, mFrameCount; - private float mFps; - private DecimalFormat mDf; -} - -public class TouchLatencyActivity extends Activity { - private Mode mDisplayModes[]; - private int mCurrentModeIndex; + }; @Override protected void onCreate(Bundle savedInstanceState) { @@ -235,9 +79,9 @@ public class TouchLatencyActivity extends Activity { Trace.beginSection("TouchLatencyActivity onCreate"); setContentView(R.layout.activity_touch_latency); - mTouchView = findViewById(R.id.canvasView); + configureDisplayListener(); WindowManager wm = getWindowManager(); Display display = wm.getDefaultDisplay(); mDisplayModes = display.getSupportedModes(); @@ -249,37 +93,89 @@ public class TouchLatencyActivity extends Activity { break; } } - Trace.endSection(); } + public void updateOptionsMenu() { + if (mDisplayModes.length > 1) { + MenuItem menuItem = mMenu.findItem(R.id.display_mode); + Mode currentMode = getWindowManager().getDefaultDisplay().getMode(); + updateDisplayMode(menuItem, currentMode); + } + updateRefreshRateMenu(mMenu.findItem(R.id.frame_rate)); + } @Override public boolean onCreateOptionsMenu(Menu menu) { Trace.beginSection("TouchLatencyActivity onCreateOptionsMenu"); + mMenu = menu; // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.menu_touch_latency, menu); - if (mDisplayModes.length > 1) { - MenuItem menuItem = menu.findItem(R.id.display_mode); - Mode currentMode = getWindowManager().getDefaultDisplay().getMode(); - updateDisplayMode(menuItem, currentMode); - } + getMenuInflater().inflate(R.menu.menu_touch_latency, mMenu); + updateOptionsMenu(); Trace.endSection(); return true; } - private void updateDisplayMode(MenuItem menuItem, Mode displayMode) { int fps = (int) displayMode.getRefreshRate(); menuItem.setTitle(fps + "hz"); menuItem.setVisible(true); } + private float getHighestRefreshRate() { + float maxRefreshRate = 0; + for (Display.Mode mode : getDisplay().getSupportedModes()) { + if (sameSizeMode(mode) && mode.getRefreshRate() > maxRefreshRate) { + maxRefreshRate = mode.getRefreshRate(); + } + } + return maxRefreshRate; + } + + private void updateRefreshRateMenu(MenuItem item) { + item.setActionView(R.layout.refresh_rate_layout); + RangeSlider slider = item.getActionView().findViewById(R.id.slider_from_layout); + slider.addOnChangeListener(mRefreshRateSliderListener); + + float highestRefreshRate = getHighestRefreshRate(); + slider.setValueFrom(REFRESH_RATE_SLIDER_MIN); + slider.setValueTo(highestRefreshRate); + slider.setStepSize(REFRESH_RATE_SLIDER_STEP); + if (mSliderPreferredRefreshRate < REFRESH_RATE_SLIDER_MIN + || mSliderPreferredRefreshRate > highestRefreshRate) { + mSliderPreferredRefreshRate = highestRefreshRate; + } + slider.setValues(mSliderPreferredRefreshRate); + } + + private void updateMultiDisplayMenu(MenuItem item) { + item.setVisible(mDisplayManager.getDisplays().length > 1); + } + + private void configureDisplayListener() { + mDisplayManager = getSystemService(DisplayManager.class); + mDisplayManager.registerDisplayListener(mDisplayListener, new Handler()); + } + + private boolean sameSizeMode(Display.Mode mode) { + Mode currentMode = mDisplayModes[mCurrentModeIndex]; + return currentMode.getPhysicalHeight() == mode.getPhysicalHeight() + && currentMode.getPhysicalWidth() == mode.getPhysicalWidth(); + } + public void changeDisplayMode(MenuItem item) { Window w = getWindow(); WindowManager.LayoutParams params = w.getAttributes(); int modeIndex = (mCurrentModeIndex + 1) % mDisplayModes.length; + while (modeIndex != mCurrentModeIndex) { + // skip modes with different resolutions + if (sameSizeMode(mDisplayModes[modeIndex])) { + break; + } + modeIndex = (modeIndex + 1) % mDisplayModes.length; + } + params.preferredDisplayModeId = mDisplayModes[modeIndex].getModeId(); w.setAttributes(params); @@ -287,7 +183,6 @@ public class TouchLatencyActivity extends Activity { mCurrentModeIndex = modeIndex; } - @Override public boolean onOptionsItemSelected(MenuItem item) { Trace.beginSection("TouchLatencyActivity onOptionsItemSelected"); @@ -297,15 +192,28 @@ public class TouchLatencyActivity extends Activity { int id = item.getItemId(); //noinspection SimplifiableIfStatement - if (id == R.id.action_settings) { - mTouchView.changeMode(item); - } else if (id == R.id.display_mode) { - changeDisplayMode(item); + switch (id) { + case R.id.action_settings: { + mTouchView.changeMode(item); + break; + } + case R.id.display_mode: { + changeDisplayMode(item); + break; + } } Trace.endSection(); return super.onOptionsItemSelected(item); } + @Override + protected void onDestroy() { + super.onDestroy(); + if (mDisplayManager != null) { + mDisplayManager.unregisterDisplayListener(mDisplayListener); + } + } + private TouchLatencyView mTouchView; } diff --git a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyView.java b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyView.java new file mode 100644 index 000000000000..0803e8e8510f --- /dev/null +++ b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyView.java @@ -0,0 +1,219 @@ +/* + * 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.prefabulated.touchlatency; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.os.Trace; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; + +import java.math.RoundingMode; +import java.text.DecimalFormat; + +class TouchLatencyView extends View implements View.OnTouchListener { + private static final String LOG_TAG = "TouchLatency"; + private static final int BACKGROUND_COLOR = 0xFF400080; + private static final int INNER_RADIUS = 70; + private static final int BALL_DIAMETER = 200; + private static final int SEC_TO_NANOS = 1000000000; + private static final float FPS_UPDATE_THRESHOLD = 20; + private static final long BALL_VELOCITY = 420; + + public TouchLatencyView(Context context, AttributeSet attrs) { + super(context, attrs); + Trace.beginSection("TouchLatencyView constructor"); + setOnTouchListener(this); + setWillNotDraw(false); + mBluePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mBluePaint.setColor(0xFF0000FF); + mBluePaint.setStyle(Paint.Style.FILL); + mGreenPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mGreenPaint.setColor(0xFF00FF00); + mGreenPaint.setStyle(Paint.Style.FILL); + mYellowPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mYellowPaint.setColor(0xFFFFFF00); + mYellowPaint.setStyle(Paint.Style.FILL); + mRedPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mRedPaint.setColor(0xFFFF0000); + mRedPaint.setStyle(Paint.Style.FILL); + mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mTextPaint.setColor(0xFFFFFFFF); + mTextPaint.setTextSize(100); + mTextPaint.setTextAlign(Paint.Align.RIGHT); + + mTouching = false; + + mLastDrawNano = 0; + mFps = 0; + mLastFpsUpdate = 0; + mFrameCount = 0; + + mDf = new DecimalFormat("fps: #.##"); + mDf.setRoundingMode(RoundingMode.HALF_UP); + + Trace.endSection(); + } + + @Override + public boolean onTouch(View view, MotionEvent event) { + Trace.beginSection("TouchLatencyView onTouch"); + int action = event.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE) { + mTouching = true; + invalidate(); + + mTouchX = event.getX(); + mTouchY = event.getY(); + } else if (action == MotionEvent.ACTION_UP) { + mTouching = false; + invalidate(); + } + Trace.endSection(); + return true; + } + + private void drawTouch(Canvas canvas) { + Trace.beginSection("TouchLatencyView drawTouch"); + + try { + if (!mTouching) { + Log.d(LOG_TAG, "Filling background"); + canvas.drawColor(BACKGROUND_COLOR); + return; + } + + float deltaX = (mTouchX - mLastDrawnX); + float deltaY = (mTouchY - mLastDrawnY); + float scaleFactor = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY) * 1.5f; + + mLastDrawnX = mTouchX; + mLastDrawnY = mTouchY; + + canvas.drawColor(BACKGROUND_COLOR); + canvas.drawCircle(mTouchX, mTouchY, INNER_RADIUS + 3 * scaleFactor, mRedPaint); + canvas.drawCircle(mTouchX, mTouchY, INNER_RADIUS + 2 * scaleFactor, mYellowPaint); + canvas.drawCircle(mTouchX, mTouchY, INNER_RADIUS + scaleFactor, mGreenPaint); + canvas.drawCircle(mTouchX, mTouchY, INNER_RADIUS, mBluePaint); + } finally { + Trace.endSection(); + } + } + + private Paint getBallColor() { + if (mFps > 75) { + return mGreenPaint; + } else if (mFps > 45) { + return mYellowPaint; + } else + return mRedPaint; + } + + private void drawBall(Canvas canvas) { + Trace.beginSection("TouchLatencyView drawBall"); + int width = canvas.getWidth(); + int height = canvas.getHeight(); + float fps = 0f; + + long t = System.nanoTime(); + long tDiff = t - mLastDrawNano; + mLastDrawNano = t; + mFrameCount++; + + if (tDiff < SEC_TO_NANOS) { + fps = 1f * SEC_TO_NANOS / tDiff; + } + + long fDiff = t - mLastFpsUpdate; + if (Math.abs(mFps - fps) > FPS_UPDATE_THRESHOLD) { + mFps = fps; + mLastFpsUpdate = t; + mFrameCount = 0; + } else if (fDiff > SEC_TO_NANOS) { + mFps = 1f * mFrameCount * SEC_TO_NANOS / fDiff; + mLastFpsUpdate = t; + mFrameCount = 0; + } + + final long pos = t * BALL_VELOCITY / SEC_TO_NANOS; + final long xMax = width - BALL_DIAMETER; + final long yMax = height - BALL_DIAMETER; + long xOffset = pos % xMax; + long yOffset = pos % yMax; + + float left, right, top, bottom; + + if (((pos / xMax) & 1) == 0) { + left = xMax - xOffset; + } else { + left = xOffset; + } + right = left + BALL_DIAMETER; + + if (((pos / yMax) & 1) == 0) { + top = yMax - yOffset; + } else { + top = yOffset; + } + bottom = top + BALL_DIAMETER; + + // Draw the ball + canvas.drawColor(BACKGROUND_COLOR); + canvas.drawOval(left, top, right, bottom, getBallColor()); + canvas.drawText(mDf.format(mFps), width, 100, mTextPaint); + + invalidate(); + Trace.endSection(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + Trace.beginSection("TouchLatencyView onDraw"); + if (mMode == 0) { + drawTouch(canvas); + } else { + drawBall(canvas); + } + Trace.endSection(); + } + + public void changeMode(MenuItem item) { + Trace.beginSection("TouchLatencyView changeMode"); + final int NUM_MODES = 2; + final String modes[] = {"Touch", "Ball"}; + mMode = (mMode + 1) % NUM_MODES; + invalidate(); + item.setTitle(modes[mMode]); + Trace.endSection(); + } + + private final Paint mBluePaint, mGreenPaint, mYellowPaint, mRedPaint, mTextPaint; + private int mMode; + + private boolean mTouching; + private float mTouchX, mTouchY; + private float mLastDrawnX, mLastDrawnY; + + private long mLastDrawNano, mLastFpsUpdate, mFrameCount; + private float mFps; + private DecimalFormat mDf; +} diff --git a/tests/TouchLatency/app/src/main/res/layout/refresh_rate_layout.xml b/tests/TouchLatency/app/src/main/res/layout/refresh_rate_layout.xml new file mode 100644 index 000000000000..bb9ce609c56f --- /dev/null +++ b/tests/TouchLatency/app/src/main/res/layout/refresh_rate_layout.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <com.google.android.material.slider.RangeSlider + android:id="@+id/slider_from_layout" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:tickColor="@color/cardview_light_background" + app:trackColor="@color/cardview_light_background" + app:thumbColor="@color/cardview_dark_background" + android:visibility="visible"/> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml b/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml index 52be91900ae8..f637f71f7ef2 100644 --- a/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml +++ b/tests/TouchLatency/app/src/main/res/menu/menu_touch_latency.xml @@ -14,15 +14,20 @@ limitations under the License. --> <menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".TouchLatencyActivity"> <item android:id="@+id/action_settings" android:orderInCategory="101" - android:showAsAction="always" - android:title="@string/mode"/> + android:title="@string/mode" + app:showAsAction="always" /> + <item + android:id="@+id/frame_rate" + android:title="@string/frame_rate" + app:showAsAction="collapseActionView" /> <item android:id="@+id/display_mode" - android:showAsAction="ifRoom" android:title="@string/display_mode" - android:visible="false"/> + android:visible="false" + app:showAsAction="always" /> </menu> diff --git a/tests/TouchLatency/app/src/main/res/values/strings.xml b/tests/TouchLatency/app/src/main/res/values/strings.xml index 771992c8e5d3..cad2df78ffcd 100644 --- a/tests/TouchLatency/app/src/main/res/values/strings.xml +++ b/tests/TouchLatency/app/src/main/res/values/strings.xml @@ -18,4 +18,6 @@ <string name="mode">Touch</string> <string name="display_mode">Mode</string> + <string name="frame_rate">Frame Rate</string> + <string name="multi_display">multi-display</string> </resources> diff --git a/tests/TouchLatency/app/src/main/res/values/styles.xml b/tests/TouchLatency/app/src/main/res/values/styles.xml index 22da7c1d050b..b23a87e57754 100644 --- a/tests/TouchLatency/app/src/main/res/values/styles.xml +++ b/tests/TouchLatency/app/src/main/res/values/styles.xml @@ -16,7 +16,7 @@ <resources> <!-- Base application theme. --> - <style name="AppTheme" parent="@android:style/Theme.Material.Light.DarkActionBar"> + <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar"> <!-- Customize your theme here. --> </style> diff --git a/tests/TouchLatency/build.gradle b/tests/TouchLatency/build.gradle index 03abe82a4359..f52935bae092 100644 --- a/tests/TouchLatency/build.gradle +++ b/tests/TouchLatency/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.android.tools.build:gradle:7.4.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/tests/TouchLatency/gradle.properties b/tests/TouchLatency/gradle.properties index 1d3591c8a4c9..ccd5dda1d6fa 100644 --- a/tests/TouchLatency/gradle.properties +++ b/tests/TouchLatency/gradle.properties @@ -15,4 +15,5 @@ # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true
\ No newline at end of file +# org.gradle.parallel=true +android.useAndroidX=true
\ No newline at end of file diff --git a/tests/TouchLatency/gradle/wrapper/gradle-wrapper.jar b/tests/TouchLatency/gradle/wrapper/gradle-wrapper.jar Binary files differindex 758de960ec79..7454180f2ae8 100644 --- a/tests/TouchLatency/gradle/wrapper/gradle-wrapper.jar +++ b/tests/TouchLatency/gradle/wrapper/gradle-wrapper.jar diff --git a/tests/TouchLatency/gradle/wrapper/gradle-wrapper.properties b/tests/TouchLatency/gradle/wrapper/gradle-wrapper.properties index 2d80b69a7665..8049c684f04f 100644 --- a/tests/TouchLatency/gradle/wrapper/gradle-wrapper.properties +++ b/tests/TouchLatency/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.8.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/tests/TouchLatency/gradlew b/tests/TouchLatency/gradlew index cccdd3d517fc..1b6c787337ff 100755 --- a/tests/TouchLatency/gradlew +++ b/tests/TouchLatency/gradlew @@ -1,78 +1,129 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -89,84 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/tests/TouchLatency/gradlew.bat b/tests/TouchLatency/gradlew.bat index e95643d6a2ca..ac1b06f93825 100644 --- a/tests/TouchLatency/gradlew.bat +++ b/tests/TouchLatency/gradlew.bat @@ -1,3 +1,19 @@ +@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,28 +64,14 @@ echo location of your Java installation. goto fail
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
diff --git a/tests/TrustTests/Android.bp b/tests/TrustTests/Android.bp index 77f98e88f1eb..a1b888aef934 100644 --- a/tests/TrustTests/Android.bp +++ b/tests/TrustTests/Android.bp @@ -24,7 +24,7 @@ android_test { static_libs: [ "androidx.test.rules", "androidx.test.ext.junit", - "androidx.test.uiautomator", + "androidx.test.uiautomator_uiautomator", "mockito-target-minus-junit4", "servicestests-utils", "truth-prebuilt", diff --git a/tests/TrustTests/AndroidManifest.xml b/tests/TrustTests/AndroidManifest.xml index 8b4cbfd0e44b..30cf345db34d 100644 --- a/tests/TrustTests/AndroidManifest.xml +++ b/tests/TrustTests/AndroidManifest.xml @@ -78,6 +78,16 @@ <action android:name="android.service.trust.TrustAgentService" /> </intent-filter> </service> + <service + android:name=".IsActiveUnlockRunningTrustAgent" + android:exported="true" + android:label="Test Agent" + android:permission="android.permission.BIND_TRUST_AGENT"> + <intent-filter> + <action android:name="android.service.trust.TrustAgentService" /> + </intent-filter> + </service> + </application> <!-- self-instrumenting test package. --> diff --git a/tests/TrustTests/src/android/trust/test/CanUnlockWithActiveUnlockTest.kt b/tests/TrustTests/src/android/trust/test/CanUnlockWithActiveUnlockTest.kt new file mode 100644 index 000000000000..7b68a829e23b --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/CanUnlockWithActiveUnlockTest.kt @@ -0,0 +1,131 @@ +/* + * 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 android.trust.test + +import android.app.trust.TrustManager +import android.content.Context +import android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE +import android.trust.BaseTrustAgentService +import android.trust.TrustTestActivity +import android.trust.test.lib.LockStateTrackingRule +import android.trust.test.lib.ScreenLockRule +import android.trust.test.lib.TestTrustListener +import android.trust.test.lib.TrustAgentRule +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import androidx.test.uiautomator.UiDevice +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.runner.RunWith + +/** + * Test for testing isActiveUnlockRunning. + * + * atest TrustTests:IsActiveUnlockRunningTest + */ +@RunWith(AndroidJUnit4::class) +class IsActiveUnlockRunningTest { + private val uiDevice = UiDevice.getInstance(getInstrumentation()) + private val context: Context = getApplicationContext() + private val userId = context.userId + private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager + private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java) + private val lockStateTrackingRule = LockStateTrackingRule() + private val trustAgentRule = TrustAgentRule<IsActiveUnlockRunningTrustAgent>() + + private val listener = object : TestTrustListener() { + var isRunning = false + private set + + override fun onIsActiveUnlockRunningChanged(isRunning: Boolean, userId: Int) { + this.isRunning = isRunning + } + } + + @get:Rule + val rule: RuleChain = RuleChain + .outerRule(activityScenarioRule) + .around(ScreenLockRule()) + .around(lockStateTrackingRule) + .around(trustAgentRule) + + @Before + fun manageTrust() { + trustAgentRule.agent.setManagingTrust(true) + trustManager.registerTrustListener(listener) + } + + @After + fun unregisterListener() { + trustManager.unregisterTrustListener(listener) + } + + @Test + fun defaultState_isActiveUnlockRunningIsFalse() { + assertThat(trustManager.isActiveUnlockRunning(userId)).isFalse() + assertThat(listener.isRunning).isFalse() + } + + @Test + fun grantTrustLockedDevice_isActiveUnlockRunningIsFalse() { + uiDevice.sleep() + lockStateTrackingRule.assertLocked() + + uiDevice.wakeUp() + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} + + assertThat(trustManager.isActiveUnlockRunning(userId)).isFalse() + assertThat(listener.isRunning).isFalse() + } + + @Test + fun grantTrustUnlockedDevice_isActiveUnlockRunningIsTrueWhileLocked() { + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} + uiDevice.sleep() + + lockStateTrackingRule.assertLocked() + + assertThat(trustManager.isActiveUnlockRunning(userId)).isTrue() + assertThat(listener.isRunning).isTrue() + } + + @Test + fun trustRevoked_isActiveUnlockRunningIsFalse() { + trustAgentRule.agent.grantTrust( + GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} + + trustAgentRule.agent.revokeTrust() + + assertThat(trustManager.isActiveUnlockRunning(userId)).isFalse() + assertThat(listener.isRunning).isFalse() + } + + companion object { + private const val GRANT_MESSAGE = "granted by test" + private fun await(millis: Long) = Thread.sleep(millis) + } +} + +class IsActiveUnlockRunningTrustAgent : BaseTrustAgentService() diff --git a/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt index 6a8752abfde7..501b9d33871a 100644 --- a/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt +++ b/tests/TrustTests/src/android/trust/test/UserUnlockRequestTest.kt @@ -79,6 +79,16 @@ class UserUnlockRequestTest { .isEqualTo(oldCount + 1) } + @Test + fun reportUserMayRequestUnlock_differentUserId_doesNotPropagateToAgent() { + val oldCount = trustAgentRule.agent.onUserMayRequestUnlockCallCount + trustManager.reportUserMayRequestUnlock(userId + 1) + await() + + assertThat(trustAgentRule.agent.onUserMayRequestUnlockCallCount) + .isEqualTo(oldCount) + } + companion object { private const val TAG = "UserUnlockRequestTest" private fun await() = Thread.sleep(250) diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt index 2031af2cf0c9..1400dde5781d 100644 --- a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt +++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt @@ -17,7 +17,6 @@ package android.trust.test.lib import android.app.trust.TrustManager -import android.app.trust.TrustManager.TrustListener import android.content.Context import android.util.Log import android.view.WindowManagerGlobal @@ -60,9 +59,10 @@ class LockStateTrackingRule : TestRule { wait("locked per TrustListener") { lockState.locked == false } } - inner class Listener : TrustListener { + inner class Listener : TestTrustListener() { override fun onTrustChanged( enabled: Boolean, + newlyUnlocked: Boolean, userId: Int, flags: Int, trustGrantedMessages: MutableList<String> @@ -70,12 +70,6 @@ class LockStateTrackingRule : TestRule { Log.d(TAG, "Device became trusted=$enabled") lockState = lockState.copy(locked = !enabled) } - - override fun onTrustManagedChanged(enabled: Boolean, userId: Int) { - } - - override fun onTrustError(message: CharSequence) { - } } data class LockState( diff --git a/tests/TrustTests/src/android/trust/test/lib/TestTrustListener.kt b/tests/TrustTests/src/android/trust/test/lib/TestTrustListener.kt new file mode 100644 index 000000000000..880497e954f1 --- /dev/null +++ b/tests/TrustTests/src/android/trust/test/lib/TestTrustListener.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 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 android.trust.test.lib + +import android.app.trust.TrustManager.TrustListener + +/** + * A listener that has default empty implementations for all [TrustListener] methods. + */ +open class TestTrustListener : TrustListener { + override fun onTrustChanged( + enabled: Boolean, + newlyUnlocked: Boolean, + userId: Int, + flags: Int, + trustGrantedMessages: MutableList<String> + ) { + } + + override fun onTrustManagedChanged(enabled: Boolean, userId: Int) { + } + + override fun onTrustError(message: CharSequence) { + } + + override fun onEnabledTrustAgentsChanged(userId: Int) { + } + + override fun onIsActiveUnlockRunningChanged(isRunning: Boolean, userId: Int) { + } +} diff --git a/tests/UiBench/Android.bp b/tests/UiBench/Android.bp index 0d2f2ef46cab..90e61c52da68 100644 --- a/tests/UiBench/Android.bp +++ b/tests/UiBench/Android.bp @@ -24,5 +24,6 @@ android_test { "androidx.recyclerview_recyclerview", "androidx.leanback_leanback", ], + certificate: "platform", test_suites: ["device-tests"], } diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml index 4fc6ec71f29c..47211c5fbad1 100644 --- a/tests/UiBench/AndroidManifest.xml +++ b/tests/UiBench/AndroidManifest.xml @@ -18,6 +18,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.android.test.uibench"> + <uses-permission android:name="android.permission.INJECT_EVENTS" /> <application android:allowBackup="false" android:theme="@style/Theme.AppCompat.Light.DarkActionBar" diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp index 9a9e42bfc300..9bfcc18ee301 100644 --- a/tests/UpdatableSystemFontTest/Android.bp +++ b/tests/UpdatableSystemFontTest/Android.bp @@ -37,6 +37,7 @@ android_test { "vts", ], data: [ + ":EmojiRenderingTestApp", ":UpdatableSystemFontTestCertDer", ":UpdatableSystemFontTest_NotoColorEmoji.ttf", ":UpdatableSystemFontTest_NotoColorEmoji.sig", diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java index cbe13d9aa149..fa5b7c15a6fe 100644 --- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java +++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java @@ -69,6 +69,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; +import java.util.stream.Stream; /** * Tests if fonts can be updated by {@link FontManager} API. @@ -246,7 +247,10 @@ public class UpdatableSystemFontTest { @Test public void updateFontFamily() throws Exception { assertThat(updateNotoSerifAs("serif")).isEqualTo(FontManager.RESULT_SUCCESS); - FontConfig.FontFamily family = findFontFamilyOrThrow("serif"); + final FontConfig.NamedFamilyList namedFamilyList = findFontFamilyOrThrow("serif"); + assertThat(namedFamilyList.getFamilies().size()).isEqualTo(1); + final FontConfig.FontFamily family = namedFamilyList.getFamilies().get(0); + assertThat(family.getFontList()).hasSize(2); assertThat(family.getFontList().get(0).getPostScriptName()) .isEqualTo(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME); @@ -265,7 +269,10 @@ public class UpdatableSystemFontTest { public void updateFontFamily_asNewFont() throws Exception { assertThat(updateNotoSerifAs("UpdatableSystemFontTest-serif")) .isEqualTo(FontManager.RESULT_SUCCESS); - FontConfig.FontFamily family = findFontFamilyOrThrow("UpdatableSystemFontTest-serif"); + final FontConfig.NamedFamilyList namedFamilyList = + findFontFamilyOrThrow("UpdatableSystemFontTest-serif"); + assertThat(namedFamilyList.getFamilies().size()).isEqualTo(1); + final FontConfig.FontFamily family = namedFamilyList.getFamilies().get(0); assertThat(family.getFontList()).hasSize(2); assertThat(family.getFontList().get(0).getPostScriptName()) .isEqualTo(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME); @@ -373,6 +380,10 @@ public class UpdatableSystemFontTest { try (InputStream is = new FileInputStream(certPath)) { result = runShellCommand("mini-keyctl padd asymmetric fsv_test .fs-verity", is); } + // /data/local/tmp is not readable by system server. Copy a cert file to /data/fonts + final String copiedCert = "/data/fonts/debug_cert.der"; + runShellCommand("cp " + certPath + " " + copiedCert, null); + runShellCommand("cmd font install-debug-cert " + copiedCert, null); // Assert that there are no errors. assertThat(result.second).isEmpty(); String keyId = result.first.trim(); @@ -430,9 +441,15 @@ public class UpdatableSystemFontTest { private String getFontPath(String psName) { FontConfig fontConfig = SystemUtil.runWithShellPermissionIdentity(mFontManager::getFontConfig); - return fontConfig.getFontFamilies().stream() + final List<FontConfig.FontFamily> namedFamilies = fontConfig.getNamedFamilyLists().stream() + .flatMap(namedFamily -> namedFamily.getFamilies().stream()).toList(); + + return Stream.concat(fontConfig.getFontFamilies().stream(), namedFamilies.stream()) .flatMap(family -> family.getFontList().stream()) - .filter(font -> psName.equals(font.getPostScriptName())) + .filter(font -> { + Log.e("Debug", "PsName = " + font.getPostScriptName()); + return psName.equals(font.getPostScriptName()); + }) // Return the last match, because the latter family takes precedence if two families // have the same name. .reduce((first, second) -> second) @@ -441,10 +458,10 @@ public class UpdatableSystemFontTest { .getAbsolutePath(); } - private FontConfig.FontFamily findFontFamilyOrThrow(String familyName) { + private FontConfig.NamedFamilyList findFontFamilyOrThrow(String familyName) { FontConfig fontConfig = SystemUtil.runWithShellPermissionIdentity(mFontManager::getFontConfig); - return fontConfig.getFontFamilies().stream() + return fontConfig.getNamedFamilyLists().stream() .filter(family -> familyName.equals(family.getName())) // Return the last match, because the latter family takes precedence if two families // have the same name. diff --git a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java index 782439f80fc8..e2099e652c49 100644 --- a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java +++ b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java @@ -24,12 +24,15 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.usb.UsbManager; +import android.os.Binder; import android.os.RemoteException; import android.util.Log; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.concurrent.atomic.AtomicInteger; + /** * Unit tests lib for {@link android.hardware.usb.UsbManager}. */ @@ -42,6 +45,11 @@ public class UsbManagerTestLib { private UsbManager mUsbManagerMock; @Mock private android.hardware.usb.IUsbManager mMockUsbService; + /** + * Counter for tracking UsbOperation operations. + */ + private static final AtomicInteger sUsbOperationCount = new AtomicInteger(); + public UsbManagerTestLib(Context context) { MockitoAnnotations.initMocks(this); mContext = context; @@ -82,10 +90,11 @@ public class UsbManagerTestLib { } private void testSetCurrentFunctionsMock_Matched(long functions) { + int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid(); try { setCurrentFunctions(functions); - verify(mMockUsbService).setCurrentFunctions(eq(functions)); + verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId); } catch (RemoteException remEx) { Log.w(TAG, "RemoteException"); } @@ -106,9 +115,10 @@ public class UsbManagerTestLib { } public void testSetCurrentFunctionsEx(long functions) throws Exception { + int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid(); setCurrentFunctions(functions); - verify(mMockUsbService).setCurrentFunctions(eq(functions)); + verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId); } public void testGetCurrentFunctions_shouldMatched() { @@ -117,6 +127,7 @@ public class UsbManagerTestLib { testGetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_PTP); testGetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_MIDI); testGetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_RNDIS); + testGetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_NCM); } public void testSetCurrentFunctions_shouldMatched() { @@ -125,5 +136,6 @@ public class UsbManagerTestLib { testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_PTP); testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_MIDI); testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_RNDIS); + testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_NCM); } } diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java index 861d221238ff..210e3ea2a9b2 100644 --- a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java +++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java @@ -98,7 +98,7 @@ public class UsbHandlerTest { } @Override - protected void setEnabledFunctions(long functions, boolean force) { + protected void setEnabledFunctions(long functions, boolean force, int operationId) { mCurrentFunctions = functions; } @@ -134,6 +134,24 @@ public class UsbHandlerTest { protected void sendStickyBroadcast(Intent intent) { mBroadcastedIntent = intent; } + + @Override + public void handlerInitDone(int operationId) { + } + + @Override + public void setCurrentUsbFunctionsCb(long functions, + int status, int mRequest, long mFunctions, boolean mChargingFunctions){ + } + + @Override + public void getUsbSpeedCb(int speed){ + } + + @Override + public void resetCb(int status){ + } + } @Before @@ -182,6 +200,14 @@ public class UsbHandlerTest { @SmallTest @Test + public void setFunctionsNcm() { + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS, + UsbManager.FUNCTION_NCM)); + assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_NCM, 0); + } + + @SmallTest + @Test public void setFunctionsNcmAndRndis() { final long rndisPlusNcm = UsbManager.FUNCTION_RNDIS | UsbManager.FUNCTION_NCM; diff --git a/tests/UsbTests/src/com/android/server/usb/UsbMidiPacketConverterTest.java b/tests/UsbTests/src/com/android/server/usb/UsbMidiPacketConverterTest.java new file mode 100644 index 000000000000..ad701e5117fc --- /dev/null +++ b/tests/UsbTests/src/com/android/server/usb/UsbMidiPacketConverterTest.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2023 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.android.server.usb; + +import static org.junit.Assert.assertEquals; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.Random; + +/** + * Unit tests for com.android.server.usb.UsbMidiPacketConverter. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class UsbMidiPacketConverterTest { + private byte[] generateRandomByteStream(Random rnd, int size) { + byte[] output = new byte[size]; + rnd.nextBytes(output); + return output; + } + + private void compareByteArrays(byte[] expectedArray, byte[] outputArray) { + assertEquals(expectedArray.length, outputArray.length); + for (int i = 0; i < outputArray.length; i++) { + assertEquals(expectedArray[i], outputArray[i]); + } + } + + @Test + public void testDecoderSinglePacket() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createDecoders(2); + byte[] input = new byte[] {0x19 /* Cable 1 Note-On */, (byte) 0x91, 0x33, 0x66}; + byte[] expectedOutputCable0 = new byte[] {}; + byte[] expectedOutputCable1 = new byte[] {(byte) 0x91, 0x33, 0x66}; + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0); + byte[] actualOutputCable1 = usbMidiPacketConverter.pullDecodedMidiPackets(1); + compareByteArrays(expectedOutputCable0, actualOutputCable0); + compareByteArrays(expectedOutputCable1, actualOutputCable1); + } + + @Test + public void testDecoderMultiplePackets() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createDecoders(4); + byte[] input = new byte[] { + 0x1B /* Cable 1 Control Change */, (byte) 0xB4, 0x55, 0x6E, + 0x35 /* Cable 3 Single byte SysEx */, (byte) 0xF8, 0x00, 0x00, + 0x02 /* Cable 0 Two byte System Common */, (byte) 0xF3, 0x12, 0x00}; + byte[] expectedOutputCable0 = new byte[] {(byte) 0xF3, 0x12}; + byte[] expectedOutputCable1 = new byte[] {(byte) 0xB4, 0x55, 0x6E}; + byte[] expectedOutputCable2 = new byte[] {}; + byte[] expectedOutputCable3 = new byte[] {(byte) 0xF8}; + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0); + byte[] actualOutputCable1 = usbMidiPacketConverter.pullDecodedMidiPackets(1); + byte[] actualOutputCable2 = usbMidiPacketConverter.pullDecodedMidiPackets(2); + byte[] actualOutputCable3 = usbMidiPacketConverter.pullDecodedMidiPackets(3); + compareByteArrays(expectedOutputCable0, actualOutputCable0); + compareByteArrays(expectedOutputCable1, actualOutputCable1); + compareByteArrays(expectedOutputCable2, actualOutputCable2); + compareByteArrays(expectedOutputCable3, actualOutputCable3); + } + + @Test + public void testDecoderSysExEndFirstByte() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createDecoders(2); + byte[] input = new byte[] { + 0x14 /* Cable 1 SysEx Start */, (byte) 0xF0, 0x00, 0x01, + 0x15 /* Cable 1 Single byte SysEx End */, (byte) 0xF7, 0x00, 0x00}; + byte[] expectedOutputCable0 = new byte[] {}; + byte[] expectedOutputCable1 = new byte[] { + (byte) 0xF0, 0x00, 0x01, + (byte) 0xF7}; + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0); + byte[] actualOutputCable1 = usbMidiPacketConverter.pullDecodedMidiPackets(1); + compareByteArrays(expectedOutputCable0, actualOutputCable0); + compareByteArrays(expectedOutputCable1, actualOutputCable1); + } + + @Test + public void testDecoderSysExEndSecondByte() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createDecoders(1); + byte[] input = new byte[] { + 0x04 /* Cable 0 SysEx Start */, (byte) 0xF0, 0x00, 0x01, + 0x06 /* Cable 0 Two byte SysEx End */, 0x02, (byte) 0xF7, 0x00}; + byte[] expectedOutputCable0 = new byte[] { + (byte) 0xF0, 0x00, 0x01, + 0x02, (byte) 0xF7}; + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0); + compareByteArrays(expectedOutputCable0, actualOutputCable0); + } + + @Test + public void testDecoderSysExEndThirdByte() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + byte[] input = new byte[] { + 0x04 /* Cable 0 SysEx Start */, (byte) 0xF0, 0x00, 0x01, + 0x07 /* Cable 0 Three byte SysEx End */, 0x02, 0x03, (byte) 0xF7}; + usbMidiPacketConverter.createDecoders(1); + byte[] expectedOutputCable0 = new byte[] { + (byte) 0xF0, 0x00, 0x01, + 0x02, 0x03, (byte) 0xF7}; + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0); + compareByteArrays(expectedOutputCable0, actualOutputCable0); + } + + @Test + public void testDecoderSysExStartEnd() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + byte[] input = new byte[] { + 0x06 /* Cable 0 Two byte SysEx End */, (byte) 0xF0, (byte) 0xF7, 0x00}; + usbMidiPacketConverter.createDecoders(1); + byte[] expectedOutputCable0 = new byte[] { + (byte) 0xF0, (byte) 0xF7}; + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0); + compareByteArrays(expectedOutputCable0, actualOutputCable0); + } + + @Test + public void testDecoderSysExStartByteEnd() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + byte[] input = new byte[] { + 0x07 /* Cable 0 Three byte SysEx End */, (byte) 0xF0, 0x44, (byte) 0xF7}; + usbMidiPacketConverter.createDecoders(1); + byte[] expectedOutputCable0 = new byte[] { + (byte) 0xF0, 0x44, (byte) 0xF7}; + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0); + compareByteArrays(expectedOutputCable0, actualOutputCable0); + } + + @Test + public void testDecoderDefaultToFirstCable() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + byte[] input = new byte[] {0x49 /* Cable 4 Note-On */, (byte) 0x91, 0x22, 0x33}; + usbMidiPacketConverter.createDecoders(1); + byte[] expectedOutputCable0 = new byte[] { + (byte) 0x91, 0x22, 0x33}; + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + byte[] actualOutputCable0 = usbMidiPacketConverter.pullDecodedMidiPackets(0); + compareByteArrays(expectedOutputCable0, actualOutputCable0); + } + + @Test + public void testDecoderLargePacketDoesNotCrash() { + for (long seed = 1001; seed < 5000; seed += 777) { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createDecoders(3); + Random rnd = new Random(seed); + byte[] input = generateRandomByteStream(rnd, 1003 /* arbitrary large size */); + usbMidiPacketConverter.decodeMidiPackets(input, input.length); + usbMidiPacketConverter.pullDecodedMidiPackets(0); + usbMidiPacketConverter.pullDecodedMidiPackets(1); + usbMidiPacketConverter.pullDecodedMidiPackets(2); + } + } + + @Test + public void testEncoderBasic() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(1); + byte[] input = new byte[] {(byte) 0x91 /* Note-On */, 0x33, 0x66}; + byte[] expectedOutput = new byte[] { + 0x09 /* Cable 0 Note-On */, (byte) 0x91, 0x33, 0x66}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderMultiplePackets() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(3); + byte[] inputCable2 = new byte[] { + (byte) 0xB4 /* Control Change */, 0x55, 0x6E}; + byte[] inputCable1 = new byte[] { + (byte) 0xF8 /* Timing Clock (Single Byte) */, + (byte) 0xF3 /* Song Select (Two Bytes) */, 0x12}; + byte[] expectedOutput = new byte[] { + 0x2B /* Cable 2 Control Change */, (byte) 0xB4, 0x55, 0x6E, + 0x15 /* Cable 1 Timing Clock */, (byte) 0xF8, 0x00, 0x00, + 0x12 /* Cable 1 Two Byte System Common */, (byte) 0xF3, 0x12, 0x00}; + usbMidiPacketConverter.encodeMidiPackets(inputCable2, inputCable2.length, 2); + usbMidiPacketConverter.encodeMidiPackets(inputCable1, inputCable1.length, 1); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderWeavePackets() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(2); + byte[] inputCable1Msg1 = new byte[] { + (byte) 0x93 /* Note-On */, 0x23, 0x43}; + byte[] inputCable0Msg = new byte[] { + (byte) 0xB4 /* Control Change */, 0x65, 0x26}; + byte[] inputCable1Msg2 = new byte[] { + (byte) 0xA4 /* Poly-KeyPress */, 0x52, 0x76}; + byte[] expectedOutput = new byte[] { + 0x19 /* Cable 1 Note-On */, (byte) 0x93, 0x23, 0x43, + 0x0B /* Cable 0 Control Change */, (byte) 0xB4, 0x65, 0x26, + 0x1A /* Cable 1 Poly-KeyPress */, (byte) 0xA4, 0x52, 0x76}; + usbMidiPacketConverter.encodeMidiPackets(inputCable1Msg1, inputCable1Msg1.length, 1); + usbMidiPacketConverter.encodeMidiPackets(inputCable0Msg, inputCable0Msg.length, 0); + usbMidiPacketConverter.encodeMidiPackets(inputCable1Msg2, inputCable1Msg2.length, 1); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderSysExEndFirstByte() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(1); + byte[] input = new byte[] { + (byte) 0xF0 /* SysEx Start */, 0x00, 0x01, + (byte) 0xF7 /* SysEx End */}; + byte[] expectedOutput = new byte[] { + 0x04 /* Cable 0 Three Byte SysEx Start */, (byte) 0xF0, 0x00, 0x01, + 0x05 /* Cable 0 One Byte SysEx End */, (byte) 0xF7, 0x00, 0x00}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderSysExEndSecondByte() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(1); + byte[] input = new byte[] { + (byte) 0xF0 /* SysEx Start */, 0x00, 0x01, + 0x02, (byte) 0xF7 /* SysEx End */}; + byte[] expectedOutput = new byte[] { + 0x04 /* Cable 0 Three Byte SysEx Start */, (byte) 0xF0, 0x00, 0x01, + 0x06 /* Cable 0 Two Byte SysEx End */, 0x02, (byte) 0xF7, 0x00}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderSysExEndThirdByte() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(1); + byte[] input = new byte[] { + (byte) 0xF0 /* SysEx Start */, 0x00, 0x01, + 0x02, 0x03, (byte) 0xF7 /* SysEx End */}; + byte[] expectedOutput = new byte[] { + 0x04 /* Cable 0 Three Byte SysEx Start */, (byte) 0xF0, 0x00, 0x01, + 0x07 /* Cable 0 Three Byte SysEx End */, 0x02, 0x03, (byte) 0xF7}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderSysExStartEnd() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(1); + byte[] input = new byte[] { + (byte) 0xF0 /* SysEx Start */, (byte) 0xF7 /* SysEx End */}; + byte[] expectedOutput = new byte[] { + 0x06 /* Cable 0 Two Byte SysEx End */, (byte) 0xF0, (byte) 0xF7, 0x00}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderSysExStartByteEnd() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(1); + byte[] input = new byte[] { + (byte) 0xF0 /* SysEx Start */, 0x44, (byte) 0xF7 /* SysEx End */}; + byte[] expectedOutput = new byte[] { + 0x07 /* Cable 0 Three Byte SysEx End */, (byte) 0xF0, 0x44, (byte) 0xF7}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderMultiplePulls() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(1); + + byte[] input = new byte[] { + (byte) 0xF0 /* SysEx Start */, 0x44, 0x55, + 0x66, 0x77}; // 0x66 and 0x77 will not be pulled the first time + byte[] expectedOutput = new byte[] { + 0x04 /* SysEx Start */, (byte) 0xF0, 0x44, 0x55}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + + input = new byte[] { + 0x11, // Combined with 0x66 and 0x77 above + 0x22, (byte) 0xF7 /* SysEx End */}; + expectedOutput = new byte[] { + 0x04 /* Cable 0 SysEx Continue */, 0x66, 0x77, 0x11, + 0x06 /* Cable 0 Two Byte SysEx End */, 0x22, (byte) 0xF7, 0x00}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + + input = new byte[] { + (byte) 0xF0 /* SysEx Start */, (byte) 0xF7 /* SysEx End */}; + expectedOutput = new byte[] { + 0x06 /* Cable 0 Two Byte SysEx End */, (byte) 0xF0, (byte) 0xF7, 0x00}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 0); + output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderDefaultToFirstCable() { + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(2); + byte[] input = new byte[] {(byte) 0x91 /* Note-On */, 0x22, 0x33}; + byte[] expectedOutput = new byte[] { + 0x09 /* Cable 0 Note-On */, (byte) 0x91, 0x22, 0x33}; + usbMidiPacketConverter.encodeMidiPackets(input, input.length, 4); + byte[] output = usbMidiPacketConverter.pullEncodedMidiPackets(); + compareByteArrays(expectedOutput, output); + } + + @Test + public void testEncoderLargePacketDoesNotCrash() { + for (long seed = 234; seed < 4000; seed += 666) { + Random rnd = new Random(seed); + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(4); + for (int cableNumber = 0; cableNumber < 4; cableNumber++) { + byte[] input = generateRandomByteStream(rnd, 1003 /* arbitrary large size */); + usbMidiPacketConverter.encodeMidiPackets(input, input.length, cableNumber); + } + usbMidiPacketConverter.pullEncodedMidiPackets(); + } + } + + @Test + public void testEncodeDecode() { + final int bufferSize = 30; + final int numCables = 16; + final int bytesToEncodePerEncoding = 10; + byte[][] rawMidi = new byte[numCables][bufferSize]; + for (long seed = 45; seed < 3000; seed += 300) { + Random rnd = new Random(seed); + for (int cableNumber = 0; cableNumber < numCables; cableNumber++) { + rawMidi[cableNumber] = generateRandomByteStream(rnd, bufferSize); + + // Change the last byte to SysEx End. + // This way the encoder is guaranteed to flush all packets. + rawMidi[cableNumber][bufferSize - 1] = (byte) 0xF7; + } + UsbMidiPacketConverter usbMidiPacketConverter = new UsbMidiPacketConverter(); + usbMidiPacketConverter.createEncoders(numCables); + // Encode packets and interweave them + for (int startByte = 0; startByte < bufferSize; + startByte += bytesToEncodePerEncoding) { + for (int cableNumber = 0; cableNumber < numCables; cableNumber++) { + byte[] bytesToEncode = Arrays.copyOfRange(rawMidi[cableNumber], startByte, + startByte + bytesToEncodePerEncoding); + usbMidiPacketConverter.encodeMidiPackets(bytesToEncode, bytesToEncode.length, + cableNumber); + } + } + byte[] usbMidi = usbMidiPacketConverter.pullEncodedMidiPackets(); + + usbMidiPacketConverter.createDecoders(numCables); + + // Now decode the MIDI packets to check if they are the same as the original + usbMidiPacketConverter.decodeMidiPackets(usbMidi, usbMidi.length); + for (int cableNumber = 0; cableNumber < numCables; cableNumber++) { + byte[] decodedRawMidi = usbMidiPacketConverter.pullDecodedMidiPackets(cableNumber); + compareByteArrays(rawMidi[cableNumber], decodedRawMidi); + } + } + } +} diff --git a/tests/VectorDrawableTest/OWNERS b/tests/VectorDrawableTest/OWNERS new file mode 100644 index 000000000000..27e16681899e --- /dev/null +++ b/tests/VectorDrawableTest/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 24939 + +include /graphics/java/android/graphics/OWNERS diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java index db4898492ac5..661dd845757b 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java @@ -33,6 +33,8 @@ import android.widget.CheckBox; import android.widget.ImageView; import android.widget.TextView; +import java.util.Arrays; + public class MainInteractionSession extends VoiceInteractionSession implements View.OnClickListener { static final String TAG = "MainInteractionSession"; @@ -403,7 +405,7 @@ public class MainInteractionSession extends VoiceInteractionSession @Override public void onRequestPickOption(PickOptionRequest request) { Log.i(TAG, "onPickOption: prompt=" + request.getVoicePrompt() + " options=" - + request.getOptions() + " extras=" + request.getExtras()); + + Arrays.toString(request.getOptions()) + " extras=" + request.getExtras()); mConfirmButton.setText("Pick Option"); mPendingRequest = request; setPrompt(request.getVoicePrompt()); diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java index 733f602f6c14..8ae7186461d3 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/StartVoiceInteractionActivity.java @@ -24,6 +24,8 @@ import android.view.View; import android.widget.Button; import android.widget.TextView; +import java.util.Arrays; + public class StartVoiceInteractionActivity extends Activity implements View.OnClickListener { static final String TAG = "LocalVoiceInteractionActivity"; @@ -187,7 +189,8 @@ public class StartVoiceInteractionActivity extends Activity implements View.OnCl } @Override public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) { - Log.i(TAG, "Pick result: finished=" + finished + " selections=" + selections + Log.i(TAG, "Pick result: finished=" + finished + + " selections=" + Arrays.toString(selections) + " result=" + result); StringBuilder sb = new StringBuilder(); if (finished) { diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java index ada0e21e059a..4fc3a15ac38d 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java @@ -28,6 +28,8 @@ import android.view.View; import android.widget.Button; import android.widget.TextView; +import java.util.Arrays; + public class TestInteractionActivity extends Activity implements View.OnClickListener { static final String TAG = "TestInteractionActivity"; @@ -240,7 +242,8 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis } @Override public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) { - Log.i(TAG, "Pick result: finished=" + finished + " selections=" + selections + Log.i(TAG, "Pick result: finished=" + finished + + " selections=" + Arrays.toString(selections) + " result=" + result); StringBuilder sb = new StringBuilder(); if (finished) { diff --git a/tests/WindowAnimationJank/Android.bp b/tests/WindowAnimationJank/Android.bp index ed86aa5f90ea..8542f885d645 100644 --- a/tests/WindowAnimationJank/Android.bp +++ b/tests/WindowAnimationJank/Android.bp @@ -25,7 +25,7 @@ android_test { name: "WindowAnimationJank", srcs: ["src/**/*.java"], static_libs: [ - "ub-uiautomator", + "androidx.test.uiautomator_uiautomator", "androidx.test.janktesthelper", "junit", ], diff --git a/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java b/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java index 25314644ca7e..48a359c4d0c6 100644 --- a/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java +++ b/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java @@ -18,11 +18,12 @@ import android.app.UiAutomation; import android.content.ComponentName; import android.content.Intent; import android.os.SystemClock; -import android.support.test.uiautomator.By; -import android.support.test.uiautomator.BySelector; -import android.support.test.uiautomator.UiDevice; -import android.support.test.uiautomator.UiObject2; -import android.support.test.uiautomator.Until; + +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.BySelector; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject2; +import androidx.test.uiautomator.Until; /** * Set of helpers to manipulate test activities. diff --git a/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java b/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java index a8ace162c4d0..cb7c5112cba7 100644 --- a/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java +++ b/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java @@ -16,9 +16,8 @@ package android.windowanimationjank; -import android.support.test.uiautomator.UiDevice; - import androidx.test.jank.JankTestBase; +import androidx.test.uiautomator.UiDevice; /** * This adds additional system level jank monitor and its result is merged with primary monitor diff --git a/tests/WindowInsetsTests/res/layout/controller_activity.xml b/tests/WindowInsetsTests/res/layout/controller_activity.xml index d51a4ddd43e8..5550eab61a33 100644 --- a/tests/WindowInsetsTests/res/layout/controller_activity.xml +++ b/tests/WindowInsetsTests/res/layout/controller_activity.xml @@ -88,7 +88,7 @@ <TextView android:id="@+id/textViewControllableInsets" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" /> diff --git a/tests/WindowInsetsTests/res/values/strings.xml b/tests/WindowInsetsTests/res/values/strings.xml index d6355f5a0464..516d4584426e 100644 --- a/tests/WindowInsetsTests/res/values/strings.xml +++ b/tests/WindowInsetsTests/res/values/strings.xml @@ -22,7 +22,7 @@ <!-- The item positions should match the flag values respectively. --> <string-array name="behaviors"> - <item>BEHAVIOR_SHOW_BARS_BY_TOUCH</item> + <item>BEHAVIOR_SHOW_BARS_BY_TOUCH (deprecated)</item> <item>BEHAVIOR_DEFAULT</item> <item>BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE</item> </string-array> diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java index 95fd959e5587..167d560633ab 100644 --- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java +++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java @@ -83,7 +83,12 @@ public class ControllerActivity extends Activity implements View.OnApplyWindowIn final View contentView = findViewById(R.id.content); contentView.setOnApplyWindowInsetsListener(this); contentView.getWindowInsetsController().addOnControllableInsetsChangedListener( - (c, types) -> mTextControllableInsets.setText("ControllableInsetsTypes=" + types)); + (c, types) -> mTextControllableInsets.setText( + "ControllableInsetsTypes:\n" + insetsTypesToString(types))); + } + + private static String insetsTypesToString(int types) { + return types == 0 ? "none" : WindowInsets.Type.toString(types); } @Override diff --git a/tests/backup/Android.bp b/tests/backup/Android.bp new file mode 100644 index 000000000000..3890a1f58078 --- /dev/null +++ b/tests/backup/Android.bp @@ -0,0 +1,54 @@ +// Copyright (C) 2008 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. + +// native test +// ======================================== +package { + // See: http://go/android-license-faq + default_applicable_licenses: [ + "frameworks_base_license", + ], +} + +cc_binary { + name: "backup_helper_test", + + srcs: ["backup_helper_test.cpp"], + + cflags: [ + "-Wall", + "-Werror", + ], + + shared_libs: [ + "libandroidfw", + "libutils", + ], + +} + +// java test +// ======================================== +android_app { + name: "BackupTest", + + srcs: ["**/*.java"], + + platform_apis: true, + + optimize: { + enabled: false, + }, + +} diff --git a/tests/backup/Android.mk b/tests/backup/Android.mk deleted file mode 100644 index b6f34717658c..000000000000 --- a/tests/backup/Android.mk +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright (C) 2008 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. - -LOCAL_PATH := $(call my-dir) - -# native test -# ======================================== -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - backup_helper_test.cpp - -LOCAL_CFLAGS := -Wall -Werror -LOCAL_MODULE_TAGS := optional -LOCAL_MODULE := backup_helper_test -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE -LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) -LOCAL_SHARED_LIBRARIES := libandroidfw libutils - -include $(BUILD_EXECUTABLE) - -# java test -# ======================================== -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := optional - -LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) - -LOCAL_SRC_FILES := $(call all-subdir-java-files) - -LOCAL_PACKAGE_NAME := BackupTest -LOCAL_PRIVATE_PLATFORM_APIS := true - -LOCAL_PROGUARD_ENABLED := disabled - -LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 -LOCAL_LICENSE_CONDITIONS := notice -LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE -include $(BUILD_PACKAGE) diff --git a/tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java b/tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java index 388548691b77..0a03e8d6534a 100644 --- a/tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java +++ b/tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java @@ -1,454 +1,448 @@ -/*
- * Copyright (C) 2020 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.android.internal;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import android.app.Activity;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.SystemClock;
-import android.util.Log;
-
-import androidx.test.filters.LargeTest;
-
-import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.internal.util.function.pooled.PooledPredicate;
-
-import org.junit.Assume;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runners.model.Statement;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/** Compares the performance of regular lambda and pooled lambda. */
-@LargeTest
-public class LambdaPerfTest {
- private static final boolean DEBUG = false;
- private static final String TAG = LambdaPerfTest.class.getSimpleName();
-
- private static final String LAMBDA_FORM_REGULAR = "regular";
- private static final String LAMBDA_FORM_POOLED = "pooled";
-
- private static final int WARMUP_ITERATIONS = 1000;
- private static final int TEST_ITERATIONS = 3000000;
- private static final int TASK_COUNT = 10;
- private static final long DELAY_AFTER_BENCH_MS = 1000;
-
- private String mMethodName;
-
- private final Bundle mTestResults = new Bundle();
- private final ArrayList<Task> mTasks = new ArrayList<>();
-
- // The member fields are used to ensure lambda capturing. They don't have the actual meaning.
- private final Task mTask = new Task();
- private final Rect mBounds = new Rect();
- private int mTaskId;
- private long mTime;
- private boolean mTop;
-
- @Rule
- public final TestRule mRule = (base, description) -> new Statement() {
- @Override
- public void evaluate() throws Throwable {
- mMethodName = description.getMethodName();
- mTasks.clear();
- for (int i = 0; i < TASK_COUNT; i++) {
- final Task t = new Task();
- mTasks.add(t);
- }
- base.evaluate();
-
- getInstrumentation().sendStatus(Activity.RESULT_OK, mTestResults);
- }
- };
-
- @Test
- public void test1ParamConsumer() {
- evaluate(LAMBDA_FORM_REGULAR, () -> forAllTask(t -> t.doSomething(mTask)));
- evaluate(LAMBDA_FORM_POOLED, () -> {
- final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
- PooledLambda.__(Task.class), mTask);
- forAllTask(c);
- c.recycle();
- });
- }
-
- @Test
- public void test2PrimitiveParamsConsumer() {
- // Not in Integer#IntegerCache (-128~127) for autoboxing, that will create new object.
- mTaskId = 12345;
- mTime = 54321;
-
- evaluate(LAMBDA_FORM_REGULAR, () -> forAllTask(t -> t.doSomething(mTaskId, mTime)));
- evaluate(LAMBDA_FORM_POOLED, () -> {
- final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
- PooledLambda.__(Task.class), mTaskId, mTime);
- forAllTask(c);
- c.recycle();
- });
- }
-
- @Test
- public void test3ParamsPredicate() {
- mTop = true;
- // In Integer#IntegerCache.
- mTaskId = 10;
-
- evaluate(LAMBDA_FORM_REGULAR, () -> handleTask(t -> t.doSomething(mBounds, mTop, mTaskId)));
- evaluate(LAMBDA_FORM_POOLED, () -> {
- final PooledPredicate c = PooledLambda.obtainPredicate(Task::doSomething,
- PooledLambda.__(Task.class), mBounds, mTop, mTaskId);
- handleTask(c);
- c.recycle();
- });
- }
-
- @Test
- public void testMessage() {
- evaluate(LAMBDA_FORM_REGULAR, () -> {
- final Message m = Message.obtain().setCallback(() -> mTask.doSomething(mTaskId, mTime));
- m.getCallback().run();
- m.recycle();
- });
- evaluate(LAMBDA_FORM_POOLED, () -> {
- final Message m = PooledLambda.obtainMessage(Task::doSomething, mTask, mTaskId, mTime);
- m.getCallback().run();
- m.recycle();
- });
- }
-
- @Test
- public void testRunnable() {
- evaluate(LAMBDA_FORM_REGULAR, () -> {
- final Runnable r = mTask::doSomething;
- r.run();
- });
- evaluate(LAMBDA_FORM_POOLED, () -> {
- final Runnable r = PooledLambda.obtainRunnable(Task::doSomething, mTask).recycleOnUse();
- r.run();
- });
- }
-
- @Test
- public void testMultiThread() {
- final int numThread = 3;
-
- final Runnable regularAction = () -> forAllTask(t -> t.doSomething(mTask));
- final Runnable[] regularActions = new Runnable[numThread];
- Arrays.fill(regularActions, regularAction);
- evaluateMultiThread(LAMBDA_FORM_REGULAR, regularActions);
-
- final Runnable pooledAction = () -> {
- final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
- PooledLambda.__(Task.class), mTask);
- forAllTask(c);
- c.recycle();
- };
- final Runnable[] pooledActions = new Runnable[numThread];
- Arrays.fill(pooledActions, pooledAction);
- evaluateMultiThread(LAMBDA_FORM_POOLED, pooledActions);
- }
-
- private void forAllTask(Consumer<Task> callback) {
- for (int i = mTasks.size() - 1; i >= 0; i--) {
- callback.accept(mTasks.get(i));
- }
- }
-
- private void handleTask(Predicate<Task> callback) {
- for (int i = mTasks.size() - 1; i >= 0; i--) {
- final Task task = mTasks.get(i);
- if (callback.test(task)) {
- return;
- }
- }
- }
-
- private void evaluate(String title, Runnable action) {
- for (int i = 0; i < WARMUP_ITERATIONS; i++) {
- action.run();
- }
- performGc();
-
- final GcStatus startGcStatus = getGcStatus();
- final long startTime = SystemClock.elapsedRealtime();
- for (int i = 0; i < TEST_ITERATIONS; i++) {
- action.run();
- }
- evaluateResult(title, startGcStatus, startTime);
- }
-
- private void evaluateMultiThread(String title, Runnable[] actions) {
- performGc();
-
- final CountDownLatch latch = new CountDownLatch(actions.length);
- final GcStatus startGcStatus = getGcStatus();
- final long startTime = SystemClock.elapsedRealtime();
- for (Runnable action : actions) {
- new Thread() {
- @Override
- public void run() {
- for (int i = 0; i < TEST_ITERATIONS; i++) {
- action.run();
- }
- latch.countDown();
- };
- }.start();
- }
- try {
- latch.await();
- } catch (InterruptedException ignored) {
- }
- evaluateResult(title, startGcStatus, startTime);
- }
-
- private void evaluateResult(String title, GcStatus startStatus, long startTime) {
- final float elapsed = SystemClock.elapsedRealtime() - startTime;
- // Sleep a while to see if GC may happen.
- SystemClock.sleep(DELAY_AFTER_BENCH_MS);
- final GcStatus endStatus = getGcStatus();
- final GcInfo info = startStatus.calculateGcTime(endStatus, title, mTestResults);
- Log.i(TAG, mMethodName + "_" + title + " execution time: "
- + elapsed + "ms (avg=" + String.format("%.5f", elapsed / TEST_ITERATIONS) + "ms)"
- + " GC time: " + String.format("%.3f", info.mTotalGcTime) + "ms"
- + " GC paused time: " + String.format("%.3f", info.mTotalGcPausedTime) + "ms");
- }
-
- /** Cleans the test environment. */
- private static void performGc() {
- System.gc();
- System.runFinalization();
- System.gc();
- }
-
- private static GcStatus getGcStatus() {
- if (DEBUG) {
- Log.i(TAG, "===== Read GC dump =====");
- }
- final GcStatus status = new GcStatus();
- final List<String> vmDump = getVmDump();
- Assume.assumeFalse("VM dump is empty", vmDump.isEmpty());
- for (String line : vmDump) {
- status.visit(line);
- if (line.startsWith("DALVIK THREADS")) {
- break;
- }
- }
- return status;
- }
-
- private static List<String> getVmDump() {
- final int myPid = Process.myPid();
- // Another approach Debug#dumpJavaBacktraceToFileTimeout requires setenforce 0.
- Process.sendSignal(myPid, Process.SIGNAL_QUIT);
- // Give a chance to handle the signal.
- SystemClock.sleep(100);
-
- String dump = null;
- final String pattern = myPid + " written to: ";
- final List<String> logs = shell("logcat -v brief -d tombstoned:I *:S");
- for (int i = logs.size() - 1; i >= 0; i--) {
- final String log = logs.get(i);
- // Log pattern: Traces for pid 9717 written to: /data/anr/trace_07
- final int pos = log.indexOf(pattern);
- if (pos > 0) {
- dump = log.substring(pattern.length() + pos);
- break;
- }
- }
-
- Assume.assumeNotNull("Unable to find VM dump", dump);
- // It requires system or root uid to read the trace.
- return shell("cat " + dump);
- }
-
- private static List<String> shell(String command) {
- final ParcelFileDescriptor.AutoCloseInputStream stream =
- new ParcelFileDescriptor.AutoCloseInputStream(
- getInstrumentation().getUiAutomation().executeShellCommand(command));
- final ArrayList<String> lines = new ArrayList<>();
- try (BufferedReader br = new BufferedReader(new InputStreamReader(stream))) {
- String line;
- while ((line = br.readLine()) != null) {
- lines.add(line);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- return lines;
- }
-
- /** An empty class which provides some methods with different type arguments. */
- static class Task {
- void doSomething() {
- }
-
- void doSomething(Task t) {
- }
-
- void doSomething(int taskId, long time) {
- }
-
- boolean doSomething(Rect bounds, boolean top, int taskId) {
- return false;
- }
- }
-
- static class ValPattern {
- static final int TYPE_COUNT = 0;
- static final int TYPE_TIME = 1;
- static final String PATTERN_COUNT = "(\\d+)";
- static final String PATTERN_TIME = "(\\d+\\.?\\d+)(\\w+)";
- final String mRawPattern;
- final Pattern mPattern;
- final int mType;
-
- int mIntValue;
- float mFloatValue;
-
- ValPattern(String p, int type) {
- mRawPattern = p;
- mPattern = Pattern.compile(
- p + (type == TYPE_TIME ? PATTERN_TIME : PATTERN_COUNT) + ".*");
- mType = type;
- }
-
- boolean visit(String line) {
- final Matcher matcher = mPattern.matcher(line);
- if (!matcher.matches()) {
- return false;
- }
- final String value = matcher.group(1);
- if (value == null) {
- return false;
- }
- if (mType == TYPE_COUNT) {
- mIntValue = Integer.parseInt(value);
- return true;
- }
- final float time = Float.parseFloat(value);
- final String unit = matcher.group(2);
- if (unit == null) {
- return false;
- }
- // Refer to art/libartbase/base/time_utils.cc
- switch (unit) {
- case "s":
- mFloatValue = time * 1000;
- break;
- case "ms":
- mFloatValue = time;
- break;
- case "us":
- mFloatValue = time / 1000;
- break;
- case "ns":
- mFloatValue = time / 1000 / 1000;
- break;
- default:
- throw new IllegalArgumentException();
- }
-
- return true;
- }
-
- @Override
- public String toString() {
- return mRawPattern + (mType == TYPE_TIME ? (mFloatValue + "ms") : mIntValue);
- }
- }
-
- /** Parses the dump pattern of Heap::DumpGcPerformanceInfo. */
- private static class GcStatus {
- private static final int TOTAL_GC_TIME_INDEX = 1;
- private static final int TOTAL_GC_PAUSED_TIME_INDEX = 5;
-
- // Refer to art/runtime/gc/heap.cc
- final ValPattern[] mPatterns = {
- new ValPattern("Total GC count: ", ValPattern.TYPE_COUNT),
- new ValPattern("Total GC time: ", ValPattern.TYPE_TIME),
- new ValPattern("Total time waiting for GC to complete: ", ValPattern.TYPE_TIME),
- new ValPattern("Total blocking GC count: ", ValPattern.TYPE_COUNT),
- new ValPattern("Total blocking GC time: ", ValPattern.TYPE_TIME),
- new ValPattern("Total mutator paused time: ", ValPattern.TYPE_TIME),
- new ValPattern("Total number of allocations ", ValPattern.TYPE_COUNT),
- new ValPattern("concurrent copying paused: Sum: ", ValPattern.TYPE_TIME),
- new ValPattern("concurrent copying total time: ", ValPattern.TYPE_TIME),
- new ValPattern("concurrent copying freed: ", ValPattern.TYPE_COUNT),
- new ValPattern("Peak regions allocated ", ValPattern.TYPE_COUNT),
- };
-
- void visit(String dumpLine) {
- for (ValPattern p : mPatterns) {
- if (p.visit(dumpLine)) {
- if (DEBUG) {
- Log.i(TAG, " " + p);
- }
- }
- }
- }
-
- GcInfo calculateGcTime(GcStatus newStatus, String title, Bundle result) {
- Log.i(TAG, "===== GC status of " + title + " =====");
- final GcInfo info = new GcInfo();
- for (int i = 0; i < mPatterns.length; i++) {
- final ValPattern p = mPatterns[i];
- if (p.mType == ValPattern.TYPE_COUNT) {
- final int diff = newStatus.mPatterns[i].mIntValue - p.mIntValue;
- Log.i(TAG, " " + p.mRawPattern + diff);
- if (diff > 0) {
- result.putInt("[" + title + "] " + p.mRawPattern, diff);
- }
- continue;
- }
- final float diff = newStatus.mPatterns[i].mFloatValue - p.mFloatValue;
- Log.i(TAG, " " + p.mRawPattern + diff + "ms");
- if (diff > 0) {
- result.putFloat("[" + title + "] " + p.mRawPattern + "(ms)", diff);
- }
- if (i == TOTAL_GC_TIME_INDEX) {
- info.mTotalGcTime = diff;
- } else if (i == TOTAL_GC_PAUSED_TIME_INDEX) {
- info.mTotalGcPausedTime = diff;
- }
- }
- return info;
- }
- }
-
- private static class GcInfo {
- float mTotalGcTime;
- float mTotalGcPausedTime;
- }
-}
+/* + * Copyright (C) 2020 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.android.internal; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import android.app.Activity; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.SystemClock; +import android.util.Log; + +import androidx.test.filters.LargeTest; + +import com.android.internal.util.function.pooled.PooledLambda; +import com.android.internal.util.function.pooled.PooledPredicate; + +import org.junit.Assume; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runners.model.Statement; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** Compares the performance of regular lambda and pooled lambda. */ +@LargeTest +public class LambdaPerfTest { + private static final boolean DEBUG = false; + private static final String TAG = LambdaPerfTest.class.getSimpleName(); + + private static final String LAMBDA_FORM_REGULAR = "regular"; + private static final String LAMBDA_FORM_POOLED = "pooled"; + + private static final int WARMUP_ITERATIONS = 1000; + private static final int TEST_ITERATIONS = 3000000; + private static final int TASK_COUNT = 10; + private static final long DELAY_AFTER_BENCH_MS = 1000; + + private String mMethodName; + + private final Bundle mTestResults = new Bundle(); + private final ArrayList<Task> mTasks = new ArrayList<>(); + + // The member fields are used to ensure lambda capturing. They don't have the actual meaning. + private final Task mTask = new Task(); + private final Rect mBounds = new Rect(); + private int mTaskId; + private long mTime; + private boolean mTop; + + @Rule + public final TestRule mRule = (base, description) -> new Statement() { + @Override + public void evaluate() throws Throwable { + mMethodName = description.getMethodName(); + mTasks.clear(); + for (int i = 0; i < TASK_COUNT; i++) { + final Task t = new Task(); + mTasks.add(t); + } + base.evaluate(); + + getInstrumentation().sendStatus(Activity.RESULT_OK, mTestResults); + } + }; + + @Test + public void test1ParamPredicate() { + evaluate(LAMBDA_FORM_REGULAR, () -> handleTask(t -> t.doSomething(mTaskId, mTime))); + evaluate(LAMBDA_FORM_POOLED, () -> { + final PooledPredicate c = PooledLambda.obtainPredicate(Task::doSomething, + PooledLambda.__(Task.class), mTaskId, mTime); + handleTask(c); + c.recycle(); + }); + } + + @Test + public void test2PrimitiveParamsPredicate() { + // Not in Integer#IntegerCache (-128~127) for autoboxing, that may create new object. + mTaskId = 12345; + mTime = 54321; + + evaluate(LAMBDA_FORM_REGULAR, () -> handleTask(t -> t.doSomething(mTaskId, mTime))); + evaluate(LAMBDA_FORM_POOLED, () -> { + final PooledPredicate c = PooledLambda.obtainPredicate(Task::doSomething, + PooledLambda.__(Task.class), mTaskId, mTime); + handleTask(c); + c.recycle(); + }); + } + + @Test + public void test3ParamsPredicate() { + mTop = true; + // In Integer#IntegerCache. + mTaskId = 10; + + evaluate(LAMBDA_FORM_REGULAR, () -> handleTask(t -> t.doSomething(mBounds, mTop, mTaskId))); + evaluate(LAMBDA_FORM_POOLED, () -> { + final PooledPredicate c = PooledLambda.obtainPredicate(Task::doSomething, + PooledLambda.__(Task.class), mBounds, mTop, mTaskId); + handleTask(c); + c.recycle(); + }); + } + + @Test + public void testMessage() { + evaluate(LAMBDA_FORM_REGULAR, () -> { + final Message m = Message.obtain().setCallback(() -> mTask.doSomething(mTaskId, mTime)); + m.getCallback().run(); + m.recycle(); + }); + evaluate(LAMBDA_FORM_POOLED, () -> { + final Message m = PooledLambda.obtainMessage(Task::doSomething, mTask, mTaskId, mTime); + m.getCallback().run(); + m.recycle(); + }); + } + + @Test + public void testRunnable() { + evaluate(LAMBDA_FORM_REGULAR, () -> { + final Runnable r = mTask::doSomething; + r.run(); + }); + evaluate(LAMBDA_FORM_POOLED, () -> { + final Runnable r = PooledLambda.obtainRunnable(Task::doSomething, mTask).recycleOnUse(); + r.run(); + }); + } + + @Test + public void testMultiThread() { + final int numThread = 3; + + final Runnable regularAction = () -> handleTask(t -> t.doSomething(mTaskId, mTime)); + final Runnable[] regularActions = new Runnable[numThread]; + Arrays.fill(regularActions, regularAction); + evaluateMultiThread(LAMBDA_FORM_REGULAR, regularActions); + + final Runnable pooledAction = () -> { + final PooledPredicate c = PooledLambda.obtainPredicate(Task::doSomething, + PooledLambda.__(Task.class), mTaskId, mTime); + handleTask(c); + c.recycle(); + }; + final Runnable[] pooledActions = new Runnable[numThread]; + Arrays.fill(pooledActions, pooledAction); + evaluateMultiThread(LAMBDA_FORM_POOLED, pooledActions); + } + + private void handleTask(Predicate<Task> callback) { + for (int i = mTasks.size() - 1; i >= 0; i--) { + final Task task = mTasks.get(i); + if (callback.test(task)) { + return; + } + } + } + + private void evaluate(String title, Runnable action) { + for (int i = 0; i < WARMUP_ITERATIONS; i++) { + action.run(); + } + performGc(); + + final GcStatus startGcStatus = getGcStatus(); + final long startTime = SystemClock.elapsedRealtime(); + for (int i = 0; i < TEST_ITERATIONS; i++) { + action.run(); + } + evaluateResult(title, startGcStatus, startTime); + } + + private void evaluateMultiThread(String title, Runnable[] actions) { + performGc(); + + final CountDownLatch latch = new CountDownLatch(actions.length); + final GcStatus startGcStatus = getGcStatus(); + final long startTime = SystemClock.elapsedRealtime(); + for (Runnable action : actions) { + new Thread() { + @Override + public void run() { + for (int i = 0; i < TEST_ITERATIONS; i++) { + action.run(); + } + latch.countDown(); + }; + }.start(); + } + try { + latch.await(); + } catch (InterruptedException ignored) { + } + evaluateResult(title, startGcStatus, startTime); + } + + private void evaluateResult(String title, GcStatus startStatus, long startTime) { + final float elapsed = SystemClock.elapsedRealtime() - startTime; + // Sleep a while to see if GC may happen. + SystemClock.sleep(DELAY_AFTER_BENCH_MS); + final GcStatus endStatus = getGcStatus(); + final GcInfo info = startStatus.calculateGcTime(endStatus, title, mTestResults); + mTestResults.putFloat("[" + title + "-execution-time]", elapsed); + Log.i(TAG, mMethodName + "_" + title + " execution time: " + + elapsed + "ms (avg=" + String.format("%.5f", elapsed / TEST_ITERATIONS) + "ms)" + + " GC time: " + String.format("%.3f", info.mTotalGcTime) + "ms" + + " GC paused time: " + String.format("%.3f", info.mTotalGcPausedTime) + "ms"); + } + + /** Cleans the test environment. */ + private static void performGc() { + System.gc(); + System.runFinalization(); + System.gc(); + } + + private static GcStatus getGcStatus() { + if (DEBUG) { + Log.i(TAG, "===== Read GC dump ====="); + } + final GcStatus status = new GcStatus(); + final List<String> vmDump = getVmDump(); + Assume.assumeFalse("VM dump is empty", vmDump.isEmpty()); + for (String line : vmDump) { + status.visit(line); + if (line.startsWith("DALVIK THREADS")) { + break; + } + } + return status; + } + + private static List<String> getVmDump() { + final int myPid = Process.myPid(); + // Another approach Debug#dumpJavaBacktraceToFileTimeout requires setenforce 0. + Process.sendSignal(myPid, Process.SIGNAL_QUIT); + // Give a chance to handle the signal. + SystemClock.sleep(100); + + String dump = null; + final String pattern = myPid + " written to: "; + final List<String> logs = shell("logcat -v brief -d tombstoned:I *:S"); + for (int i = logs.size() - 1; i >= 0; i--) { + final String log = logs.get(i); + // Log pattern: Traces for pid 9717 written to: /data/anr/trace_07 + final int pos = log.indexOf(pattern); + if (pos > 0) { + dump = log.substring(pattern.length() + pos); + if (!dump.startsWith("/data/anr/")) { + dump = "/data/anr/" + dump; + } + break; + } + } + + Assume.assumeNotNull("Unable to find VM dump", dump); + // It requires system or root uid to read the trace. + return shell("cat " + dump); + } + + private static List<String> shell(String command) { + final ParcelFileDescriptor.AutoCloseInputStream stream = + new ParcelFileDescriptor.AutoCloseInputStream( + getInstrumentation().getUiAutomation().executeShellCommand(command)); + final ArrayList<String> lines = new ArrayList<>(); + try (BufferedReader br = new BufferedReader(new InputStreamReader(stream))) { + String line; + while ((line = br.readLine()) != null) { + lines.add(line); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return lines; + } + + /** An empty class which provides some methods with different type arguments. */ + static class Task { + void doSomething() { + } + + boolean doSomething(int taskId, long time) { + return false; + } + + boolean doSomething(Rect bounds, boolean top, int taskId) { + return false; + } + } + + static class ValPattern { + static final int TYPE_COUNT = 0; + static final int TYPE_TIME = 1; + static final String PATTERN_COUNT = "(\\d+)"; + static final String PATTERN_TIME = "(\\d+\\.?\\d+)(\\w+)"; + final String mRawPattern; + final Pattern mPattern; + final int mType; + + int mIntValue; + float mFloatValue; + + ValPattern(String p, int type) { + mRawPattern = p; + mPattern = Pattern.compile( + p + (type == TYPE_TIME ? PATTERN_TIME : PATTERN_COUNT) + ".*"); + mType = type; + } + + boolean visit(String line) { + final Matcher matcher = mPattern.matcher(line); + if (!matcher.matches()) { + return false; + } + final String value = matcher.group(1); + if (value == null) { + return false; + } + if (mType == TYPE_COUNT) { + mIntValue = Integer.parseInt(value); + return true; + } + final float time = Float.parseFloat(value); + final String unit = matcher.group(2); + if (unit == null) { + return false; + } + // Refer to art/libartbase/base/time_utils.cc + switch (unit) { + case "s": + mFloatValue = time * 1000; + break; + case "ms": + mFloatValue = time; + break; + case "us": + mFloatValue = time / 1000; + break; + case "ns": + mFloatValue = time / 1000 / 1000; + break; + default: + throw new IllegalArgumentException(); + } + + return true; + } + + @Override + public String toString() { + return mRawPattern + (mType == TYPE_TIME ? (mFloatValue + "ms") : mIntValue); + } + } + + /** Parses the dump pattern of Heap::DumpGcPerformanceInfo. */ + private static class GcStatus { + private static final int TOTAL_GC_TIME_INDEX = 1; + private static final int TOTAL_GC_PAUSED_TIME_INDEX = 5; + + // Refer to art/runtime/gc/heap.cc + final ValPattern[] mPatterns = { + new ValPattern("Total GC count: ", ValPattern.TYPE_COUNT), + new ValPattern("Total GC time: ", ValPattern.TYPE_TIME), + new ValPattern("Total time waiting for GC to complete: ", ValPattern.TYPE_TIME), + new ValPattern("Total blocking GC count: ", ValPattern.TYPE_COUNT), + new ValPattern("Total blocking GC time: ", ValPattern.TYPE_TIME), + new ValPattern("Total mutator paused time: ", ValPattern.TYPE_TIME), + new ValPattern("Total number of allocations ", ValPattern.TYPE_COUNT), + new ValPattern("concurrent copying paused: Sum: ", ValPattern.TYPE_TIME), + new ValPattern("concurrent copying total time: ", ValPattern.TYPE_TIME), + new ValPattern("concurrent copying freed: ", ValPattern.TYPE_COUNT), + new ValPattern("Peak regions allocated ", ValPattern.TYPE_COUNT), + }; + + void visit(String dumpLine) { + for (ValPattern p : mPatterns) { + if (p.visit(dumpLine)) { + if (DEBUG) { + Log.i(TAG, " " + p); + } + } + } + } + + GcInfo calculateGcTime(GcStatus newStatus, String title, Bundle result) { + Log.i(TAG, "===== GC status of " + title + " ====="); + final GcInfo info = new GcInfo(); + for (int i = 0; i < mPatterns.length; i++) { + final ValPattern p = mPatterns[i]; + if (p.mType == ValPattern.TYPE_COUNT) { + final int diff = newStatus.mPatterns[i].mIntValue - p.mIntValue; + Log.i(TAG, " " + p.mRawPattern + diff); + if (diff > 0) { + result.putInt("[" + title + "] " + p.mRawPattern, diff); + } + continue; + } + final float diff = newStatus.mPatterns[i].mFloatValue - p.mFloatValue; + Log.i(TAG, " " + p.mRawPattern + diff + "ms"); + if (diff > 0) { + result.putFloat("[" + title + "] " + p.mRawPattern + "(ms)", diff); + } + if (i == TOTAL_GC_TIME_INDEX) { + info.mTotalGcTime = diff; + } else if (i == TOTAL_GC_PAUSED_TIME_INDEX) { + info.mTotalGcPausedTime = diff; + } + } + return info; + } + } + + private static class GcInfo { + float mTotalGcTime; + float mTotalGcPausedTime; + } +} diff --git a/tests/componentalias/Android.bp b/tests/componentalias/Android.bp index e5eb3c7b6394..7af76e1144f8 100644 --- a/tests/componentalias/Android.bp +++ b/tests/componentalias/Android.bp @@ -16,6 +16,9 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], } +// TODO: Delete this file. It's no longer needed, but removing it on udc-dev will cause +// a conflict on master. + java_defaults { name: "ComponentAliasTests_defaults", static_libs: [ @@ -34,54 +37,3 @@ java_defaults { ], platform_apis: true, // We use hidden APIs in the test. } - -// We build three APKs from the exact same source files, so these APKs contain the exact same tests. -// And we run the tests on each APK, so that we can test various situations: -// - When the alias is in the same package, target in the same package. -// - When the alias is in the same package, target in another package. -// - When the alias is in another package, which also contains the target. -// - When the alias is in another package, and the target is in yet another package. -// etc etc... - -android_test { - name: "ComponentAliasTests", - defaults: [ - "ComponentAliasTests_defaults", - ], - package_name: "android.content.componentalias.tests", - manifest: "AndroidManifest.xml", - additional_manifests: [ - "AndroidManifest_main.xml", - "AndroidManifest_service_aliases.xml", - "AndroidManifest_service_targets.xml", - ], - test_config_template: "AndroidTest-template.xml", -} - -android_test { - name: "ComponentAliasTests1", - defaults: [ - "ComponentAliasTests_defaults", - ], - package_name: "android.content.componentalias.tests.sub1", - manifest: "AndroidManifest.xml", - additional_manifests: [ - "AndroidManifest_sub1.xml", - "AndroidManifest_service_targets.xml", - ], - test_config_template: "AndroidTest-template.xml", -} - -android_test { - name: "ComponentAliasTests2", - defaults: [ - "ComponentAliasTests_defaults", - ], - package_name: "android.content.componentalias.tests.sub2", - manifest: "AndroidManifest.xml", - additional_manifests: [ - "AndroidManifest_sub2.xml", - "AndroidManifest_service_targets.xml", - ], - test_config_template: "AndroidTest-template.xml", -} diff --git a/tests/componentalias/AndroidManifest_main.xml b/tests/componentalias/AndroidManifest_main.xml deleted file mode 100755 index 70e817ebf3e7..000000000000 --- a/tests/componentalias/AndroidManifest_main.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (C) 2021 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. - --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.content.componentalias.tests" > - - <application> - </application> - - <instrumentation - android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="android.content.componentalias.tests" > - </instrumentation> -</manifest> diff --git a/tests/componentalias/AndroidManifest_service_aliases.xml b/tests/componentalias/AndroidManifest_service_aliases.xml deleted file mode 100644 index c96f1736c684..000000000000 --- a/tests/componentalias/AndroidManifest_service_aliases.xml +++ /dev/null @@ -1,81 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (C) 2021 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. - --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.content.componentalias.tests" > - <application> - <!-- - Note the alias components are essentially just placeholders, so the APKs don't have to - have the implementation classes. - --> - <service android:name=".s.Alias00" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests/android.content.componentalias.tests.s.Target00" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_00" /></intent-filter> - </service> - <service android:name=".s.Alias01" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.s.Target01" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_01" /></intent-filter> - </service> - <service android:name=".s.Alias02" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.s.Target02" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_02" /></intent-filter> - </service> - <service android:name=".s.Alias03" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.s.Target03" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_03" /></intent-filter> - </service> - <service android:name=".s.Alias04" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.s.Target04" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_ALIAS_04" /></intent-filter> - </service> - - <receiver android:name=".b.Alias00" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests/android.content.componentalias.tests.b.Target00" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_00" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - <receiver android:name=".b.Alias01" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.b.Target01" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_01" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - <receiver android:name=".b.Alias02" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.b.Target02" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_02" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - <receiver android:name=".b.Alias03" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub1/android.content.componentalias.tests.b.Target03" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_03" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - <receiver android:name=".b.Alias04" android:exported="true" android:enabled="true" > - <meta-data android:name="alias_target" android:value="android.content.componentalias.tests.sub2/android.content.componentalias.tests.b.Target04" /> - <intent-filter><action android:name="com.android.intent.action.EXPERIMENTAL_IS_ALIAS" /></intent-filter> - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_04" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - </application> -</manifest> diff --git a/tests/componentalias/AndroidManifest_service_targets.xml b/tests/componentalias/AndroidManifest_service_targets.xml deleted file mode 100644 index 24c0432bcf4c..000000000000 --- a/tests/componentalias/AndroidManifest_service_targets.xml +++ /dev/null @@ -1,57 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (C) 2021 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. - --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.content.componentalias.tests" > - <application> - <service android:name=".s.Target00" android:exported="true" android:enabled="true" > - </service> - <service android:name=".s.Target01" android:exported="true" android:enabled="true" > - </service> - <service android:name=".s.Target02" android:exported="true" android:enabled="true" > - </service> - <service android:name=".s.Target03" android:exported="true" android:enabled="true" > - </service> - <service android:name=".s.Target04" android:exported="true" android:enabled="true" > - </service> - - <!-- - Due to http://go/intents-match-intent-filters-guide, the target intent has to have - an intent filter that matches the original intent. (modulo the package name) - This restriction shouldn't exist in the final version. - --> - <receiver android:name=".b.Target00" android:exported="true" android:enabled="true" > - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_00" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - <receiver android:name=".b.Target01" android:exported="true" android:enabled="true" > - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_01" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - <receiver android:name=".b.Target02" android:exported="true" android:enabled="true" > - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_02" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - <receiver android:name=".b.Target03" android:exported="true" android:enabled="true" > - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_03" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - <receiver android:name=".b.Target04" android:exported="true" android:enabled="true" > - <intent-filter><action android:name="android.content.componentalias.tests.IS_RECEIVER_04" /></intent-filter> - <intent-filter><action android:name="ACTION_BROADCAST" /></intent-filter> - </receiver> - </application> -</manifest> diff --git a/tests/componentalias/AndroidManifest_sub1.xml b/tests/componentalias/AndroidManifest_sub1.xml deleted file mode 100755 index 21616f5edf00..000000000000 --- a/tests/componentalias/AndroidManifest_sub1.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (C) 2021 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. - --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.content.componentalias.tests" > - - <application> - </application> - - <instrumentation - android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="android.content.componentalias.tests.sub1" > - </instrumentation> -</manifest> diff --git a/tests/componentalias/AndroidManifest_sub2.xml b/tests/componentalias/AndroidManifest_sub2.xml deleted file mode 100755 index c11b0cd55ef4..000000000000 --- a/tests/componentalias/AndroidManifest_sub2.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - * Copyright (C) 2021 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. - --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.content.componentalias.tests" > - - <application> - </application> - - <instrumentation - android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="android.content.componentalias.tests.sub2" > - </instrumentation> -</manifest> diff --git a/tests/componentalias/AndroidTest-template.xml b/tests/componentalias/AndroidTest-template.xml deleted file mode 100644 index afdfe79ea4a4..000000000000 --- a/tests/componentalias/AndroidTest-template.xml +++ /dev/null @@ -1,38 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2021 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. ---> -<configuration> - <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> - <option name="cleanup-apks" value="true" /> - <option name="test-file-name" value="ComponentAliasTests.apk" /> - <option name="test-file-name" value="ComponentAliasTests1.apk" /> - <option name="test-file-name" value="ComponentAliasTests2.apk" /> - </target_preparer> - <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> - <!-- Exempt the helper APKs from the BG restriction, so they can start BG services. --> - <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests" /> - <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests.sub1" /> - <option name="run-command" value="cmd deviceidle whitelist +android.content.componentalias.tests.sub2" /> - - <option name="teardown-command" value="cmd deviceidle whitelist -android.content.componentalias.tests" /> - <option name="teardown-command" value="cmd deviceidle whitelist -android.content.componentalias.tests.sub1" /> - <option name="teardown-command" value="cmd deviceidle whitelist -android.content.componentalias.tests.sub2" /> - </target_preparer> - <test class="com.android.tradefed.testtype.AndroidJUnitTest" > - <option name="package" value="{PACKAGE}" /> - <option name="runtime-hint" value="2m" /> - <option name="isolated-storage" value="false" /> - </test> -</configuration> diff --git a/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java b/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java deleted file mode 100644 index 99322ee46106..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2021 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 android.content.componentalias.tests; - -import android.content.ComponentName; -import android.content.Context; -import android.os.Build; -import android.provider.DeviceConfig; -import android.util.Log; - -import androidx.test.InstrumentationRegistry; - -import com.android.compatibility.common.util.DeviceConfigStateHelper; -import com.android.compatibility.common.util.ShellUtils; -import com.android.compatibility.common.util.TestUtils; - -import org.junit.AfterClass; -import org.junit.Assume; -import org.junit.Before; - -import java.util.function.Consumer; - -public class BaseComponentAliasTest { - protected static final Context sContext = InstrumentationRegistry.getTargetContext(); - - protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper( - DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS); - @Before - public void enableComponentAliasWithCompatFlag() throws Exception { - Assume.assumeTrue(Build.isDebuggable()); - ShellUtils.runShellCommand( - "am compat enable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android"); - sDeviceConfig.set("enable_experimental_component_alias", ""); - sDeviceConfig.set("component_alias_overrides", ""); - - // Make sure the feature is actually enabled, and the aliases are loaded. - TestUtils.waitUntil("Wait until component alias is actually enabled", () -> { - String out = ShellUtils.runShellCommand("dumpsys activity component-alias"); - - return out.contains("Enabled: true") - && out.contains("android.content.componentalias.tests/.b.Alias04") - && out.contains("android.content.componentalias.tests/.s.Alias04"); - }); - ShellUtils.runShellCommand("am wait-for-broadcast-idle"); - } - - @AfterClass - public static void restoreDeviceConfig() throws Exception { - ShellUtils.runShellCommand( - "am compat disable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android"); - sDeviceConfig.close(); - } - - protected static void log(String message) { - Log.i(ComponentAliasTestCommon.TAG, "[" + sContext.getPackageName() + "] " + message); - } - - /** - * Defines a test target. - */ - public static class Combo { - public final ComponentName alias; - public final ComponentName target; - public final String action; - - public Combo(ComponentName alias, ComponentName target, String action) { - this.alias = alias; - this.target = target; - this.action = action; - } - - @Override - public String toString() { - return "Combo{" - + "alias=" + toString(alias) - + ", target=" + toString(target) - + ", action='" + action + '\'' - + '}'; - } - - private static String toString(ComponentName cn) { - return cn == null ? "[null]" : cn.flattenToShortString(); - } - - public void apply(Consumer<Combo> callback) { - log("Testing for: " + this); - callback.accept(this); - } - } -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java deleted file mode 100644 index 7d5e0b9c6d8a..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2021 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 android.content.componentalias.tests; - -import static android.content.componentalias.tests.ComponentAliasTestCommon.MAIN_PACKAGE; -import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB1_PACKAGE; -import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB2_PACKAGE; -import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.ComponentName; -import android.content.Intent; - -import com.android.compatibility.common.util.BroadcastMessenger.Receiver; - -import org.junit.Test; - -import java.util.function.Consumer; - -public class ComponentAliasBroadcastTest extends BaseComponentAliasTest { - private void forEachCombo(Consumer<Combo> callback) { - new Combo( - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Alias00"), - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Target00"), - MAIN_PACKAGE + ".IS_RECEIVER_00").apply(callback); - - new Combo( - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Alias01"), - new ComponentName(SUB1_PACKAGE, MAIN_PACKAGE + ".b.Target01"), - MAIN_PACKAGE + ".IS_RECEIVER_01").apply(callback); - new Combo( - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".b.Alias02"), - new ComponentName(SUB2_PACKAGE, MAIN_PACKAGE + ".b.Target02"), - MAIN_PACKAGE + ".IS_RECEIVER_02").apply(callback); - } - - @Test - public void testBroadcast_explicitComponentName() { - forEachCombo((c) -> { - Intent i = new Intent().setComponent(c.alias); - i.setAction("ACTION_BROADCAST"); - ComponentAliasMessage m; - - try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) { - log("Sending: " + i); - sContext.sendBroadcast(i); - - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onReceive"); - assertThat(m.getSenderIdentity()).isEqualTo(c.target.flattenToShortString()); - - // The broadcast intent will always have the receiving component name set. - assertThat(m.getIntent().getComponent()).isEqualTo(c.target); - - receiver.ensureNoMoreMessages(); - } - }); - } - - @Test - public void testBroadcast_explicitPackageName() { - forEachCombo((c) -> { - // In this test, we only set the package name to the intent. - // If the alias and target are the same package, the intent will be sent to both of them - // *and* the one to the alias is redirected to the target, so the target will receive - // the intent twice. This case is haled at *1 below. - - - Intent i = new Intent().setPackage(c.alias.getPackageName()); - i.setAction(c.action); - ComponentAliasMessage m; - - try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) { - log("Sending broadcast: " + i); - sContext.sendBroadcast(i); - - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onReceive"); - assertThat(m.getSenderIdentity()).isEqualTo(c.target.flattenToShortString()); - assertThat(m.getIntent().getComponent()).isEqualTo(c.target); - - // *1 -- if the alias and target are in the same package, we expect one more - // message. - if (c.alias.getPackageName().equals(c.target.getPackageName())) { - m = receiver.waitForNextMessage(); - assertThat(m.getMethodName()).isEqualTo("onReceive"); - assertThat(m.getSenderIdentity()).isEqualTo(c.target.flattenToShortString()); - assertThat(m.getIntent().getComponent()).isEqualTo(c.target); - } - receiver.ensureNoMoreMessages(); - } - }); - } -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java deleted file mode 100644 index ee20379d971a..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasEnableWithDeviceConfigTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 android.content.componentalias.tests; - -import android.os.Build; -import android.provider.DeviceConfig; - -import com.android.compatibility.common.util.DeviceConfigStateHelper; -import com.android.compatibility.common.util.ShellUtils; -import com.android.compatibility.common.util.TestUtils; - -import org.junit.AfterClass; -import org.junit.Assume; -import org.junit.Test; - -public class ComponentAliasEnableWithDeviceConfigTest { - protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper( - DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS); - - @AfterClass - public static void restoreDeviceConfig() throws Exception { - sDeviceConfig.close(); - } - - @Test - public void enableComponentAliasWithCompatFlag() throws Exception { - Assume.assumeTrue(Build.isDebuggable()); - - sDeviceConfig.set("component_alias_overrides", ""); - - // First, disable with both compat-id and device config. - ShellUtils.runShellCommand( - "am compat disable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android"); - sDeviceConfig.set("enable_experimental_component_alias", ""); - - TestUtils.waitUntil("Wait until component alias is actually enabled", () -> { - return ShellUtils.runShellCommand("dumpsys activity component-alias") - .indexOf("Enabled: false") > 0; - }); - - // Then, enable by device config. - sDeviceConfig.set("enable_experimental_component_alias", "true"); - - // Make sure the feature is actually enabled. - TestUtils.waitUntil("Wait until component alias is actually enabled", () -> { - return ShellUtils.runShellCommand("dumpsys activity component-alias") - .indexOf("Enabled: true") > 0; - }); - } -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java deleted file mode 100644 index d41696f27880..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2021 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 android.content.componentalias.tests; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.ComponentName; -import android.content.Intent; -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.internal.util.DataClass; - -/** - * Parcelabe containing a "message" that's meant to be delivered via BroadcastMessenger. - * - * To add a new field, just add a private member field, and run: - * codegen $ANDROID_BUILD_TOP/frameworks/base/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java - */ -@DataClass( - genConstructor = false, - genSetters = true, - genToString = true, - genAidl = false) -public final class ComponentAliasMessage implements Parcelable { - public ComponentAliasMessage() { - } - - @Nullable - private String mMessage; - - @Nullable - private String mMethodName; - - @Nullable - private String mSenderIdentity; - - @Nullable - private Intent mIntent; - - @Nullable - private ComponentName mComponent; - - - - // Code below generated by codegen v1.0.23. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - - - @DataClass.Generated.Member - public @Nullable String getMessage() { - return mMessage; - } - - @DataClass.Generated.Member - public @Nullable String getMethodName() { - return mMethodName; - } - - @DataClass.Generated.Member - public @Nullable String getSenderIdentity() { - return mSenderIdentity; - } - - @DataClass.Generated.Member - public @Nullable Intent getIntent() { - return mIntent; - } - - @DataClass.Generated.Member - public @Nullable ComponentName getComponent() { - return mComponent; - } - - @DataClass.Generated.Member - public @NonNull ComponentAliasMessage setMessage(@NonNull String value) { - mMessage = value; - return this; - } - - @DataClass.Generated.Member - public @NonNull ComponentAliasMessage setMethodName(@NonNull String value) { - mMethodName = value; - return this; - } - - @DataClass.Generated.Member - public @NonNull ComponentAliasMessage setSenderIdentity(@NonNull String value) { - mSenderIdentity = value; - return this; - } - - @DataClass.Generated.Member - public @NonNull ComponentAliasMessage setIntent(@NonNull Intent value) { - mIntent = value; - return this; - } - - @DataClass.Generated.Member - public @NonNull ComponentAliasMessage setComponent(@NonNull ComponentName value) { - mComponent = value; - return this; - } - - @Override - @DataClass.Generated.Member - public String toString() { - // You can override field toString logic by defining methods like: - // String fieldNameToString() { ... } - - return "ComponentAliasMessage { " + - "message = " + mMessage + ", " + - "methodName = " + mMethodName + ", " + - "senderIdentity = " + mSenderIdentity + ", " + - "intent = " + mIntent + ", " + - "component = " + mComponent + - " }"; - } - - @Override - @DataClass.Generated.Member - public void writeToParcel(@NonNull Parcel dest, int flags) { - // You can override field parcelling by defining methods like: - // void parcelFieldName(Parcel dest, int flags) { ... } - - byte flg = 0; - if (mMessage != null) flg |= 0x1; - if (mMethodName != null) flg |= 0x2; - if (mSenderIdentity != null) flg |= 0x4; - if (mIntent != null) flg |= 0x8; - if (mComponent != null) flg |= 0x10; - dest.writeByte(flg); - if (mMessage != null) dest.writeString(mMessage); - if (mMethodName != null) dest.writeString(mMethodName); - if (mSenderIdentity != null) dest.writeString(mSenderIdentity); - if (mIntent != null) dest.writeTypedObject(mIntent, flags); - if (mComponent != null) dest.writeTypedObject(mComponent, flags); - } - - @Override - @DataClass.Generated.Member - public int describeContents() { return 0; } - - /** @hide */ - @SuppressWarnings({"unchecked", "RedundantCast"}) - @DataClass.Generated.Member - /* package-private */ ComponentAliasMessage(@NonNull Parcel in) { - // You can override field unparcelling by defining methods like: - // static FieldType unparcelFieldName(Parcel in) { ... } - - byte flg = in.readByte(); - String message = (flg & 0x1) == 0 ? null : in.readString(); - String methodName = (flg & 0x2) == 0 ? null : in.readString(); - String senderIdentity = (flg & 0x4) == 0 ? null : in.readString(); - Intent intent = (flg & 0x8) == 0 ? null : (Intent) in.readTypedObject(Intent.CREATOR); - ComponentName component = (flg & 0x10) == 0 ? null : (ComponentName) in.readTypedObject(ComponentName.CREATOR); - - this.mMessage = message; - this.mMethodName = methodName; - this.mSenderIdentity = senderIdentity; - this.mIntent = intent; - this.mComponent = component; - - // onConstructed(); // You can define this method to get a callback - } - - @DataClass.Generated.Member - public static final @NonNull Parcelable.Creator<ComponentAliasMessage> CREATOR - = new Parcelable.Creator<ComponentAliasMessage>() { - @Override - public ComponentAliasMessage[] newArray(int size) { - return new ComponentAliasMessage[size]; - } - - @Override - public ComponentAliasMessage createFromParcel(@NonNull Parcel in) { - return new ComponentAliasMessage(in); - } - }; - - @DataClass.Generated( - time = 1630098801203L, - codegenVersion = "1.0.23", - sourceFile = "frameworks/base/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java", - inputSignatures = "private @android.annotation.Nullable java.lang.String mMessage\nprivate @android.annotation.Nullable java.lang.String mMethodName\nprivate @android.annotation.Nullable java.lang.String mSenderIdentity\nprivate @android.annotation.Nullable android.content.Intent mIntent\nprivate @android.annotation.Nullable android.content.ComponentName mComponent\nclass ComponentAliasMessage extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true, genToString=true, genAidl=false)") - @Deprecated - private void __metadata() {} - - - //@formatter:on - // End of generated code - -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java deleted file mode 100644 index 0899886fe951..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasNotSupportedOnUserBuildTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 android.content.componentalias.tests; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.Build; -import android.provider.DeviceConfig; - -import com.android.compatibility.common.util.DeviceConfigStateHelper; -import com.android.compatibility.common.util.ShellUtils; - -import org.junit.AfterClass; -import org.junit.Assume; -import org.junit.Test; - -/** - * Test to make sure component-alias can't be enabled on user builds. - */ -public class ComponentAliasNotSupportedOnUserBuildTest { - protected static final DeviceConfigStateHelper sDeviceConfig = new DeviceConfigStateHelper( - DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS); - - @AfterClass - public static void restoreDeviceConfig() throws Exception { - sDeviceConfig.close(); - } - - @Test - public void enableComponentAliasWithCompatFlag() throws Exception { - Assume.assumeFalse(Build.isDebuggable()); - - // Try to enable it by both the device config and compat-id. - sDeviceConfig.set("enable_experimental_component_alias", "true"); - ShellUtils.runShellCommand( - "am compat enable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android"); - - // Sleep for an arbitrary amount of time, so the config would sink in, if there was - // no "not on user builds" check. - - Thread.sleep(5000); - - // Make sure the feature is still disabled. - assertThat(ShellUtils.runShellCommand("dumpsys activity component-alias") - .indexOf("Enabled: false") > 0).isTrue(); - } -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java deleted file mode 100644 index f0ff088815af..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (C) 2021 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 android.content.componentalias.tests; - -import static android.content.Context.BIND_AUTO_CREATE; -import static android.content.componentalias.tests.ComponentAliasTestCommon.MAIN_PACKAGE; -import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB1_PACKAGE; -import static android.content.componentalias.tests.ComponentAliasTestCommon.SUB2_PACKAGE; -import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG; - -import static com.google.common.truth.Truth.assertThat; - -import static org.hamcrest.core.IsNot.not; - -import android.content.ComponentName; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; - -import com.android.compatibility.common.util.BroadcastMessenger; -import com.android.compatibility.common.util.BroadcastMessenger.Receiver; -import com.android.compatibility.common.util.ShellUtils; -import com.android.compatibility.common.util.TestUtils; - -import org.junit.Assume; -import org.junit.Test; - -import java.util.function.Consumer; - -/** - * Test for the experimental "Component alias" feature. - * - * Note this test exercises the relevant APIs, but don't actually check if the aliases are - * resolved. - * - * Note all the helper APKs are battery-exempted (via AndroidTest.xml), so they can run - * BG services. - */ -public class ComponentAliasServiceTest extends BaseComponentAliasTest { - /** - * Service connection used throughout the tests. It sends a message for each callback via - * the messenger. - */ - private static final ServiceConnection sServiceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - log("onServiceConnected: " + name); - - ComponentAliasMessage m = new ComponentAliasMessage() - .setSenderIdentity("sServiceConnection") - .setMethodName("onServiceConnected") - .setComponent(name); - - BroadcastMessenger.send(sContext, TAG, m); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - log("onServiceDisconnected: " + name); - - ComponentAliasMessage m = new ComponentAliasMessage() - .setSenderIdentity("sServiceConnection") - .setMethodName("onServiceDisconnected") - .setComponent(name); - - BroadcastMessenger.send(sContext, TAG, m); - } - - @Override - public void onBindingDied(ComponentName name) { - log("onBindingDied: " + name); - - ComponentAliasMessage m = new ComponentAliasMessage() - .setSenderIdentity("sServiceConnection") - .setMethodName("onBindingDied"); - - BroadcastMessenger.send(sContext, TAG, m); - } - - @Override - public void onNullBinding(ComponentName name) { - log("onNullBinding: " + name); - - ComponentAliasMessage m = new ComponentAliasMessage() - .setSenderIdentity("sServiceConnection") - .setMethodName("onNullBinding"); - - BroadcastMessenger.send(sContext, TAG, m); - } - }; - - private void testStartAndStopService_common( - Intent originalIntent, - ComponentName componentNameForClient, - ComponentName componentNameForTarget) { - - ComponentAliasMessage m; - - try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) { - // Start the service. - ComponentName result = sContext.startService(originalIntent); - assertThat(result).isEqualTo(componentNameForClient); - - // Check - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onStartCommand"); - // The app sees the rewritten intent. - assertThat(m.getIntent().getComponent()).isEqualTo(componentNameForTarget); - - // Verify the original intent. - assertThat(m.getIntent().getOriginalIntent().getComponent()) - .isEqualTo(originalIntent.getComponent()); - assertThat(m.getIntent().getOriginalIntent().getPackage()) - .isEqualTo(originalIntent.getPackage()); - - // Stop the service. - sContext.stopService(originalIntent); - - // Check - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onDestroy"); - - receiver.ensureNoMoreMessages(); - } - } - - private void forEachCombo(Consumer<Combo> callback) { - new Combo( - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias00"), - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Target00"), - MAIN_PACKAGE + ".IS_ALIAS_00").apply(callback); - new Combo( - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias01"), - new ComponentName(SUB1_PACKAGE, MAIN_PACKAGE + ".s.Target01"), - MAIN_PACKAGE + ".IS_ALIAS_01").apply(callback); - new Combo( - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias02"), - new ComponentName(SUB2_PACKAGE, MAIN_PACKAGE + ".s.Target02"), - MAIN_PACKAGE + ".IS_ALIAS_02").apply(callback); - } - - @Test - public void testStartAndStopService_explicitComponentName() { - forEachCombo((c) -> { - Intent i = new Intent().setComponent(c.alias); - testStartAndStopService_common(i, c.alias, c.target); - }); - } - - @Test - public void testStartAndStopService_explicitPackageName() { - forEachCombo((c) -> { - Intent i = new Intent().setPackage(c.alias.getPackageName()); - i.setAction(c.action); - - testStartAndStopService_common(i, c.alias, c.target); - }); - } - - @Test - public void testStartAndStopService_override() throws Exception { - Intent i = new Intent().setPackage(MAIN_PACKAGE); - i.setAction(MAIN_PACKAGE + ".IS_ALIAS_01"); - - // Change some of the aliases from what's defined in <meta-data>. - - ComponentName aliasA = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias01"); - ComponentName targetA = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Target02"); - - ComponentName aliasB = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias02"); - ComponentName targetB = new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Target01"); - - sDeviceConfig.set("component_alias_overrides", - aliasA.flattenToShortString() + ":" + targetA.flattenToShortString() - + "," - + aliasB.flattenToShortString() + ":" + targetB.flattenToShortString()); - - TestUtils.waitUntil("Wait until component alias is actually enabled", () -> { - return ShellUtils.runShellCommand("dumpsys activity component-alias") - .indexOf(aliasA.flattenToShortString() - + " -> " + targetA.flattenToShortString()) > 0; - }); - - - testStartAndStopService_common(i, aliasA, targetA); - } - - private void testBindAndUnbindService_common( - Intent originalIntent, - ComponentName componentNameForClient, - ComponentName componentNameForTarget) { - ComponentAliasMessage m; - - try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) { - // Bind to the service. - assertThat(sContext.bindService( - originalIntent, sServiceConnection, BIND_AUTO_CREATE)).isTrue(); - - // Check the target side behavior. - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onBind"); - // The app sees the rewritten intent. - assertThat(m.getIntent().getComponent()).isEqualTo(componentNameForTarget); - - // Verify the original intent. - assertThat(m.getIntent().getOriginalIntent().getComponent()) - .isEqualTo(originalIntent.getComponent()); - assertThat(m.getIntent().getOriginalIntent().getPackage()) - .isEqualTo(originalIntent.getPackage()); - - // Check the client side behavior. - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onServiceConnected"); - // The app sees the rewritten intent. - assertThat(m.getComponent()).isEqualTo(componentNameForClient); - - // Unbind. - sContext.unbindService(sServiceConnection); - - // Check the target side behavior. - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onDestroy"); - - // Note onServiceDisconnected() won't be called in this case. - receiver.ensureNoMoreMessages(); - } - } - - @Test - public void testBindService_explicitComponentName() { - forEachCombo((c) -> { - Intent i = new Intent().setComponent(c.alias); - - testBindAndUnbindService_common(i, c.alias, c.target); - }); - - } - - @Test - public void testBindService_explicitPackageName() { - forEachCombo((c) -> { - Intent i = new Intent().setPackage(c.alias.getPackageName()); - i.setAction(c.action); - - testBindAndUnbindService_common(i, c.alias, c.target); - }); - } - - /** - * Make sure, when the service process is killed, the client will get a callback with the - * right component name. - */ - @Test - public void testBindService_serviceKilled() { - - // We need to kill SUB2_PACKAGE, don't run it for this package. - Assume.assumeThat(sContext.getPackageName(), not(SUB2_PACKAGE)); - - Intent originalIntent = new Intent().setPackage(MAIN_PACKAGE); - originalIntent.setAction(MAIN_PACKAGE + ".IS_ALIAS_02"); - - final ComponentName componentNameForClient = - new ComponentName(MAIN_PACKAGE, MAIN_PACKAGE + ".s.Alias02"); - final ComponentName componentNameForTarget = - new ComponentName(SUB2_PACKAGE, MAIN_PACKAGE + ".s.Target02"); - - ComponentAliasMessage m; - - try (Receiver<ComponentAliasMessage> receiver = new Receiver<>(sContext, TAG)) { - // Bind to the service. - assertThat(sContext.bindService( - originalIntent, sServiceConnection, BIND_AUTO_CREATE)).isTrue(); - - // Check the target side behavior. - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onBind"); - - m = receiver.waitForNextMessage(); - assertThat(m.getMethodName()).isEqualTo("onServiceConnected"); - assertThat(m.getComponent()).isEqualTo(componentNameForClient); - // We don't need to check all the fields because these are tested else where. - - // Now kill the service process. - ShellUtils.runShellCommand("su 0 killall %s", SUB2_PACKAGE); - - // Check the target side behavior. - m = receiver.waitForNextMessage(); - - assertThat(m.getMethodName()).isEqualTo("onServiceDisconnected"); - assertThat(m.getComponent()).isEqualTo(componentNameForClient); - - receiver.ensureNoMoreMessages(); - } - } -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java b/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java deleted file mode 100644 index 1d05e72a2f3f..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2021 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 android.content.componentalias.tests.b; - -import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG; - -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.componentalias.tests.ComponentAliasMessage; -import android.util.Log; - -import com.android.compatibility.common.util.BroadcastMessenger; - -public class BaseReceiver extends BroadcastReceiver { - private String getMyIdentity(Context context) { - return (new ComponentName(context.getPackageName(), this.getClass().getCanonicalName())) - .flattenToShortString(); - } - - @Override - public void onReceive(Context context, Intent intent) { - Log.i(TAG, "onReceive: on " + getMyIdentity(context) + " intent=" + intent); - ComponentAliasMessage m = new ComponentAliasMessage() - .setSenderIdentity(getMyIdentity(context)) - .setMethodName("onReceive") - .setIntent(intent); - BroadcastMessenger.send(context, TAG, m); - } -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java deleted file mode 100644 index f9b9886b0bb2..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021 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 android.content.componentalias.tests.b; - -public class Target04 extends BaseReceiver { -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/BaseService.java b/tests/componentalias/src/android/content/componentalias/tests/s/BaseService.java deleted file mode 100644 index 535d9b80f100..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/s/BaseService.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2021 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 android.content.componentalias.tests.s; - -import static android.content.componentalias.tests.ComponentAliasTestCommon.TAG; - -import android.app.Service; -import android.content.ComponentName; -import android.content.Intent; -import android.content.componentalias.tests.ComponentAliasMessage; -import android.os.Binder; -import android.os.IBinder; -import android.util.Log; - -import com.android.compatibility.common.util.BroadcastMessenger; - -public class BaseService extends Service { - private String getMyIdentity() { - return (new ComponentName(this.getPackageName(), this.getClass().getCanonicalName())) - .flattenToShortString(); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - Log.i(TAG, "onStartCommand: on " + getMyIdentity() + " intent=" + intent); - ComponentAliasMessage m = new ComponentAliasMessage() - .setSenderIdentity(getMyIdentity()) - .setMethodName("onStartCommand") - .setIntent(intent); - BroadcastMessenger.send(this, TAG, m); - - return START_NOT_STICKY; - } - - @Override - public void onDestroy() { - Log.i(TAG, "onDestroy: on " + getMyIdentity()); - - ComponentAliasMessage m = new ComponentAliasMessage() - .setSenderIdentity(getMyIdentity()) - .setMethodName("onDestroy"); - BroadcastMessenger.send(this, TAG, m); - } - - @Override - public IBinder onBind(Intent intent) { - Log.i(TAG, "onBind: on " + getMyIdentity() + " intent=" + intent); - - ComponentAliasMessage m = new ComponentAliasMessage() - .setSenderIdentity(getMyIdentity()) - .setMethodName("onBind") - .setIntent(intent); - BroadcastMessenger.send(this, TAG, m); - - return new Binder(); - } -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target00.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target00.java deleted file mode 100644 index 64b91f5695f5..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/s/Target00.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021 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 android.content.componentalias.tests.s; - -public class Target00 extends BaseService { -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target01.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target01.java deleted file mode 100644 index bd589991d7dc..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/s/Target01.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021 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 android.content.componentalias.tests.s; - -public class Target01 extends BaseService { -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target02.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target02.java deleted file mode 100644 index 0ddf8188768b..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/s/Target02.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021 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 android.content.componentalias.tests.s; - -public class Target02 extends BaseService { -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target03.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target03.java deleted file mode 100644 index 0dbc0501b6f9..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/s/Target03.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021 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 android.content.componentalias.tests.s; - -public class Target03 extends BaseService { -} diff --git a/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java b/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java deleted file mode 100644 index 099425867f02..000000000000 --- a/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021 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 android.content.componentalias.tests.s; - -public class Target04 extends BaseService { -} diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java index e0f3f03e9cb7..421ceb797c15 100644 --- a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java +++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java @@ -50,6 +50,7 @@ import org.junit.runners.JUnit4; public class VibratorManagerServicePermissionTest { private static final String PACKAGE_NAME = "com.android.framework.permission.tests"; + private static final int DISPLAY_ID = 1; private static final CombinedVibration EFFECT = CombinedVibration.createParallel( VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE)); @@ -106,7 +107,8 @@ public class VibratorManagerServicePermissionTest { @Test public void testVibrateWithoutPermissionFails() throws RemoteException { expectSecurityException("VIBRATE"); - mVibratorService.vibrate(Process.myUid(), PACKAGE_NAME, EFFECT, ATTRS, "testVibrate", + mVibratorService.vibrate(Process.myUid(), DISPLAY_ID, PACKAGE_NAME, EFFECT, ATTRS, + "testVibrate", new Binder()); } @@ -115,7 +117,8 @@ public class VibratorManagerServicePermissionTest { throws RemoteException { getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( Manifest.permission.VIBRATE); - mVibratorService.vibrate(Process.myUid(), PACKAGE_NAME, EFFECT, ATTRS, "testVibrate", + mVibratorService.vibrate(Process.myUid(), DISPLAY_ID, PACKAGE_NAME, EFFECT, ATTRS, + "testVibrate", new Binder()); } @@ -124,7 +127,8 @@ public class VibratorManagerServicePermissionTest { expectSecurityException("UPDATE_APP_OPS_STATS"); getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( Manifest.permission.VIBRATE); - mVibratorService.vibrate(Process.SYSTEM_UID, "android", EFFECT, ATTRS, "testVibrate", + mVibratorService.vibrate(Process.SYSTEM_UID, DISPLAY_ID, "android", EFFECT, ATTRS, + "testVibrate", new Binder()); } @@ -133,7 +137,8 @@ public class VibratorManagerServicePermissionTest { getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( Manifest.permission.VIBRATE, Manifest.permission.UPDATE_APP_OPS_STATS); - mVibratorService.vibrate(Process.SYSTEM_UID, "android", EFFECT, ATTRS, "testVibrate", + mVibratorService.vibrate(Process.SYSTEM_UID, DISPLAY_ID, "android", EFFECT, ATTRS, + "testVibrate", new Binder()); } diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java index 1fe13fe97fbe..06cbeb5368a5 100644 --- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java +++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java @@ -124,16 +124,6 @@ public class WindowManagerPermissionTests extends TestCase { @SmallTest public void testSET_ORIENTATION() { try { - mWm.updateRotation(true, false); - fail("IWindowManager.updateRotation did not throw SecurityException as" - + " expected"); - } catch (SecurityException e) { - // expected - } catch (RemoteException e) { - fail("Unexpected remote exception"); - } - - try { mWm.freezeRotation(-1); fail("IWindowManager.freezeRotation did not throw SecurityException as" + " expected"); diff --git a/tests/testables/src/android/testing/TestWithLooperRule.java b/tests/testables/src/android/testing/TestWithLooperRule.java new file mode 100644 index 000000000000..99b303e0c43a --- /dev/null +++ b/tests/testables/src/android/testing/TestWithLooperRule.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2023 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 android.testing; + +import android.testing.TestableLooper.LooperFrameworkMethod; +import android.testing.TestableLooper.RunWithLooper; + +import org.junit.internal.runners.statements.InvokeMethod; +import org.junit.rules.MethodRule; +import org.junit.runner.RunWith; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +/* + * This rule is meant to be an alternative of using AndroidTestingRunner. + * It let tests to start from background thread, and assigns mainLooper or new + * Looper for the Statement. + */ +public class TestWithLooperRule implements MethodRule { + + /* + * This rule requires to be the inner most Rule, so the next statement is RunAfters + * instead of another rule. You can set it by '@Rule(order = Integer.MAX_VALUE)' + */ + @Override + public Statement apply(Statement base, FrameworkMethod method, Object target) { + // getting testRunner check, if AndroidTestingRunning then we skip this rule + RunWith runWithAnnotation = target.getClass().getAnnotation(RunWith.class); + if (runWithAnnotation != null) { + // if AndroidTestingRunner or it's subclass is in use, do nothing + if (AndroidTestingRunner.class.isAssignableFrom(runWithAnnotation.value())) { + return base; + } + } + + // check if RunWithLooper annotation is used. If not skip this rule + RunWithLooper looperAnnotation = method.getAnnotation(RunWithLooper.class); + if (looperAnnotation == null) { + looperAnnotation = target.getClass().getAnnotation(RunWithLooper.class); + } + if (looperAnnotation == null) { + return base; + } + + try { + wrapMethodInStatement(base, method, target); + } catch (Exception e) { + throw new RuntimeException(e); + } + return base; + } + + // This method is based on JUnit4 test runner flow. It might need to be revisited when JUnit is + // upgraded + // TODO(b/277743626): use a cleaner way to wrap each statements; may require some JUnit + // patching to facilitate this. + private void wrapMethodInStatement(Statement base, FrameworkMethod method, Object target) + throws Exception { + Statement next = base; + try { + while (next != null) { + switch (next.getClass().getSimpleName()) { + case "RunAfters": + this.<List<FrameworkMethod>>wrapFieldMethodFor(next, + next.getClass(), "afters", method, target); + next = getNextStatement(next, "next"); + break; + case "RunBefores": + this.<List<FrameworkMethod>>wrapFieldMethodFor(next, + next.getClass(), "befores", method, target); + next = getNextStatement(next, "next"); + break; + case "FailOnTimeout": + // Note: withPotentialTimeout() from BlockJUnit4ClassRunner might use + // FailOnTimeout which always wraps a new thread during InvokeMethod + // method evaluation. + next = getNextStatement(next, "originalStatement"); + break; + case "InvokeMethod": + this.<FrameworkMethod>wrapFieldMethodFor(next, + InvokeMethod.class, "testMethod", method, target); + return; + default: + throw new Exception( + String.format("Unexpected Statement received: [%s]", + next.getClass().getName()) + ); + } + } + } catch (Exception e) { + throw e; + } + } + + // Wrapping the befores, afters, and InvokeMethods with LooperFrameworkMethod + // within the statement. + private <T> void wrapFieldMethodFor(Statement base, Class<?> targetClass, String fieldStr, + FrameworkMethod method, Object target) + throws NoSuchFieldException, IllegalAccessException { + Field field = targetClass.getDeclaredField(fieldStr); + field.setAccessible(true); + T fieldInstance = (T) field.get(base); + if (fieldInstance instanceof FrameworkMethod) { + field.set(base, looperWrap(method, target, (FrameworkMethod) fieldInstance)); + } else { + // Befores and afters methods lists + field.set(base, looperWrap(method, target, (List<FrameworkMethod>) fieldInstance)); + } + } + + // Retrieve the next wrapped statement based on the selected field string + private Statement getNextStatement(Statement base, String fieldStr) + throws NoSuchFieldException, IllegalAccessException { + Field nextField = base.getClass().getDeclaredField(fieldStr); + nextField.setAccessible(true); + Object value = nextField.get(base); + return value instanceof Statement ? (Statement) value : null; + } + + protected FrameworkMethod looperWrap(FrameworkMethod method, Object test, + FrameworkMethod base) { + RunWithLooper annotation = method.getAnnotation(RunWithLooper.class); + if (annotation == null) annotation = test.getClass().getAnnotation(RunWithLooper.class); + if (annotation != null) { + return LooperFrameworkMethod.get(base, annotation.setAsMainLooper(), test); + } + return base; + } + + protected List<FrameworkMethod> looperWrap(FrameworkMethod method, Object test, + List<FrameworkMethod> methods) { + RunWithLooper annotation = method.getAnnotation(RunWithLooper.class); + if (annotation == null) annotation = test.getClass().getAnnotation(RunWithLooper.class); + if (annotation != null) { + methods = new ArrayList<>(methods); + for (int i = 0; i < methods.size(); i++) { + methods.set(i, LooperFrameworkMethod.get(methods.get(i), + annotation.setAsMainLooper(), test)); + } + } + return methods; + } +} diff --git a/tests/testables/src/android/testing/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java index e2668bc4281f..0f04d6ae1721 100644 --- a/tests/testables/src/android/testing/TestableContext.java +++ b/tests/testables/src/android/testing/TestableContext.java @@ -33,11 +33,15 @@ import android.provider.Settings; import android.util.ArrayMap; import android.view.LayoutInflater; +import androidx.annotation.Nullable; + import org.junit.rules.TestRule; import org.junit.rules.TestWatcher; import org.junit.runner.Description; import org.junit.runners.model.Statement; +import java.util.ArrayList; + /** * A ContextWrapper with utilities specifically designed to make Testing easier. * @@ -61,6 +65,7 @@ public class TestableContext extends ContextWrapper implements TestRule { private final TestableContentResolver mTestableContentResolver; private final TestableSettingsProvider mSettingsProvider; + private ArrayList<MockServiceResolver> mMockServiceResolvers; private ArrayMap<String, Object> mMockSystemServices; private ArrayMap<ComponentName, IBinder> mMockServices; private ArrayMap<ServiceConnection, ComponentName> mActiveServices; @@ -214,12 +219,15 @@ public class TestableContext extends ContextWrapper implements TestRule { /** * Adds a mock service to be connected to by a bindService call. * <p> - * Normally a TestableContext will pass through all bind requests to the base context - * but when addMockService has been called for a ComponentName being bound, then - * TestableContext will immediately trigger a {@link ServiceConnection#onServiceConnected} - * with the specified service, and will call {@link ServiceConnection#onServiceDisconnected} - * when the service is unbound. + * Normally a TestableContext will pass through all bind requests to the base context + * but when addMockService has been called for a ComponentName being bound, then + * TestableContext will immediately trigger a {@link ServiceConnection#onServiceConnected} + * with the specified service, and will call {@link ServiceConnection#onServiceDisconnected} + * when the service is unbound. * </p> + * + * @see #addMockServiceResolver(MockServiceResolver) for custom resolution of service Intents to + * ComponentNames */ public void addMockService(ComponentName component, IBinder service) { if (mMockServices == null) mMockServices = new ArrayMap<>(); @@ -227,12 +235,38 @@ public class TestableContext extends ContextWrapper implements TestRule { } /** + * Strategy to resolve a service {@link Intent} to a mock service {@link ComponentName}. + */ + public interface MockServiceResolver { + @Nullable + ComponentName resolve(Intent service); + } + + /** + * Registers a strategy to resolve service intents to registered mock services. + * <p> + * The result of the first {@link MockServiceResolver} to return a non-null + * {@link ComponentName} is used to look up a mock service. The mock service must be registered + * via {@link #addMockService(ComponentName, IBinder)} separately, using the same component + * name. + * + * If none of the resolvers return a non-null value, or the first returned component name + * does not link to a registered mock service, the bind requests are passed to the base context + * + * The resolvers are queried in order of registration. + */ + public void addMockServiceResolver(MockServiceResolver resolver) { + if (mMockServiceResolvers == null) mMockServiceResolvers = new ArrayList<>(); + mMockServiceResolvers.add(resolver); + } + + /** * @see #addMockService(ComponentName, IBinder) */ @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) { if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); - if (checkMocks(service.getComponent(), conn)) return true; + if (checkMocks(service, conn)) return true; return super.bindService(service, conn, flags); } @@ -243,7 +277,7 @@ public class TestableContext extends ContextWrapper implements TestRule { public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user) { if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); - if (checkMocks(service.getComponent(), conn)) return true; + if (checkMocks(service, conn)) return true; return super.bindServiceAsUser(service, conn, flags, handler, user); } @@ -254,18 +288,36 @@ public class TestableContext extends ContextWrapper implements TestRule { public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, UserHandle user) { if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable()); - if (checkMocks(service.getComponent(), conn)) return true; + if (checkMocks(service, conn)) return true; return super.bindServiceAsUser(service, conn, flags, user); } - private boolean checkMocks(ComponentName component, ServiceConnection conn) { - if (mMockServices != null && component != null && mMockServices.containsKey(component)) { - if (mActiveServices == null) mActiveServices = new ArrayMap<>(); - mActiveServices.put(conn, component); - conn.onServiceConnected(component, mMockServices.get(component)); - return true; + private boolean checkMocks(Intent service, ServiceConnection conn) { + if (mMockServices == null) return false; + + ComponentName serviceComponent = resolveMockServiceComponent(service); + if (serviceComponent == null) return false; + + IBinder serviceImpl = mMockServices.get(serviceComponent); + if (serviceImpl == null) return false; + + if (mActiveServices == null) mActiveServices = new ArrayMap<>(); + mActiveServices.put(conn, serviceComponent); + conn.onServiceConnected(serviceComponent, serviceImpl); + return true; + } + + private ComponentName resolveMockServiceComponent(Intent service) { + ComponentName specifiedComponentName = service.getComponent(); + if (specifiedComponentName != null) return specifiedComponentName; + + if (mMockServiceResolvers == null) return null; + + for (MockServiceResolver resolver : mMockServiceResolvers) { + ComponentName resolvedComponent = resolver.resolve(service); + if (resolvedComponent != null) return resolvedComponent; } - return false; + return null; } /** diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index ebe9b5706bf8..edd6dd3468ef 100644 --- a/tests/testables/src/android/testing/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -30,6 +30,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.reflect.Field; import java.util.Map; /** @@ -45,6 +46,9 @@ public class TestableLooper { * catch crashes. */ public static final boolean HOLD_MAIN_THREAD = false; + private static final Field MESSAGE_QUEUE_MESSAGES_FIELD; + private static final Field MESSAGE_NEXT_FIELD; + private static final Field MESSAGE_WHEN_FIELD; private Looper mLooper; private MessageQueue mQueue; @@ -54,6 +58,19 @@ public class TestableLooper { private Runnable mEmptyMessage; private TestLooperManager mQueueWrapper; + static { + try { + MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); + MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); + MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); + MESSAGE_NEXT_FIELD.setAccessible(true); + MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when"); + MESSAGE_WHEN_FIELD.setAccessible(true); + } catch (NoSuchFieldException e) { + throw new RuntimeException("Failed to initialize TestableLooper", e); + } + } + public TestableLooper(Looper l) throws Exception { this(acquireLooperManager(l), l); } @@ -119,6 +136,33 @@ public class TestableLooper { while (processQueuedMessages() != 0) ; } + public void moveTimeForward(long milliSeconds) { + try { + Message msg = getMessageLinkedList(); + while (msg != null) { + long updatedWhen = msg.getWhen() - milliSeconds; + if (updatedWhen < 0) { + updatedWhen = 0; + } + MESSAGE_WHEN_FIELD.set(msg, updatedWhen); + msg = (Message) MESSAGE_NEXT_FIELD.get(msg); + } + } catch (IllegalAccessException e) { + throw new RuntimeException("Access failed in TestableLooper: set - Message.when", e); + } + } + + private Message getMessageLinkedList() { + try { + MessageQueue queue = mLooper.getQueue(); + return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Access failed in TestableLooper: get - MessageQueue.mMessages", + e); + } + } + private int processQueuedMessages() { int count = 0; mEmptyMessage = () -> { }; diff --git a/tests/testables/src/android/testing/TestableResources.java b/tests/testables/src/android/testing/TestableResources.java index c60f07d56d92..27d5b66b355e 100644 --- a/tests/testables/src/android/testing/TestableResources.java +++ b/tests/testables/src/android/testing/TestableResources.java @@ -59,7 +59,8 @@ public class TestableResources { * Since resource ids are unique there is a single addOverride that will override the value * whenever it is gotten regardless of which method is used (i.e. getColor or getDrawable). * </p> - * @param id The resource id to be overridden + * + * @param id The resource id to be overridden * @param value The value of the resource, null to cause a {@link Resources.NotFoundException} * when gotten. */ @@ -74,28 +75,33 @@ public class TestableResources { * cause a {@link Resources.NotFoundException} whereas removing the override will actually * switch back to returning the default/real value of the resource. * </p> - * @param id */ public void removeOverride(int id) { mOverrides.remove(id); } private Object answer(InvocationOnMock invocationOnMock) throws Throwable { - try { - int id = invocationOnMock.getArgument(0); - int index = mOverrides.indexOfKey(id); - if (index >= 0) { - Object value = mOverrides.valueAt(index); - if (value == null) throw new Resources.NotFoundException(); - return value; + // Only try to override methods with an integer first argument + if (invocationOnMock.getArguments().length > 0) { + Object argument = invocationOnMock.getArgument(0); + if (argument instanceof Integer) { + try { + int id = (Integer)argument; + int index = mOverrides.indexOfKey(id); + if (index >= 0) { + Object value = mOverrides.valueAt(index); + if (value == null) throw new Resources.NotFoundException(); + return value; + } + } catch (Resources.NotFoundException e) { + // Let through NotFoundException. + throw e; + } catch (Throwable t) { + // Generic catching for the many things that can go wrong, fall back to + // the real implementation. + Log.i(TAG, "Falling back to default resources call " + t); + } } - } catch (Resources.NotFoundException e) { - // Let through NotFoundException. - throw e; - } catch (Throwable t) { - // Generic catching for the many things that can go wrong, fall back to - // the real implementation. - Log.i(TAG, "Falling back to default resources call " + t); } return invocationOnMock.callRealMethod(); } diff --git a/tests/testables/src/android/testing/TestableSettingsProvider.java b/tests/testables/src/android/testing/TestableSettingsProvider.java index fd92c657cb2e..b850cb8e6dfd 100644 --- a/tests/testables/src/android/testing/TestableSettingsProvider.java +++ b/tests/testables/src/android/testing/TestableSettingsProvider.java @@ -49,14 +49,15 @@ public class TestableSettingsProvider extends MockContentProvider { } void clearValuesAndCheck(Context context) { - int userId = UserHandle.myUserId(); - mValues.put(key("global", MY_UNIQUE_KEY, userId), MY_UNIQUE_KEY); - mValues.put(key("secure", MY_UNIQUE_KEY, userId), MY_UNIQUE_KEY); - mValues.put(key("system", MY_UNIQUE_KEY, userId), MY_UNIQUE_KEY); - + // Ensure we swapped over to use TestableSettingsProvider Settings.Global.clearProviderForTest(); Settings.Secure.clearProviderForTest(); Settings.System.clearProviderForTest(); + + // putString will eventually invoking the mocked call() method and update mValues + Settings.Global.putString(context.getContentResolver(), MY_UNIQUE_KEY, MY_UNIQUE_KEY); + Settings.Secure.putString(context.getContentResolver(), MY_UNIQUE_KEY, MY_UNIQUE_KEY); + Settings.System.putString(context.getContentResolver(), MY_UNIQUE_KEY, MY_UNIQUE_KEY); // Verify that if any test is using TestableContext, they all have the correct settings // provider. assertEquals("Incorrect settings provider, test using incorrect Context?", MY_UNIQUE_KEY, @@ -71,7 +72,11 @@ public class TestableSettingsProvider extends MockContentProvider { public Bundle call(String method, String arg, Bundle extras) { // Methods are "GET_system", "GET_global", "PUT_secure", etc. - final int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, UserHandle.myUserId()); + int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, UserHandle.USER_CURRENT); + if (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF) { + userId = UserHandle.myUserId(); + } + final String[] commands = method.split("_", 2); final String op = commands[0]; final String table = commands[1]; diff --git a/tests/testables/src/com/android/internal/config/sysui/OWNERS b/tests/testables/src/com/android/internal/config/sysui/OWNERS new file mode 100644 index 000000000000..2e96c97c8bb3 --- /dev/null +++ b/tests/testables/src/com/android/internal/config/sysui/OWNERS @@ -0,0 +1 @@ +include /packages/SystemUI/OWNERS diff --git a/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java b/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java new file mode 100644 index 000000000000..a8815dc1f0ee --- /dev/null +++ b/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 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.android.internal.config.sysui; + +import java.util.HashMap; +import java.util.Map; + +public class TestableFlagResolver implements SystemUiSystemPropertiesFlags.FlagResolver { + private Map<String, Boolean> mOverrides = new HashMap<>(); + + @Override + public boolean isEnabled(SystemUiSystemPropertiesFlags.Flag flag) { + return mOverrides.getOrDefault(flag.mSysPropKey, flag.mDefaultValue); + } + + public void setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag, boolean isEnabled) { + mOverrides.put(flag.mSysPropKey, isEnabled); + } +} diff --git a/tests/testables/tests/Android.bp b/tests/testables/tests/Android.bp index ba323d3fe47a..06449e0ce574 100644 --- a/tests/testables/tests/Android.bp +++ b/tests/testables/tests/Android.bp @@ -46,5 +46,8 @@ android_test { "android.test.mock", ], certificate: "platform", - test_suites: ["device-tests"], + test_suites: [ + "device-tests", + "automotive-tests", + ], } diff --git a/tests/testables/tests/com/android/internal/config/sysui/OWNERS b/tests/testables/tests/com/android/internal/config/sysui/OWNERS new file mode 100644 index 000000000000..2e96c97c8bb3 --- /dev/null +++ b/tests/testables/tests/com/android/internal/config/sysui/OWNERS @@ -0,0 +1 @@ +include /packages/SystemUI/OWNERS diff --git a/tests/testables/tests/src/android/testing/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java index 25f6a48871d3..0f491b86626c 100644 --- a/tests/testables/tests/src/android/testing/TestableLooperTest.java +++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java @@ -19,15 +19,19 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InOrder; import android.os.Handler; import android.os.Looper; @@ -162,7 +166,7 @@ public class TestableLooperTest { @Test public void testCorrectLooperExecution() throws Exception { - boolean[] hasRun = new boolean[] { false }; + boolean[] hasRun = new boolean[]{false}; Runnable r = () -> { assertEquals("Should run on main looper", Looper.getMainLooper(), Looper.myLooper()); hasRun[0] = true; @@ -177,4 +181,63 @@ public class TestableLooperTest { testableLooper.destroy(); } } + + @Test + public void testDelayedDispatchNoTimeMove() { + Handler handler = spy(new Handler(mTestableLooper.getLooper())); + InOrder inOrder = inOrder(handler); + + final Message messageA = handler.obtainMessage(1); + final Message messageB = handler.obtainMessage(2); + + handler.sendMessageDelayed(messageA, 0); + handler.sendMessageDelayed(messageB, 0); + + mTestableLooper.processAllMessages(); + + inOrder.verify(handler).dispatchMessage(messageA); + inOrder.verify(handler).dispatchMessage(messageB); + } + + @Test + public void testDelayedMessageDoesntSend() { + Handler handler = spy(new Handler(mTestableLooper.getLooper())); + InOrder inOrder = inOrder(handler); + + final Message messageA = handler.obtainMessage(1); + final Message messageB = handler.obtainMessage(2); + final Message messageC = handler.obtainMessage(3); + + handler.sendMessageDelayed(messageA, 0); + handler.sendMessageDelayed(messageB, 0); + handler.sendMessageDelayed(messageC, 500); + + mTestableLooper.processAllMessages(); + + inOrder.verify(handler).dispatchMessage(messageA); + inOrder.verify(handler).dispatchMessage(messageB); + verify(handler, never()).dispatchMessage(messageC); + } + + @Test + public void testMessageSendsAfterDelay() { + Handler handler = spy(new Handler(mTestableLooper.getLooper())); + InOrder inOrder = inOrder(handler); + + final Message messageA = handler.obtainMessage(1); + final Message messageB = handler.obtainMessage(2); + final Message messageC = handler.obtainMessage(3); + + handler.sendMessageDelayed(messageA, 0); + handler.sendMessageDelayed(messageB, 0); + handler.sendMessageDelayed(messageC, 500); + + mTestableLooper.moveTimeForward(500); + mTestableLooper.processAllMessages(); + + inOrder.verify(handler).dispatchMessage(messageA); + inOrder.verify(handler).dispatchMessage(messageB); + inOrder.verify(handler).dispatchMessage(messageC); + } + } diff --git a/tests/utils/hostutils/src/com/android/fsverity/AddFsVerityCertRule.java b/tests/utils/hostutils/src/com/android/fsverity/AddFsVerityCertRule.java deleted file mode 100644 index 5ab4dc60e2ac..000000000000 --- a/tests/utils/hostutils/src/com/android/fsverity/AddFsVerityCertRule.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2021 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.android.fsverity; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; - -import static org.junit.Assume.assumeTrue; - -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.log.LogUtil; -import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; -import com.android.tradefed.util.CommandResult; -import com.android.tradefed.util.CommandStatus; - -import org.junit.rules.ExternalResource; - -public final class AddFsVerityCertRule extends ExternalResource { - - private static final String APK_VERITY_STANDARD_MODE = "2"; - - private final BaseHostJUnit4Test mHost; - private final String mCertPath; - private String mKeyId; - - public AddFsVerityCertRule(BaseHostJUnit4Test host, String certPath) { - mHost = host; - mCertPath = certPath; - } - - @Override - protected void before() throws Throwable { - ITestDevice device = mHost.getDevice(); - String apkVerityMode = device.getProperty("ro.apk_verity.mode"); - assumeTrue(device.getLaunchApiLevel() >= 30 - || APK_VERITY_STANDARD_MODE.equals(apkVerityMode)); - - String keyId = executeCommand( - "mini-keyctl padd asymmetric fsv_test .fs-verity < " + mCertPath).trim(); - assertThat(keyId).matches("^\\d+$"); - mKeyId = keyId; - } - - @Override - protected void after() { - if (mKeyId == null) return; - try { - executeCommand("mini-keyctl unlink " + mKeyId + " .fs-verity"); - } catch (DeviceNotAvailableException e) { - LogUtil.CLog.e(e); - } - mKeyId = null; - } - - private String executeCommand(String cmd) throws DeviceNotAvailableException { - CommandResult result = mHost.getDevice().executeShellV2Command(cmd); - assertWithMessage("`" + cmd + "` failed: " + result.getStderr()) - .that(result.getStatus()) - .isEqualTo(CommandStatus.SUCCESS); - return result.getStdout(); - } -} diff --git a/tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java b/tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java new file mode 100644 index 000000000000..b94bb41c0988 --- /dev/null +++ b/tests/utils/testutils/java/android/os/test/FakePermissionEnforcer.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 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 android.os.test; + +import static android.permission.PermissionManager.PERMISSION_GRANTED; +import static android.permission.PermissionManager.PERMISSION_HARD_DENIED; + +import android.annotation.NonNull; +import android.content.AttributionSource; +import android.os.PermissionEnforcer; + +import java.util.HashSet; +import java.util.Set; + +/** + * Fake for {@link PermissionEnforcer}. Useful for tests wanting to mock the + * permission checks of an AIDL service. FakePermissionEnforcer may be passed + * to the constructor of the AIDL-generated Stub class. + * + */ +public class FakePermissionEnforcer extends PermissionEnforcer { + private Set<String> mGranted; + + public FakePermissionEnforcer() { + mGranted = new HashSet(); + } + + public void grant(String permission) { + mGranted.add(permission); + } + + public void revoke(String permission) { + mGranted.remove(permission); + } + + private boolean granted(String permission) { + return mGranted.contains(permission); + } + + @Override + protected int checkPermission(@NonNull String permission, + @NonNull AttributionSource source) { + return granted(permission) ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED; + } + + @Override + protected int checkPermission(@NonNull String permission, int pid, int uid) { + return granted(permission) ? PERMISSION_GRANTED : PERMISSION_HARD_DENIED; + } +} diff --git a/tests/utils/testutils/java/android/os/test/OWNERS b/tests/utils/testutils/java/android/os/test/OWNERS new file mode 100644 index 000000000000..3a9129e1bb69 --- /dev/null +++ b/tests/utils/testutils/java/android/os/test/OWNERS @@ -0,0 +1 @@ +per-file FakePermissionEnforcer.java = file:/tests/EnforcePermission/OWNERS diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java index adec05851f23..cc3781a0bfb3 100644 --- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java +++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java @@ -147,12 +147,39 @@ public class BroadcastInterceptingContext extends ContextWrapper { @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { - return registerReceiver(receiver, filter, null, null); + return registerReceiver(receiver, filter, null, null, 0); + } + + /** + * Registers the specified {@code receiver} to listen for broadcasts that match the {@code + * filter} in the current process. + * + * <p>Since this method only listens for broadcasts in the current process, the provided {@code + * flags} are ignored; this method is primarily intended to allow receivers that register with + * flags to register in the current process during tests. + */ + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) { + return registerReceiver(receiver, filter, null, null, flags); } @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { + return registerReceiver(receiver, filter, broadcastPermission, scheduler, 0); + } + + /** + * Registers the specified {@code receiver} to listen for broadcasts that match the {@code + * filter} to run in the context of the specified {@code scheduler} in the current process. + * + * <p>Since this method only listens for broadcasts in the current process, the provided {@code + * flags} are ignored; this method is primarily intended to allow receivers that register with + * flags to register in the current process during tests. + */ + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, + String broadcastPermission, Handler scheduler, int flags) { synchronized (mInterceptors) { mInterceptors.add(new BroadcastInterceptor(receiver, filter, scheduler)); } diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java index 15a6afc5ff7c..7c5dcf8b95f7 100644 --- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java +++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java @@ -50,6 +50,7 @@ public final class FrameworksTestsFilter extends SelectTest { "android.app.servertransaction.", // all tests under the package. "android.view.CutoutSpecificationTest", "android.view.DisplayCutoutTest", + "android.view.DisplayShapeTest", "android.view.InsetsAnimationControlImplTest", "android.view.InsetsControllerTest", "android.view.InsetsFlagsTest", diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java index 2fbcf9d87bd4..156961312323 100644 --- a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java +++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkTemplateTest.java @@ -20,6 +20,7 @@ import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.fail; import org.junit.Test; @@ -31,8 +32,8 @@ public class VcnCellUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTe private static final Set<String> ALLOWED_PLMN_IDS = new HashSet<>(); private static final Set<Integer> ALLOWED_CARRIER_IDS = new HashSet<>(); - // Package private for use in VcnGatewayConnectionConfigTest - static VcnCellUnderlyingNetworkTemplate getTestNetworkTemplate() { + // Public for use in UnderlyingNetworkControllerTest + public static VcnCellUnderlyingNetworkTemplate.Builder getTestNetworkTemplateBuilder() { return new VcnCellUnderlyingNetworkTemplate.Builder() .setMetered(MATCH_FORBIDDEN) .setMinUpstreamBandwidthKbps( @@ -44,13 +45,44 @@ public class VcnCellUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTe .setOperatorPlmnIds(ALLOWED_PLMN_IDS) .setSimSpecificCarrierIds(ALLOWED_CARRIER_IDS) .setRoaming(MATCH_FORBIDDEN) - .setOpportunistic(MATCH_REQUIRED) - .build(); + .setOpportunistic(MATCH_REQUIRED); + } + + // Package private for use in VcnGatewayConnectionConfigTest + static VcnCellUnderlyingNetworkTemplate getTestNetworkTemplate() { + return getTestNetworkTemplateBuilder().build(); + } + + private void setAllCapabilities( + VcnCellUnderlyingNetworkTemplate.Builder builder, int matchCriteria) { + builder.setCbs(matchCriteria); + builder.setDun(matchCriteria); + builder.setIms(matchCriteria); + builder.setInternet(matchCriteria); + builder.setMms(matchCriteria); + builder.setRcs(matchCriteria); + } + + private void verifyAllCapabilities( + VcnCellUnderlyingNetworkTemplate template, + int expectMatchCriteriaforNonInternet, + int expectMatchCriteriaforInternet) { + assertEquals(expectMatchCriteriaforNonInternet, template.getCbs()); + assertEquals(expectMatchCriteriaforNonInternet, template.getDun()); + assertEquals(expectMatchCriteriaforNonInternet, template.getIms()); + assertEquals(expectMatchCriteriaforNonInternet, template.getMms()); + assertEquals(expectMatchCriteriaforNonInternet, template.getRcs()); + + assertEquals(expectMatchCriteriaforInternet, template.getInternet()); } @Test public void testBuilderAndGetters() { - final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate(); + final VcnCellUnderlyingNetworkTemplate.Builder builder = getTestNetworkTemplateBuilder(); + setAllCapabilities(builder, MATCH_REQUIRED); + + final VcnCellUnderlyingNetworkTemplate networkPriority = builder.build(); + assertEquals(MATCH_FORBIDDEN, networkPriority.getMetered()); assertEquals( TEST_MIN_ENTRY_UPSTREAM_BANDWIDTH_KBPS, @@ -68,6 +100,8 @@ public class VcnCellUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTe assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getSimSpecificCarrierIds()); assertEquals(MATCH_FORBIDDEN, networkPriority.getRoaming()); assertEquals(MATCH_REQUIRED, networkPriority.getOpportunistic()); + + verifyAllCapabilities(networkPriority, MATCH_REQUIRED, MATCH_REQUIRED); } @Test @@ -86,6 +120,8 @@ public class VcnCellUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTe assertEquals(new HashSet<Integer>(), networkPriority.getSimSpecificCarrierIds()); assertEquals(MATCH_ANY, networkPriority.getRoaming()); assertEquals(MATCH_ANY, networkPriority.getOpportunistic()); + + verifyAllCapabilities(networkPriority, MATCH_ANY, MATCH_REQUIRED); } @Test @@ -112,6 +148,25 @@ public class VcnCellUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTe } @Test + public void testBuildFailWithoutRequiredCapabilities() { + try { + new VcnCellUnderlyingNetworkTemplate.Builder().setInternet(MATCH_ANY).build(); + + fail("Expected IAE for missing required capabilities"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testEqualsWithDifferentCapabilities() { + final VcnCellUnderlyingNetworkTemplate left = + new VcnCellUnderlyingNetworkTemplate.Builder().setDun(MATCH_REQUIRED).build(); + final VcnCellUnderlyingNetworkTemplate right = + new VcnCellUnderlyingNetworkTemplate.Builder().setMms(MATCH_REQUIRED).build(); + assertNotEquals(left, right); + } + + @Test public void testPersistableBundle() { final VcnCellUnderlyingNetworkTemplate networkPriority = getTestNetworkTemplate(); assertEquals( @@ -119,4 +174,16 @@ public class VcnCellUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTe VcnUnderlyingNetworkTemplate.fromPersistableBundle( networkPriority.toPersistableBundle())); } + + @Test + public void testPersistableBundleWithCapabilities() { + final VcnCellUnderlyingNetworkTemplate.Builder builder = getTestNetworkTemplateBuilder(); + setAllCapabilities(builder, MATCH_REQUIRED); + + final VcnCellUnderlyingNetworkTemplate networkPriority = builder.build(); + assertEquals( + networkPriority, + VcnUnderlyingNetworkTemplate.fromPersistableBundle( + networkPriority.toPersistableBundle())); + } } diff --git a/tests/vcn/java/android/net/vcn/VcnConfigTest.java b/tests/vcn/java/android/net/vcn/VcnConfigTest.java index 7ac51b7e3342..73a0a6183cb6 100644 --- a/tests/vcn/java/android/net/vcn/VcnConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnConfigTest.java @@ -16,7 +16,13 @@ package android.net.vcn; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_TEST; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.fail; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -24,6 +30,7 @@ import static org.mockito.Mockito.mock; import android.annotation.NonNull; import android.content.Context; import android.os.Parcel; +import android.util.ArraySet; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -42,19 +49,36 @@ public class VcnConfigTest { private static final Set<VcnGatewayConnectionConfig> GATEWAY_CONNECTION_CONFIGS = Collections.singleton(VcnGatewayConnectionConfigTest.buildTestConfig()); + private static final Set<Integer> RESTRICTED_TRANSPORTS = new ArraySet<>(); + + static { + RESTRICTED_TRANSPORTS.add(TRANSPORT_WIFI); + RESTRICTED_TRANSPORTS.add(TRANSPORT_CELLULAR); + } + private final Context mContext = mock(Context.class); // Public visibility for VcnManagementServiceTest - public static VcnConfig buildTestConfig(@NonNull Context context) { + public static VcnConfig buildTestConfig( + @NonNull Context context, Set<Integer> restrictedTransports) { VcnConfig.Builder builder = new VcnConfig.Builder(context); for (VcnGatewayConnectionConfig gatewayConnectionConfig : GATEWAY_CONNECTION_CONFIGS) { builder.addGatewayConnectionConfig(gatewayConnectionConfig); } + if (restrictedTransports != null) { + builder.setRestrictedUnderlyingNetworkTransports(restrictedTransports); + } + return builder.build(); } + // Public visibility for VcnManagementServiceTest + public static VcnConfig buildTestConfig(@NonNull Context context) { + return buildTestConfig(context, null); + } + @Before public void setUp() throws Exception { doReturn(TEST_PACKAGE_NAME).when(mContext).getOpPackageName(); @@ -91,11 +115,25 @@ public class VcnConfigTest { } @Test - public void testBuilderAndGetters() { + public void testBuilderAndGettersDefaultValues() { final VcnConfig config = buildTestConfig(mContext); assertEquals(TEST_PACKAGE_NAME, config.getProvisioningPackageName()); assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs()); + assertFalse(config.isTestModeProfile()); + assertEquals( + Collections.singleton(TRANSPORT_WIFI), + config.getRestrictedUnderlyingNetworkTransports()); + } + + @Test + public void testBuilderAndGettersConfigRestrictedTransports() { + final VcnConfig config = buildTestConfig(mContext, RESTRICTED_TRANSPORTS); + + assertEquals(TEST_PACKAGE_NAME, config.getProvisioningPackageName()); + assertEquals(GATEWAY_CONNECTION_CONFIGS, config.getGatewayConnectionConfigs()); + assertFalse(config.isTestModeProfile()); + assertEquals(RESTRICTED_TRANSPORTS, config.getRestrictedUnderlyingNetworkTransports()); } @Test @@ -106,6 +144,55 @@ public class VcnConfigTest { } @Test + public void testPersistableBundleWithRestrictedTransports() { + final VcnConfig config = buildTestConfig(mContext, RESTRICTED_TRANSPORTS); + + assertEquals(config, new VcnConfig(config.toPersistableBundle())); + } + + @Test + public void testEqualityWithRestrictedTransports() { + final VcnConfig config = buildTestConfig(mContext, RESTRICTED_TRANSPORTS); + final VcnConfig configEqual = buildTestConfig(mContext, RESTRICTED_TRANSPORTS); + final VcnConfig configNotEqual = + buildTestConfig(mContext, Collections.singleton(TRANSPORT_WIFI)); + + assertEquals(config, configEqual); + assertNotEquals(config, configNotEqual); + } + + private VcnConfig buildConfigRestrictTransportTest(boolean isTestMode) throws Exception { + VcnConfig.Builder builder = + new VcnConfig.Builder(mContext) + .setRestrictedUnderlyingNetworkTransports(Set.of(TRANSPORT_TEST)); + if (isTestMode) { + builder.setIsTestModeProfile(); + } + + for (VcnGatewayConnectionConfig gatewayConnectionConfig : GATEWAY_CONNECTION_CONFIGS) { + builder.addGatewayConnectionConfig(gatewayConnectionConfig); + } + + return builder.build(); + } + + @Test + public void testRestrictTransportTestInTestModeProfile() throws Exception { + final VcnConfig config = buildConfigRestrictTransportTest(true /* isTestMode */); + assertEquals(Set.of(TRANSPORT_TEST), config.getRestrictedUnderlyingNetworkTransports()); + } + + @Test + public void testRestrictTransportTestInNonTestModeProfile() throws Exception { + try { + buildConfigRestrictTransportTest(false /* isTestMode */); + fail("Expected exception because the config is not a test mode profile"); + } catch (Exception expected) { + + } + } + + @Test public void testParceling() { final VcnConfig config = buildTestConfig(mContext); diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java index 2aef9ae7ca32..a1a39ff173b4 100644 --- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java @@ -19,9 +19,11 @@ package android.net.vcn; import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE; import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES; import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_TEMPLATES_KEY; +import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; @@ -42,7 +44,9 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) @@ -78,6 +82,10 @@ public class VcnGatewayConnectionConfigTest { TimeUnit.MINUTES.toMillis(30) }; public static final int MAX_MTU = 1360; + public static final int MIN_UDP_PORT_4500_NAT_TIMEOUT = 120; + + private static final Set<Integer> GATEWAY_OPTIONS = + Collections.singleton(VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY); public static final IkeTunnelConnectionParams TUNNEL_CONNECTION_PARAMS = TunnelConnectionParamsUtilsTest.buildTestParams(); @@ -93,14 +101,22 @@ public class VcnGatewayConnectionConfigTest { EXPOSED_CAPS); } - // Public for use in VcnGatewayConnectionTest - public static VcnGatewayConnectionConfig buildTestConfig() { + // Public for use in UnderlyingNetworkControllerTest + public static VcnGatewayConnectionConfig buildTestConfig( + List<VcnUnderlyingNetworkTemplate> nwTemplates) { final VcnGatewayConnectionConfig.Builder builder = - newBuilder().setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_TEMPLATES); + newBuilder() + .setVcnUnderlyingNetworkPriorities(nwTemplates) + .setMinUdpPort4500NatTimeoutSeconds(MIN_UDP_PORT_4500_NAT_TIMEOUT); return buildTestConfigWithExposedCaps(builder, EXPOSED_CAPS); } + // Public for use in VcnGatewayConnectionTest + public static VcnGatewayConnectionConfig buildTestConfig() { + return buildTestConfig(UNDERLYING_NETWORK_TEMPLATES); + } + private static VcnGatewayConnectionConfig.Builder newBuilder() { // Append a unique identifier to the name prefix to guarantee that all created // VcnGatewayConnectionConfigs have a unique name (required by VcnConfig). @@ -109,10 +125,16 @@ public class VcnGatewayConnectionConfigTest { TUNNEL_CONNECTION_PARAMS); } - private static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps( - VcnGatewayConnectionConfig.Builder builder, int... exposedCaps) { + private static VcnGatewayConnectionConfig buildTestConfigWithExposedCapsAndOptions( + VcnGatewayConnectionConfig.Builder builder, + Set<Integer> gatewayOptions, + int... exposedCaps) { builder.setRetryIntervalsMillis(RETRY_INTERVALS_MS).setMaxMtu(MAX_MTU); + for (int option : gatewayOptions) { + builder.addGatewayOption(option); + } + for (int caps : exposedCaps) { builder.addExposedCapability(caps); } @@ -120,11 +142,28 @@ public class VcnGatewayConnectionConfigTest { return builder.build(); } + private static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps( + VcnGatewayConnectionConfig.Builder builder, int... exposedCaps) { + return buildTestConfigWithExposedCapsAndOptions( + builder, Collections.emptySet(), exposedCaps); + } + // Public for use in VcnGatewayConnectionTest public static VcnGatewayConnectionConfig buildTestConfigWithExposedCaps(int... exposedCaps) { return buildTestConfigWithExposedCaps(newBuilder(), exposedCaps); } + private static VcnGatewayConnectionConfig buildTestConfigWithGatewayOptions( + VcnGatewayConnectionConfig.Builder builder, Set<Integer> gatewayOptions) { + return buildTestConfigWithExposedCapsAndOptions(builder, gatewayOptions, EXPOSED_CAPS); + } + + // Public for use in VcnGatewayConnectionTest + public static VcnGatewayConnectionConfig buildTestConfigWithGatewayOptions( + Set<Integer> gatewayOptions) { + return buildTestConfigWithExposedCapsAndOptions(newBuilder(), gatewayOptions, EXPOSED_CAPS); + } + @Test public void testBuilderRequiresNonNullGatewayConnectionName() { try { @@ -211,6 +250,15 @@ public class VcnGatewayConnectionConfigTest { } @Test + public void testBuilderRequiresValidOption() { + try { + newBuilder().addGatewayOption(-1); + fail("Expected exception due to the invalid VCN gateway option"); + } catch (IllegalArgumentException e) { + } + } + + @Test public void testBuilderAndGetters() { final VcnGatewayConnectionConfig config = buildTestConfig(); @@ -225,6 +273,20 @@ public class VcnGatewayConnectionConfigTest { assertArrayEquals(RETRY_INTERVALS_MS, config.getRetryIntervalsMillis()); assertEquals(MAX_MTU, config.getMaxMtu()); + + assertFalse( + config.hasGatewayOption( + VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY)); + } + + @Test + public void testBuilderAndGettersWithOptions() { + final VcnGatewayConnectionConfig config = + buildTestConfigWithGatewayOptions(GATEWAY_OPTIONS); + + for (int option : GATEWAY_OPTIONS) { + assertTrue(config.hasGatewayOption(option)); + } } @Test @@ -235,6 +297,14 @@ public class VcnGatewayConnectionConfigTest { } @Test + public void testPersistableBundleWithOptions() { + final VcnGatewayConnectionConfig config = + buildTestConfigWithGatewayOptions(GATEWAY_OPTIONS); + + assertEquals(config, new VcnGatewayConnectionConfig(config.toPersistableBundle())); + } + + @Test public void testParsePersistableBundleWithoutVcnUnderlyingNetworkTemplates() { PersistableBundle configBundle = buildTestConfig().toPersistableBundle(); configBundle.putPersistableBundle(UNDERLYING_NETWORK_TEMPLATES_KEY, null); @@ -318,4 +388,27 @@ public class VcnGatewayConnectionConfigTest { assertNotEquals(UNDERLYING_NETWORK_TEMPLATES, networkTemplatesNotEqual); assertNotEquals(config, configNotEqual); } + + private static VcnGatewayConnectionConfig buildConfigWithGatewayOptionsForEqualityTest( + Set<Integer> gatewayOptions) { + return buildTestConfigWithGatewayOptions( + new VcnGatewayConnectionConfig.Builder( + "buildConfigWithGatewayOptionsForEqualityTest", TUNNEL_CONNECTION_PARAMS), + gatewayOptions); + } + + @Test + public void testVcnGatewayOptionsEquality() throws Exception { + final VcnGatewayConnectionConfig config = + buildConfigWithGatewayOptionsForEqualityTest(GATEWAY_OPTIONS); + + final VcnGatewayConnectionConfig configEqual = + buildConfigWithGatewayOptionsForEqualityTest(GATEWAY_OPTIONS); + + final VcnGatewayConnectionConfig configNotEqual = + buildConfigWithGatewayOptionsForEqualityTest(Collections.emptySet()); + + assertEquals(config, configEqual); + assertNotEquals(config, configNotEqual); + } } diff --git a/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java b/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java index 19df3c75266c..81814b67f5ee 100644 --- a/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java +++ b/tests/vcn/java/android/net/vcn/VcnTransportInfoTest.java @@ -19,6 +19,7 @@ package android.net.vcn; import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION; import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS; import static android.net.NetworkCapabilities.REDACT_NONE; +import static android.net.vcn.VcnGatewayConnectionConfig.MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static org.junit.Assert.assertEquals; @@ -37,11 +38,14 @@ import java.util.Arrays; public class VcnTransportInfoTest { private static final int SUB_ID = 1; private static final int NETWORK_ID = 5; + private static final int MIN_UDP_PORT_4500_NAT_TIMEOUT = 120; private static final WifiInfo WIFI_INFO = new WifiInfo.Builder().setNetworkId(NETWORK_ID).build(); - private static final VcnTransportInfo CELL_UNDERLYING_INFO = new VcnTransportInfo(SUB_ID); - private static final VcnTransportInfo WIFI_UNDERLYING_INFO = new VcnTransportInfo(WIFI_INFO); + private static final VcnTransportInfo CELL_UNDERLYING_INFO = + new VcnTransportInfo(SUB_ID, MIN_UDP_PORT_4500_NAT_TIMEOUT); + private static final VcnTransportInfo WIFI_UNDERLYING_INFO = + new VcnTransportInfo(WIFI_INFO, MIN_UDP_PORT_4500_NAT_TIMEOUT); @Test public void testGetWifiInfo() { @@ -58,6 +62,16 @@ public class VcnTransportInfoTest { } @Test + public void testGetMinUdpPort4500NatTimeoutSeconds() { + assertEquals( + MIN_UDP_PORT_4500_NAT_TIMEOUT, + CELL_UNDERLYING_INFO.getMinUdpPort4500NatTimeoutSeconds()); + assertEquals( + MIN_UDP_PORT_4500_NAT_TIMEOUT, + WIFI_UNDERLYING_INFO.getMinUdpPort4500NatTimeoutSeconds()); + } + + @Test public void testMakeCopyRedactForNetworkSettings() { for (VcnTransportInfo info : Arrays.asList(CELL_UNDERLYING_INFO, WIFI_UNDERLYING_INFO)) { assertEquals( @@ -67,6 +81,10 @@ public class VcnTransportInfoTest { assertNull( ((VcnTransportInfo) info.makeCopy(REDACT_FOR_NETWORK_SETTINGS)) .getWifiInfo()); + assertEquals( + MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET, + ((VcnTransportInfo) info.makeCopy(REDACT_FOR_NETWORK_SETTINGS)) + .getMinUdpPort4500NatTimeoutSeconds()); } } @@ -77,9 +95,17 @@ public class VcnTransportInfoTest { ((VcnTransportInfo) CELL_UNDERLYING_INFO.makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION)) .getSubId()); assertEquals( + MIN_UDP_PORT_4500_NAT_TIMEOUT, + ((VcnTransportInfo) CELL_UNDERLYING_INFO.makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION)) + .getMinUdpPort4500NatTimeoutSeconds()); + assertEquals( WifiConfiguration.INVALID_NETWORK_ID, ((VcnTransportInfo) WIFI_UNDERLYING_INFO.makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION)) .getWifiInfo().getNetworkId()); + assertEquals( + MIN_UDP_PORT_4500_NAT_TIMEOUT, + ((VcnTransportInfo) WIFI_UNDERLYING_INFO.makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION)) + .getMinUdpPort4500NatTimeoutSeconds()); } @Test @@ -110,8 +136,12 @@ public class VcnTransportInfoTest { public void testParcelNotRedactedForSysUi() { VcnTransportInfo cellRedacted = parcelForSysUi(CELL_UNDERLYING_INFO); assertEquals(SUB_ID, cellRedacted.getSubId()); + assertEquals( + MIN_UDP_PORT_4500_NAT_TIMEOUT, cellRedacted.getMinUdpPort4500NatTimeoutSeconds()); VcnTransportInfo wifiRedacted = parcelForSysUi(WIFI_UNDERLYING_INFO); assertEquals(NETWORK_ID, wifiRedacted.getWifiInfo().getNetworkId()); + assertEquals( + MIN_UDP_PORT_4500_NAT_TIMEOUT, wifiRedacted.getMinUdpPort4500NatTimeoutSeconds()); } private VcnTransportInfo parcelForSysUi(VcnTransportInfo vcnTransportInfo) { diff --git a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java index 3b201f9d20dd..9f7d2390938f 100644 --- a/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java +++ b/tests/vcn/java/android/net/vcn/persistablebundleutils/IkeSessionParamsUtilsTest.java @@ -16,6 +16,9 @@ package android.net.vcn.persistablebundleutils; +import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_AUTOMATIC_KEEPALIVE_ON_OFF; +import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION; +import static android.net.vcn.persistablebundleutils.IkeSessionParamsUtils.IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; import static android.telephony.TelephonyManager.APPTYPE_USIM; @@ -134,15 +137,35 @@ public class IkeSessionParamsUtilsTest { verifyPersistableBundleEncodeDecodeIsLossless(params); } + private static IkeSessionParams.Builder createBuilderMinimumWithEap() throws Exception { + final X509Certificate serverCaCert = createCertFromPemFile("self-signed-ca.pem"); + + final byte[] eapId = "test@android.net".getBytes(StandardCharsets.US_ASCII); + final int subId = 1; + final EapSessionConfig eapConfig = + new EapSessionConfig.Builder() + .setEapIdentity(eapId) + .setEapSimConfig(subId, APPTYPE_USIM) + .setEapAkaConfig(subId, APPTYPE_USIM) + .build(); + return createBuilderMinimum().setAuthEap(serverCaCert, eapConfig); + } + @Test public void testEncodeDecodeParamsWithIkeOptions() throws Exception { - final IkeSessionParams params = - createBuilderMinimum() + final IkeSessionParams.Builder builder = + createBuilderMinimumWithEap() .addIkeOption(IkeSessionParams.IKE_OPTION_ACCEPT_ANY_REMOTE_ID) + .addIkeOption(IkeSessionParams.IKE_OPTION_EAP_ONLY_AUTH) .addIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE) + .addIkeOption(IkeSessionParams.IKE_OPTION_FORCE_PORT_4500) .addIkeOption(IkeSessionParams.IKE_OPTION_INITIAL_CONTACT) - .build(); - verifyPersistableBundleEncodeDecodeIsLossless(params); + .addIkeOption(IkeSessionParams.IKE_OPTION_REKEY_MOBILITY) + .addIkeOption(IKE_OPTION_AUTOMATIC_ADDRESS_FAMILY_SELECTION) + .addIkeOption(IKE_OPTION_AUTOMATIC_NATT_KEEPALIVES) + .addIkeOption(IKE_OPTION_AUTOMATIC_KEEPALIVE_ON_OFF); + + verifyPersistableBundleEncodeDecodeIsLossless(builder.build()); } private static InputStream openAssetsFile(String fileName) throws Exception { @@ -176,19 +199,7 @@ public class IkeSessionParamsUtilsTest { @Test public void testEncodeRecodeParamsWithEapAuth() throws Exception { - final X509Certificate serverCaCert = createCertFromPemFile("self-signed-ca.pem"); - - final byte[] eapId = "test@android.net".getBytes(StandardCharsets.US_ASCII); - final int subId = 1; - final EapSessionConfig eapConfig = - new EapSessionConfig.Builder() - .setEapIdentity(eapId) - .setEapSimConfig(subId, APPTYPE_USIM) - .setEapAkaConfig(subId, APPTYPE_USIM) - .build(); - - final IkeSessionParams params = - createBuilderMinimum().setAuthEap(serverCaCert, eapConfig).build(); + final IkeSessionParams params = createBuilderMinimumWithEap().build(); verifyPersistableBundleEncodeDecodeIsLossless(params); } } diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index f924b2e9b932..960b57cb632a 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -17,10 +17,14 @@ package com.android.server; import static android.net.ConnectivityManager.NetworkCallback; +import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE; +import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.vcn.VcnManager.VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; @@ -65,7 +69,6 @@ import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; -import android.net.TelephonyNetworkSpecifier; import android.net.Uri; import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; @@ -95,6 +98,7 @@ import com.android.server.vcn.Vcn; import com.android.server.vcn.VcnContext; import com.android.server.vcn.VcnNetworkProvider; import com.android.server.vcn.util.PersistableBundleUtils; +import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import org.junit.Before; import org.junit.Test; @@ -114,6 +118,7 @@ import java.util.UUID; @RunWith(AndroidJUnit4.class) @SmallTest public class VcnManagementServiceTest { + private static final String CONTEXT_ATTRIBUTION_TAG = "VCN"; private static final String TEST_PACKAGE_NAME = VcnManagementServiceTest.class.getPackage().getName(); private static final String TEST_PACKAGE_NAME_2 = "TEST_PKG_2"; @@ -125,6 +130,15 @@ public class VcnManagementServiceTest { private static final VcnConfig TEST_VCN_CONFIG; private static final VcnConfig TEST_VCN_CONFIG_PKG_2; private static final int TEST_UID = Process.FIRST_APPLICATION_UID; + private static final String TEST_IFACE_NAME = "TEST_IFACE"; + private static final String TEST_IFACE_NAME_2 = "TEST_IFACE2"; + private static final LinkProperties TEST_LP_1 = new LinkProperties(); + private static final LinkProperties TEST_LP_2 = new LinkProperties(); + + static { + TEST_LP_1.setInterfaceName(TEST_IFACE_NAME); + TEST_LP_2.setInterfaceName(TEST_IFACE_NAME_2); + } static { final Context mockConfigContext = mock(Context.class); @@ -164,6 +178,7 @@ public class VcnManagementServiceTest { 0 /* carrierId */, 0 /* profileClass */); + private final Context mMockContextWithoutAttributionTag = mock(Context.class); private final Context mMockContext = mock(Context.class); private final VcnManagementService.Dependencies mMockDeps = mock(VcnManagementService.Dependencies.class); @@ -189,6 +204,10 @@ public class VcnManagementServiceTest { private final IBinder mMockIBinder = mock(IBinder.class); public VcnManagementServiceTest() throws Exception { + doReturn(mMockContext) + .when(mMockContextWithoutAttributionTag) + .createAttributionContext(CONTEXT_ATTRIBUTION_TAG); + setupSystemService( mMockContext, mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); setupSystemService( @@ -236,7 +255,7 @@ public class VcnManagementServiceTest { doReturn(bundle).when(mConfigReadWriteHelper).readFromDisk(); setupMockedCarrierPrivilege(true); - mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps); + mVcnMgmtSvc = new VcnManagementService(mMockContextWithoutAttributionTag, mMockDeps); setupActiveSubscription(TEST_UUID_1); doReturn(mMockIBinder).when(mMockPolicyListener).asBinder(); @@ -252,6 +271,10 @@ public class VcnManagementServiceTest { .when(mMockContext) .enforceCallingOrSelfPermission( eq(android.Manifest.permission.NETWORK_FACTORY), any()); + + doReturn(Collections.singleton(TRANSPORT_WIFI)) + .when(mMockDeps) + .getRestrictedTransports(any(), any(), any()); } @@ -637,8 +660,7 @@ public class VcnManagementServiceTest { final BroadcastReceiver receiver = getPackageChangeReceiver(); verify(mMockContext).registerReceiver(any(), argThat(filter -> { - return filter.hasAction(Intent.ACTION_PACKAGE_REMOVED) - && filter.hasAction(Intent.ACTION_PACKAGE_REMOVED); + return filter.hasAction(Intent.ACTION_PACKAGE_REMOVED); }), any(), any()); receiver.onReceive(mMockContext, new Intent(Intent.ACTION_PACKAGE_REMOVED)); @@ -1028,70 +1050,232 @@ public class VcnManagementServiceTest { setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive); return mVcnMgmtSvc.getUnderlyingNetworkPolicy( - getNetworkCapabilitiesBuilderForTransport(subId, transport).build(), - new LinkProperties()); + getNetworkCapabilitiesBuilderForTransport(subId, transport).build(), TEST_LP_1); + } + + private void checkGetRestrictedTransportsFromCarrierConfig( + ParcelUuid subGrp, + TelephonySubscriptionSnapshot lastSnapshot, + Set<Integer> expectedTransports) { + Set<Integer> result = + new VcnManagementService.Dependencies() + .getRestrictedTransportsFromCarrierConfig(subGrp, lastSnapshot); + assertEquals(expectedTransports, result); } @Test - public void testGetUnderlyingNetworkPolicyCellular() throws Exception { + public void testGetRestrictedTransportsFromCarrierConfig() { + final Set<Integer> restrictedTransports = new ArraySet<>(); + restrictedTransports.add(TRANSPORT_CELLULAR); + restrictedTransports.add(TRANSPORT_WIFI); + + PersistableBundle carrierConfigBundle = new PersistableBundle(); + carrierConfigBundle.putIntArray( + VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY, + restrictedTransports.stream().mapToInt(i -> i).toArray()); + final PersistableBundleWrapper carrierConfig = + new PersistableBundleWrapper(carrierConfigBundle); + + final TelephonySubscriptionSnapshot lastSnapshot = + mock(TelephonySubscriptionSnapshot.class); + doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2)); + + checkGetRestrictedTransportsFromCarrierConfig( + TEST_UUID_2, lastSnapshot, restrictedTransports); + } + + @Test + public void testGetRestrictedTransportsFromCarrierConfig_noRestrictPolicyConfigured() { + final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI); + + final PersistableBundleWrapper carrierConfig = + new PersistableBundleWrapper(new PersistableBundle()); + final TelephonySubscriptionSnapshot lastSnapshot = + mock(TelephonySubscriptionSnapshot.class); + doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2)); + + checkGetRestrictedTransportsFromCarrierConfig( + TEST_UUID_2, lastSnapshot, restrictedTransports); + } + + @Test + public void testGetRestrictedTransportsFromCarrierConfig_noCarrierConfig() { + final Set<Integer> restrictedTransports = Collections.singleton(TRANSPORT_WIFI); + + final TelephonySubscriptionSnapshot lastSnapshot = + mock(TelephonySubscriptionSnapshot.class); + + checkGetRestrictedTransportsFromCarrierConfig( + TEST_UUID_2, lastSnapshot, restrictedTransports); + } + + @Test + public void testGetRestrictedTransportsFromCarrierConfigAndVcnConfig() { + // Configure restricted transport in CarrierConfig + final Set<Integer> restrictedTransportInCarrierConfig = + Collections.singleton(TRANSPORT_WIFI); + + PersistableBundle carrierConfigBundle = new PersistableBundle(); + carrierConfigBundle.putIntArray( + VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY, + restrictedTransportInCarrierConfig.stream().mapToInt(i -> i).toArray()); + final PersistableBundleWrapper carrierConfig = + new PersistableBundleWrapper(carrierConfigBundle); + + final TelephonySubscriptionSnapshot lastSnapshot = + mock(TelephonySubscriptionSnapshot.class); + doReturn(carrierConfig).when(lastSnapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2)); + + // Configure restricted transport in VcnConfig + final Context mockContext = mock(Context.class); + doReturn(TEST_PACKAGE_NAME).when(mockContext).getOpPackageName(); + final VcnConfig vcnConfig = + VcnConfigTest.buildTestConfig( + mockContext, Collections.singleton(TRANSPORT_CELLULAR)); + + // Verifications + final Set<Integer> expectedTransports = new ArraySet<>(); + expectedTransports.add(TRANSPORT_CELLULAR); + expectedTransports.add(TRANSPORT_WIFI); + + Set<Integer> result = + new VcnManagementService.Dependencies() + .getRestrictedTransports(TEST_UUID_2, lastSnapshot, vcnConfig); + assertEquals(expectedTransports, result); + } + + private void checkGetUnderlyingNetworkPolicy( + int transportType, + boolean isTransportRestricted, + boolean isActive, + boolean expectVcnManaged, + boolean expectRestricted) + throws Exception { + + final Set<Integer> restrictedTransports = new ArraySet(); + if (isTransportRestricted) { + restrictedTransports.add(transportType); + } + doReturn(restrictedTransports).when(mMockDeps).getRestrictedTransports(any(), any(), any()); + final VcnUnderlyingNetworkPolicy policy = startVcnAndGetPolicyForTransport( - TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */, TRANSPORT_CELLULAR); + TEST_SUBSCRIPTION_ID, TEST_UUID_2, isActive, transportType); assertFalse(policy.isTeardownRequested()); verifyMergedNetworkCapabilities( policy.getMergedNetworkCapabilities(), + transportType, + expectVcnManaged, + expectRestricted); + } + + @Test + public void testGetUnderlyingNetworkPolicy_unrestrictCell() throws Exception { + checkGetUnderlyingNetworkPolicy( TRANSPORT_CELLULAR, - true /* isVcnManaged */, - false /* isRestricted */); + false /* isTransportRestricted */, + true /* isActive */, + true /* expectVcnManaged */, + false /* expectRestricted */); } @Test - public void testGetUnderlyingNetworkPolicyCellular_safeMode() throws Exception { - final VcnUnderlyingNetworkPolicy policy = - startVcnAndGetPolicyForTransport( - TEST_SUBSCRIPTION_ID, - TEST_UUID_2, - false /* isActive */, - TRANSPORT_CELLULAR); + public void testGetUnderlyingNetworkPolicy_unrestrictCellSafeMode() throws Exception { + checkGetUnderlyingNetworkPolicy( + TRANSPORT_CELLULAR, + false /* isTransportRestricted */, + false /* isActive */, + false /* expectVcnManaged */, + false /* expectRestricted */); + } - assertFalse(policy.isTeardownRequested()); - verifyMergedNetworkCapabilities( - policy.getMergedNetworkCapabilities(), - NetworkCapabilities.TRANSPORT_CELLULAR, - false /* isVcnManaged */, - false /* isRestricted */); + @Test + public void testGetUnderlyingNetworkPolicy_restrictCell() throws Exception { + checkGetUnderlyingNetworkPolicy( + TRANSPORT_CELLULAR, + true /* isTransportRestricted */, + true /* isActive */, + true /* expectVcnManaged */, + true /* expectRestricted */); } @Test - public void testGetUnderlyingNetworkPolicyWifi() throws Exception { - final VcnUnderlyingNetworkPolicy policy = - startVcnAndGetPolicyForTransport( - TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isActive */, TRANSPORT_WIFI); + public void testGetUnderlyingNetworkPolicy_restrictCellSafeMode() throws Exception { + checkGetUnderlyingNetworkPolicy( + TRANSPORT_CELLULAR, + true /* isTransportRestricted */, + false /* isActive */, + false /* expectVcnManaged */, + false /* expectRestricted */); + } - assertFalse(policy.isTeardownRequested()); - verifyMergedNetworkCapabilities( - policy.getMergedNetworkCapabilities(), - NetworkCapabilities.TRANSPORT_WIFI, - true /* isVcnManaged */, - true /* isRestricted */); + @Test + public void testGetUnderlyingNetworkPolicy_unrestrictWifi() throws Exception { + checkGetUnderlyingNetworkPolicy( + TRANSPORT_WIFI, + false /* isTransportRestricted */, + true /* isActive */, + true /* expectVcnManaged */, + false /* expectRestricted */); + } + + @Test + public void testGetUnderlyingNetworkPolicy_unrestrictWifiSafeMode() throws Exception { + checkGetUnderlyingNetworkPolicy( + TRANSPORT_WIFI, + false /* isTransportRestricted */, + false /* isActive */, + false /* expectVcnManaged */, + false /* expectRestricted */); + } + + @Test + public void testGetUnderlyingNetworkPolicy_restrictWifi() throws Exception { + checkGetUnderlyingNetworkPolicy( + TRANSPORT_WIFI, + true /* isTransportRestricted */, + true /* isActive */, + true /* expectVcnManaged */, + true /* expectRestricted */); + } + + @Test + public void testGetUnderlyingNetworkPolicy_restrictWifiSafeMode() throws Exception { + checkGetUnderlyingNetworkPolicy( + TRANSPORT_WIFI, + true /* isTransportRestricted */, + false /* isActive */, + false /* expectVcnManaged */, + true /* expectRestricted */); } @Test - public void testGetUnderlyingNetworkPolicyVcnWifi_safeMode() throws Exception { + public void testGetUnderlyingNetworkPolicyCell_restrictWifi() throws Exception { + doReturn(Collections.singleton(TRANSPORT_WIFI)) + .when(mMockDeps) + .getRestrictedTransports(any(), any(), any()); + + setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_2, true /* isVcnActive */); + + // Get the policy for a cellular network and expect it won't be affected by the wifi + // restriction policy final VcnUnderlyingNetworkPolicy policy = - startVcnAndGetPolicyForTransport( - TEST_SUBSCRIPTION_ID, TEST_UUID_2, false /* isActive */, TRANSPORT_WIFI); + mVcnMgmtSvc.getUnderlyingNetworkPolicy( + getNetworkCapabilitiesBuilderForTransport( + TEST_SUBSCRIPTION_ID, TRANSPORT_CELLULAR) + .build(), + new LinkProperties()); assertFalse(policy.isTeardownRequested()); verifyMergedNetworkCapabilities( policy.getMergedNetworkCapabilities(), - NetworkCapabilities.TRANSPORT_WIFI, - false /* isVcnManaged */, - true /* isRestricted */); + TRANSPORT_CELLULAR, + true /* expectVcnManaged */, + false /* expectRestricted */); } - private void setupTrackedCarrierWifiNetwork(NetworkCapabilities caps) { + private void setupTrackedNetwork(NetworkCapabilities caps, LinkProperties lp) { mVcnMgmtSvc.systemReady(); final ArgumentCaptor<NetworkCallback> captor = @@ -1100,7 +1284,10 @@ public class VcnManagementServiceTest { .registerNetworkCallback( eq(new NetworkRequest.Builder().clearCapabilities().build()), captor.capture()); - captor.getValue().onCapabilitiesChanged(mock(Network.class, CALLS_REAL_METHODS), caps); + + Network mockNetwork = mock(Network.class, CALLS_REAL_METHODS); + captor.getValue().onCapabilitiesChanged(mockNetwork, caps); + captor.getValue().onLinkPropertiesChanged(mockNetwork, lp); } @Test @@ -1110,7 +1297,7 @@ public class VcnManagementServiceTest { getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI) .removeCapability(NET_CAPABILITY_NOT_RESTRICTED) .build(); - setupTrackedCarrierWifiNetwork(existingNetworkCaps); + setupTrackedNetwork(existingNetworkCaps, TEST_LP_1); // Trigger test without VCN instance alive; expect restart due to change of NOT_RESTRICTED // immutable capability @@ -1119,7 +1306,7 @@ public class VcnManagementServiceTest { getNetworkCapabilitiesBuilderForTransport( TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI) .build(), - new LinkProperties()); + TEST_LP_1); assertTrue(policy.isTeardownRequested()); } @@ -1129,7 +1316,7 @@ public class VcnManagementServiceTest { final NetworkCapabilities existingNetworkCaps = getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI) .build(); - setupTrackedCarrierWifiNetwork(existingNetworkCaps); + setupTrackedNetwork(existingNetworkCaps, TEST_LP_1); final VcnUnderlyingNetworkPolicy policy = startVcnAndGetPolicyForTransport( @@ -1139,6 +1326,27 @@ public class VcnManagementServiceTest { } @Test + public void testGetUnderlyingNetworkPolicyForRestrictedImsWhenUnrestrictingCell() + throws Exception { + final NetworkCapabilities existingNetworkCaps = + getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_NOT_RESTRICTED) + .removeCapability(NET_CAPABILITY_IMS) + .build(); + setupTrackedNetwork(existingNetworkCaps, TEST_LP_1); + + final VcnUnderlyingNetworkPolicy policy = + mVcnMgmtSvc.getUnderlyingNetworkPolicy( + getNetworkCapabilitiesBuilderForTransport( + TEST_SUBSCRIPTION_ID, TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_IMS) + .removeCapability(NET_CAPABILITY_NOT_RESTRICTED) + .build(), + new LinkProperties()); + assertFalse(policy.isTeardownRequested()); + } + + @Test public void testGetUnderlyingNetworkPolicyNonVcnNetwork() throws Exception { setupSubscriptionAndStartVcn(TEST_SUBSCRIPTION_ID, TEST_UUID_1, true /* isActive */); @@ -1146,7 +1354,7 @@ public class VcnManagementServiceTest { new NetworkCapabilities.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) - .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID_2)) + .setSubscriptionIds(Collections.singleton(TEST_SUBSCRIPTION_ID_2)) .build(); VcnUnderlyingNetworkPolicy policy = @@ -1156,6 +1364,38 @@ public class VcnManagementServiceTest { assertEquals(nc, policy.getMergedNetworkCapabilities()); } + /** + * Checks that networks with similar capabilities do not clobber each other. + * + * <p>In previous iterations, the VcnMgmtSvc used capability-matching to check if a network + * undergoing policy checks were the same as an existing networks. However, this meant that if + * there were newly added capabilities that the VCN did not check, two networks differing only + * by that capability would restart each other constantly. + */ + @Test + public void testGetUnderlyingNetworkPolicySimilarNetworks() throws Exception { + NetworkCapabilities nc1 = + new NetworkCapabilities.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) + .addCapability(NET_CAPABILITY_INTERNET) + .setSubscriptionIds(Collections.singleton(TEST_SUBSCRIPTION_ID_2)) + .build(); + + NetworkCapabilities nc2 = + new NetworkCapabilities.Builder(nc1) + .addCapability(NET_CAPABILITY_ENTERPRISE) + .removeCapability(NET_CAPABILITY_NOT_RESTRICTED) + .build(); + + setupTrackedNetwork(nc1, TEST_LP_1); + + VcnUnderlyingNetworkPolicy policy = mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc2, TEST_LP_2); + + assertFalse(policy.isTeardownRequested()); + assertEquals(nc2, policy.getMergedNetworkCapabilities()); + } + @Test(expected = SecurityException.class) public void testGetUnderlyingNetworkPolicyInvalidPermission() { doReturn(PackageManager.PERMISSION_DENIED) @@ -1192,6 +1432,23 @@ public class VcnManagementServiceTest { } @Test + public void testVcnConfigChangeUpdatesPolicyListener() throws Exception { + setupActiveSubscription(TEST_UUID_2); + + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); + mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); + + final Context mockContext = mock(Context.class); + doReturn(TEST_PACKAGE_NAME).when(mockContext).getOpPackageName(); + final VcnConfig vcnConfig = + VcnConfigTest.buildTestConfig( + mockContext, Collections.singleton(TRANSPORT_CELLULAR)); + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, vcnConfig, TEST_PACKAGE_NAME); + + verify(mMockPolicyListener).onPolicyChanged(); + } + + @Test public void testRemoveVcnUpdatesPolicyListener() throws Exception { setupActiveSubscription(TEST_UUID_2); @@ -1218,6 +1475,30 @@ public class VcnManagementServiceTest { verify(mMockPolicyListener).onPolicyChanged(); } + @Test + public void testVcnCarrierConfigChangeUpdatesPolicyListener() throws Exception { + setupActiveSubscription(TEST_UUID_2); + + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); + mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); + + final TelephonySubscriptionSnapshot snapshot = + buildSubscriptionSnapshot( + TEST_SUBSCRIPTION_ID, + TEST_UUID_2, + Collections.singleton(TEST_UUID_2), + Collections.emptyMap(), + true /* hasCarrierPrivileges */); + + final PersistableBundleWrapper mockCarrierConfig = mock(PersistableBundleWrapper.class); + doReturn(mockCarrierConfig).when(snapshot).getCarrierConfigForSubGrp(eq(TEST_UUID_2)); + + final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); + cb.onNewSnapshot(snapshot); + + verify(mMockPolicyListener).onPolicyChanged(); + } + private void triggerVcnSafeMode( @NonNull ParcelUuid subGroup, @NonNull TelephonySubscriptionSnapshot snapshot, diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java index 09080be9ee41..34f884b94296 100644 --- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java @@ -16,9 +16,9 @@ package com.android.server.vcn; -import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED; -import static android.telephony.CarrierConfigManager.EXTRA_SLOT_INDEX; -import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.vcn.VcnManager.VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY; import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; @@ -39,6 +39,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -139,6 +140,8 @@ public class TelephonySubscriptionTrackerTest { @NonNull private TelephonySubscriptionTrackerCallback mCallback; @NonNull private TelephonySubscriptionTracker mTelephonySubscriptionTracker; + @NonNull private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener; + public TelephonySubscriptionTrackerTest() { mContext = mock(Context.class); mTestLooper = new TestLooper(); @@ -169,7 +172,7 @@ public class TelephonySubscriptionTrackerTest { .getSystemService(Context.CARRIER_CONFIG_SERVICE); doReturn(TEST_CARRIER_CONFIG) .when(mCarrierConfigManager) - .getConfigForSubId(eq(TEST_SUBSCRIPTION_ID_1)); + .getConfigForSubId(eq(TEST_SUBSCRIPTION_ID_1), any()); // subId 1, 2 are in same subGrp, only subId 1 is active doReturn(TEST_PARCEL_UUID).when(TEST_SUBINFO_1).getGroupUuid(); @@ -185,9 +188,15 @@ public class TelephonySubscriptionTrackerTest { doReturn(2).when(mTelephonyManager).getActiveModemCount(); mCallback = mock(TelephonySubscriptionTrackerCallback.class); + // Capture CarrierConfigChangeListener to emulate the carrier config change notification + ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor = + ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class); mTelephonySubscriptionTracker = new TelephonySubscriptionTracker(mContext, mHandler, mCallback, mDeps); mTelephonySubscriptionTracker.register(); + verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(), + listenerArgumentCaptor.capture()); + mCarrierConfigChangeListener = listenerArgumentCaptor.getAllValues().get(0); doReturn(true).when(mDeps).isConfigForIdentifiedCarrier(any()); doReturn(Arrays.asList(TEST_SUBINFO_1, TEST_SUBINFO_2)) @@ -235,14 +244,11 @@ public class TelephonySubscriptionTrackerTest { return intent; } - private Intent buildTestBroadcastIntent(boolean hasValidSubscription) { - Intent intent = new Intent(ACTION_CARRIER_CONFIG_CHANGED); - intent.putExtra(EXTRA_SLOT_INDEX, TEST_SIM_SLOT_INDEX); - intent.putExtra( - EXTRA_SUBSCRIPTION_INDEX, - hasValidSubscription ? TEST_SUBSCRIPTION_ID_1 : INVALID_SUBSCRIPTION_ID); - - return intent; + private void sendCarrierConfigChange(boolean hasValidSubscription) { + mCarrierConfigChangeListener.onCarrierConfigChanged( + TEST_SIM_SLOT_INDEX, + hasValidSubscription ? TEST_SUBSCRIPTION_ID_1 : INVALID_SUBSCRIPTION_ID, + TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID); } private TelephonySubscriptionSnapshot buildExpectedSnapshot( @@ -298,14 +304,15 @@ public class TelephonySubscriptionTrackerTest { any(), eq(mHandler)); final IntentFilter filter = getIntentFilter(); - assertEquals(2, filter.countActions()); - assertTrue(filter.hasAction(ACTION_CARRIER_CONFIG_CHANGED)); + assertEquals(1, filter.countActions()); assertTrue(filter.hasAction(ACTION_MULTI_SIM_CONFIG_CHANGED)); verify(mSubscriptionManager) .addOnSubscriptionsChangedListener(any(HandlerExecutor.class), any()); assertNotNull(getOnSubscriptionsChangedListener()); + verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(), any()); + verify(mTelephonyManager, times(2)) .registerCarrierPrivilegesCallback(anyInt(), any(HandlerExecutor.class), any()); verify(mTelephonyManager) @@ -438,7 +445,7 @@ public class TelephonySubscriptionTrackerTest { @Test public void testReceiveBroadcast_ConfigReadyWithSubscriptions() throws Exception { - mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); + sendCarrierConfigChange(true /* hasValidSubscription */); mTestLooper.dispatchAll(); verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); @@ -450,7 +457,7 @@ public class TelephonySubscriptionTrackerTest { .when(mSubscriptionManager) .getAllSubscriptionInfoList(); - mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); + sendCarrierConfigChange(true /* hasValidSubscription */); mTestLooper.dispatchAll(); // Expect an empty snapshot @@ -461,7 +468,7 @@ public class TelephonySubscriptionTrackerTest { public void testReceiveBroadcast_SlotCleared() throws Exception { setupReadySubIds(); - mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false)); + sendCarrierConfigChange(false /* hasValidSubscription */); mTestLooper.dispatchAll(); verifyNoActiveSubscriptions(); @@ -472,7 +479,7 @@ public class TelephonySubscriptionTrackerTest { public void testReceiveBroadcast_ConfigNotReady() throws Exception { doReturn(false).when(mDeps).isConfigForIdentifiedCarrier(any()); - mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); + sendCarrierConfigChange(true /* hasValidSubscription */); mTestLooper.dispatchAll(); // No interactions expected; config was not loaded @@ -481,27 +488,58 @@ public class TelephonySubscriptionTrackerTest { @Test public void testSubscriptionsClearedAfterValidTriggersCallbacks() throws Exception { - mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); + sendCarrierConfigChange(true /* hasValidSubscription */); mTestLooper.dispatchAll(); verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); assertNotNull( mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX)); doReturn(Collections.emptyList()).when(mSubscriptionManager).getAllSubscriptionInfoList(); - mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); + sendCarrierConfigChange(true /* hasValidSubscription */); mTestLooper.dispatchAll(); verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(emptyMap(), emptyMap()))); } @Test + public void testCarrierConfigUpdatedAfterValidTriggersCallbacks() throws Exception { + sendCarrierConfigChange(true /* hasValidSubscription */); + mTestLooper.dispatchAll(); + verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); + reset(mCallback); + + final PersistableBundle updatedConfig = new PersistableBundle(); + updatedConfig.putIntArray( + VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY, + new int[] {TRANSPORT_WIFI, TRANSPORT_CELLULAR}); + doReturn(updatedConfig) + .when(mCarrierConfigManager) + .getConfigForSubId(eq(TEST_SUBSCRIPTION_ID_1), any()); + + Map<Integer, PersistableBundleWrapper> subIdToCarrierConfigMap = new HashMap<>(); + subIdToCarrierConfigMap.put( + TEST_SUBSCRIPTION_ID_1, new PersistableBundleWrapper(updatedConfig)); + sendCarrierConfigChange(true /* hasValidSubscription */); + mTestLooper.dispatchAll(); + + verify(mCallback) + .onNewSnapshot( + eq( + buildExpectedSnapshot( + 0, + TEST_SUBID_TO_INFO_MAP, + subIdToCarrierConfigMap, + TEST_PRIVILEGED_PACKAGES))); + } + + @Test public void testSlotClearedAfterValidTriggersCallbacks() throws Exception { - mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true)); + sendCarrierConfigChange(true /* hasValidSubscription */); mTestLooper.dispatchAll(); verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(TEST_PRIVILEGED_PACKAGES))); assertNotNull( mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX)); - mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false)); + sendCarrierConfigChange(false /* hasValidSubscription */); mTestLooper.dispatchAll(); verify(mCallback) .onNewSnapshot( diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index 15d4f1097108..89271e1218d8 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -25,6 +25,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED; import static android.net.ipsec.ike.exceptions.IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE; +import static android.net.vcn.VcnGatewayConnectionConfigTest.MIN_UDP_PORT_4500_NAT_TIMEOUT; import static android.net.vcn.VcnManager.VCN_ERROR_CODE_CONFIG_ERROR; import static android.net.vcn.VcnManager.VCN_ERROR_CODE_INTERNAL_ERROR; import static android.net.vcn.VcnManager.VCN_ERROR_CODE_NETWORK_ERROR; @@ -50,23 +51,30 @@ import static org.mockito.Mockito.when; import static java.util.Collections.singletonList; +import android.net.ConnectivityDiagnosticsManager.DataStallReport; import android.net.ConnectivityManager; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.Network; import android.net.NetworkAgent; import android.net.NetworkCapabilities; import android.net.ipsec.ike.ChildSaProposal; import android.net.ipsec.ike.IkeSessionConnectionInfo; +import android.net.ipsec.ike.TunnelModeChildSessionParams; import android.net.ipsec.ike.exceptions.IkeException; import android.net.ipsec.ike.exceptions.IkeInternalException; import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnGatewayConnectionConfigTest; import android.net.vcn.VcnManager.VcnErrorCode; +import android.net.vcn.VcnTransportInfo; +import android.os.PersistableBundle; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.vcn.VcnGatewayConnection.VcnChildSessionCallback; +import com.android.server.vcn.routeselection.UnderlyingNetworkRecord; import com.android.server.vcn.util.MtuUtils; import org.junit.Before; @@ -86,8 +94,11 @@ import java.util.function.Consumer; @RunWith(AndroidJUnit4.class) @SmallTest public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase { + private static final int PARALLEL_SA_COUNT = 4; + private VcnIkeSession mIkeSession; private VcnNetworkAgent mNetworkAgent; + private Network mVcnNetwork; @Before public void setUp() throws Exception { @@ -98,6 +109,9 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection .when(mDeps) .newNetworkAgent(any(), any(), any(), any(), any(), any(), any(), any(), any()); + mVcnNetwork = mock(Network.class); + doReturn(mVcnNetwork).when(mNetworkAgent).getNetwork(); + mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1); mIkeSession = mGatewayConnection.buildIkeSession(TEST_UNDERLYING_NETWORK_RECORD_1.network); @@ -166,19 +180,82 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); } + private void verifyDataStallTriggersMigration( + UnderlyingNetworkRecord networkRecord, + Network networkWithDataStall, + boolean expectMobilityUpdate) + throws Exception { + mGatewayConnection.setUnderlyingNetwork(networkRecord); + triggerChildOpened(); + mTestLooper.dispatchAll(); + + final DataStallReport report = + new DataStallReport( + networkWithDataStall, + 1234 /* reportTimestamp */, + 1 /* detectionMethod */, + new LinkProperties(), + new NetworkCapabilities(), + new PersistableBundle()); + + mGatewayConnection.getConnectivityDiagnosticsCallback().onDataStallSuspected(report); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); + + if (expectMobilityUpdate) { + verify(mIkeSession).setNetwork(networkRecord.network); + } else { + verify(mIkeSession, never()).setNetwork(any(Network.class)); + } + } + + @Test + public void testDataStallTriggersMigration() throws Exception { + verifyDataStallTriggersMigration( + TEST_UNDERLYING_NETWORK_RECORD_1, mVcnNetwork, true /* expectMobilityUpdate */); + } + + @Test + public void testDataStallWontTriggerMigrationWhenOnOtherNetwork() throws Exception { + verifyDataStallTriggersMigration( + TEST_UNDERLYING_NETWORK_RECORD_1, + mock(Network.class), + false /* expectMobilityUpdate */); + } + + @Test + public void testDataStallWontTriggerMigrationWhenUnderlyingNetworkLost() throws Exception { + verifyDataStallTriggersMigration( + null /* networkRecord */, mock(Network.class), false /* expectMobilityUpdate */); + } + private void verifyVcnTransformsApplied( VcnGatewayConnection vcnGatewayConnection, boolean expectForwardTransform) throws Exception { + verifyVcnTransformsApplied( + vcnGatewayConnection, + expectForwardTransform, + Collections.singletonList(getChildSessionCallback())); + } + + private void verifyVcnTransformsApplied( + VcnGatewayConnection vcnGatewayConnection, + boolean expectForwardTransform, + List<VcnChildSessionCallback> callbacks) + throws Exception { for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) { - getChildSessionCallback().onIpSecTransformCreated(makeDummyIpSecTransform(), direction); + for (VcnChildSessionCallback cb : callbacks) { + cb.onIpSecTransformCreated(makeDummyIpSecTransform(), direction); + } mTestLooper.dispatchAll(); - verify(mIpSecSvc) + verify(mIpSecSvc, times(callbacks.size())) .applyTunnelModeTransform( eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any()); } - verify(mIpSecSvc, expectForwardTransform ? times(1) : never()) + verify(mIpSecSvc, expectForwardTransform ? times(callbacks.size()) : never()) .applyTunnelModeTransform( eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(DIRECTION_FWD), anyInt(), any()); @@ -350,6 +427,12 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection assertTrue(nc.hasCapability(cap)); } + assertTrue(nc.getTransportInfo() instanceof VcnTransportInfo); + final VcnTransportInfo vcnTransportInfo = (VcnTransportInfo) nc.getTransportInfo(); + assertEquals( + MIN_UDP_PORT_4500_NAT_TIMEOUT, + vcnTransportInfo.getMinUdpPort4500NatTimeoutSeconds()); + // Now that Vcn Network is up, notify it as validated and verify the SafeMode alarm is // canceled triggerValidation(NetworkAgent.VALIDATION_STATUS_VALID); @@ -358,6 +441,89 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection verifySafeModeStateAndCallbackFired(1 /* invocationCount */, false /* isInSafeMode */); } + private List<VcnChildSessionCallback> openChildAndVerifyParallelSasRequested() + throws Exception { + doReturn(PARALLEL_SA_COUNT) + .when(mDeps) + .getParallelTunnelCount(eq(TEST_SUBSCRIPTION_SNAPSHOT), eq(TEST_SUB_GRP)); + + // Verify scheduled but not canceled when entering ConnectedState + verifySafeModeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + triggerChildOpened(); + mTestLooper.dispatchAll(); + + // Verify new child sessions requested + final ArgumentCaptor<VcnChildSessionCallback> captor = + ArgumentCaptor.forClass(VcnChildSessionCallback.class); + verify(mIkeSession, times(PARALLEL_SA_COUNT - 1)) + .openChildSession(any(TunnelModeChildSessionParams.class), captor.capture()); + + return captor.getAllValues(); + } + + private List<VcnChildSessionCallback> verifyChildOpenedRequestsAndAppliesParallelSas() + throws Exception { + List<VcnChildSessionCallback> callbacks = openChildAndVerifyParallelSasRequested(); + + verifyVcnTransformsApplied(mGatewayConnection, false, callbacks); + + // Mock IKE calling of onOpened() + for (VcnChildSessionCallback cb : callbacks) { + cb.onOpened(mock(VcnChildSessionConfiguration.class)); + } + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); + return callbacks; + } + + @Test + public void testChildOpenedWithParallelSas() throws Exception { + verifyChildOpenedRequestsAndAppliesParallelSas(); + } + + @Test + public void testOpportunisticSa_ignoresPreOpenFailures() throws Exception { + List<VcnChildSessionCallback> callbacks = openChildAndVerifyParallelSasRequested(); + + for (VcnChildSessionCallback cb : callbacks) { + cb.onClosed(); + cb.onClosedExceptionally(mock(IkeException.class)); + } + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); + assertEquals(mIkeConnectionInfo, mGatewayConnection.getIkeConnectionInfo()); + } + + private void verifyPostOpenFailuresCloseSession(boolean shouldCloseWithException) + throws Exception { + List<VcnChildSessionCallback> callbacks = verifyChildOpenedRequestsAndAppliesParallelSas(); + + for (VcnChildSessionCallback cb : callbacks) { + if (shouldCloseWithException) { + cb.onClosed(); + } else { + cb.onClosedExceptionally(mock(IkeException.class)); + } + } + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + verify(mIkeSession).close(); + } + + @Test + public void testOpportunisticSa_handlesPostOpenFailures_onClosed() throws Exception { + verifyPostOpenFailuresCloseSession(false /* shouldCloseWithException */); + } + + @Test + public void testOpportunisticSa_handlesPostOpenFailures_onClosedExceptionally() + throws Exception { + verifyPostOpenFailuresCloseSession(true /* shouldCloseWithException */); + } + @Test public void testInternalAndDnsAddressesChanged() throws Exception { final List<LinkAddress> startingInternalAddrs = diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java index 6a9a1e22cab1..692c8a8f0898 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java @@ -24,6 +24,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.vcn.VcnGatewayConnectionConfig.VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY; import static com.android.server.vcn.VcnGatewayConnection.DUMMY_ADDR; import static com.android.server.vcn.VcnGatewayConnection.VcnChildSessionConfiguration; @@ -34,20 +35,25 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback; import android.net.IpSecManager; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; +import android.net.NetworkRequest; import android.net.TelephonyNetworkSpecifier; +import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnGatewayConnectionConfigTest; import android.net.vcn.VcnTransportInfo; import android.net.wifi.WifiInfo; @@ -64,6 +70,7 @@ import com.android.server.vcn.routeselection.UnderlyingNetworkRecord; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import java.net.InetAddress; import java.util.Arrays; @@ -71,7 +78,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; +import java.util.concurrent.Executor; /** Tests for TelephonySubscriptionTracker */ @RunWith(AndroidJUnit4.class) @@ -134,9 +143,9 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { capBuilder.setLinkDownstreamBandwidthKbps(TEST_DOWNSTREAM_BANDWIDTH); capBuilder.setAdministratorUids(new int[] {TEST_UID}); final Network underlyingNetwork = mock(Network.class, CALLS_REAL_METHODS); - UnderlyingNetworkRecord record = new UnderlyingNetworkRecord( - underlyingNetwork, - capBuilder.build(), new LinkProperties(), false); + UnderlyingNetworkRecord record = + getTestNetworkRecord( + underlyingNetwork, capBuilder.build(), new LinkProperties(), false); final NetworkCapabilities vcnCaps = VcnGatewayConnection.buildNetworkCapabilities( VcnGatewayConnectionConfigTest.buildTestConfig(), @@ -202,7 +211,7 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { doReturn(TEST_DNS_ADDRESSES).when(childSessionConfig).getInternalDnsServers(); UnderlyingNetworkRecord record = - new UnderlyingNetworkRecord( + getTestNetworkRecord( mock(Network.class, CALLS_REAL_METHODS), new NetworkCapabilities.Builder().build(), underlyingLp, @@ -287,5 +296,60 @@ public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { verify(vcnNetworkAgent).unregister(); verifyWakeLockReleased(); + + verify(mConnDiagMgr) + .unregisterConnectivityDiagnosticsCallback( + mGatewayConnection.getConnectivityDiagnosticsCallback()); + } + + private VcnGatewayConnection buildConnectionWithDataStallHandling( + boolean datatStallHandlingEnabled) throws Exception { + Set<Integer> options = + datatStallHandlingEnabled + ? Collections.singleton( + VCN_GATEWAY_OPTION_ENABLE_DATA_STALL_RECOVERY_WITH_MOBILITY) + : Collections.emptySet(); + final VcnGatewayConnectionConfig gatewayConfig = + VcnGatewayConnectionConfigTest.buildTestConfigWithGatewayOptions(options); + final VcnGatewayConnection gatewayConnection = + new VcnGatewayConnection( + mVcnContext, + TEST_SUB_GRP, + TEST_SUBSCRIPTION_SNAPSHOT, + gatewayConfig, + mGatewayStatusCallback, + true /* isMobileDataEnabled */, + mDeps); + return gatewayConnection; + } + + @Test + public void testDataStallHandlingEnabled() throws Exception { + final VcnGatewayConnection gatewayConnection = + buildConnectionWithDataStallHandling(true /* datatStallHandlingEnabled */); + + final ArgumentCaptor<NetworkRequest> networkRequestCaptor = + ArgumentCaptor.forClass(NetworkRequest.class); + verify(mConnDiagMgr) + .registerConnectivityDiagnosticsCallback( + networkRequestCaptor.capture(), + any(Executor.class), + eq(gatewayConnection.getConnectivityDiagnosticsCallback())); + + final NetworkRequest nr = networkRequestCaptor.getValue(); + final NetworkRequest expected = + new NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).build(); + assertEquals(expected, nr); + } + + @Test + public void testDataStallHandlingDisabled() throws Exception { + buildConnectionWithDataStallHandling(false /* datatStallHandlingEnabled */); + + verify(mConnDiagMgr, never()) + .registerConnectivityDiagnosticsCallback( + any(NetworkRequest.class), + any(Executor.class), + any(ConnectivityDiagnosticsCallback.class)); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index 785bff167ad2..5efbf598f941 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -16,6 +16,8 @@ package com.android.server.vcn; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; + import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import static com.android.server.vcn.VcnGatewayConnection.VcnNetworkAgent; import static com.android.server.vcn.VcnTestUtils.setupIpSecManager; @@ -35,6 +37,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import android.annotation.NonNull; import android.content.Context; +import android.net.ConnectivityDiagnosticsManager; import android.net.ConnectivityManager; import android.net.InetAddresses; import android.net.IpSecConfig; @@ -45,6 +48,7 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; +import android.net.TelephonyNetworkSpecifier; import android.net.ipsec.ike.ChildSessionCallback; import android.net.ipsec.ike.IkeSessionCallback; import android.net.ipsec.ike.IkeSessionConfiguration; @@ -108,11 +112,28 @@ public class VcnGatewayConnectionTestBase { protected static final long ELAPSED_REAL_TIME = 123456789L; protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE"; + protected static UnderlyingNetworkRecord getTestNetworkRecord( + Network network, + NetworkCapabilities networkCapabilities, + LinkProperties linkProperties, + boolean isBlocked) { + return new UnderlyingNetworkRecord( + network, + networkCapabilities, + linkProperties, + isBlocked, + false /* isSelected */, + 0 /* priorityClass */); + } + protected static final String TEST_TCP_BUFFER_SIZES_1 = "1,2,3,4"; protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 = - new UnderlyingNetworkRecord( + getTestNetworkRecord( mock(Network.class, CALLS_REAL_METHODS), - new NetworkCapabilities(), + new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_CELLULAR) + .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUB_ID)) + .build(), new LinkProperties(), false /* blocked */); @@ -123,7 +144,7 @@ public class VcnGatewayConnectionTestBase { protected static final String TEST_TCP_BUFFER_SIZES_2 = "2,3,4,5"; protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_2 = - new UnderlyingNetworkRecord( + getTestNetworkRecord( mock(Network.class, CALLS_REAL_METHODS), new NetworkCapabilities(), new LinkProperties(), @@ -157,6 +178,7 @@ public class VcnGatewayConnectionTestBase { @NonNull protected final IpSecService mIpSecSvc; @NonNull protected final ConnectivityManager mConnMgr; + @NonNull protected final ConnectivityDiagnosticsManager mConnDiagMgr; @NonNull protected final IkeSessionConnectionInfo mIkeConnectionInfo; @NonNull protected final IkeSessionConfiguration mIkeSessionConfiguration; @@ -186,6 +208,13 @@ public class VcnGatewayConnectionTestBase { VcnTestUtils.setupSystemService( mContext, mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); + mConnDiagMgr = mock(ConnectivityDiagnosticsManager.class); + VcnTestUtils.setupSystemService( + mContext, + mConnDiagMgr, + Context.CONNECTIVITY_DIAGNOSTICS_SERVICE, + ConnectivityDiagnosticsManager.class); + mIkeConnectionInfo = new IkeSessionConnectionInfo(TEST_ADDR, TEST_ADDR_2, mock(Network.class)); mIkeSessionConfiguration = new IkeSessionConfiguration.Builder(mIkeConnectionInfo).build(); @@ -200,6 +229,9 @@ public class VcnGatewayConnectionTestBase { doReturn(mWakeLock) .when(mDeps) .newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any()); + doReturn(1) + .when(mDeps) + .getParallelTunnelCount(eq(TEST_SUBSCRIPTION_SNAPSHOT), eq(TEST_SUB_GRP)); setUpWakeupMessage(mTeardownTimeoutAlarm, VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM); setUpWakeupMessage(mDisconnectRequestAlarm, VcnGatewayConnection.DISCONNECT_REQUEST_ALARM); diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java index b0d68952c39d..226604108522 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java @@ -16,6 +16,7 @@ package com.android.server.vcn.routeselection; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN; import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED; import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_ENTRY_DOWNSTREAM_BANDWIDTH_KBPS; @@ -24,8 +25,8 @@ import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT import static android.net.vcn.VcnUnderlyingNetworkTemplateTestBase.TEST_MIN_EXIT_UPSTREAM_BANDWIDTH_KBPS; import static com.android.server.vcn.VcnTestUtils.setupSystemService; -import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_ANY; -import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.calculatePriorityClass; +import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_FALLBACK; +import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_INVALID; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesCellPriorityRule; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesPriorityRule; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesWifiPriorityRule; @@ -48,6 +49,7 @@ import android.net.TelephonyNetworkSpecifier; import android.net.vcn.VcnCellUnderlyingNetworkTemplate; import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnManager; +import android.net.vcn.VcnUnderlyingNetworkTemplate; import android.net.vcn.VcnWifiUnderlyingNetworkTemplate; import android.os.ParcelUuid; import android.os.PersistableBundle; @@ -64,6 +66,8 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.Collections; +import java.util.List; import java.util.Set; import java.util.UUID; @@ -91,6 +95,7 @@ public class NetworkPriorityClassifierTest { private static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES = new NetworkCapabilities.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .setSignalStrength(WIFI_RSSI) .setSsid(SSID) .setLinkUpstreamBandwidthKbps(LINK_UPSTREAM_BANDWIDTH_KBPS) @@ -102,6 +107,7 @@ public class NetworkPriorityClassifierTest { private static final NetworkCapabilities CELL_NETWORK_CAPABILITIES = new NetworkCapabilities.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN) .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .setSubscriptionIds(Set.of(SUB_ID)) .setNetworkSpecifier(TEL_NETWORK_SPECIFIER) @@ -135,25 +141,35 @@ public class NetworkPriorityClassifierTest { false /* isInTestMode */)); doNothing().when(mVcnContext).ensureRunningOnLooperThread(); - mWifiNetworkRecord = - new UnderlyingNetworkRecord( - mNetwork, - WIFI_NETWORK_CAPABILITIES, - LINK_PROPERTIES, - false /* isBlocked */); - - mCellNetworkRecord = - new UnderlyingNetworkRecord( - mNetwork, - CELL_NETWORK_CAPABILITIES, - LINK_PROPERTIES, - false /* isBlocked */); - setupSystemService( mockContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class); when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager); when(mTelephonyManager.getNetworkOperator()).thenReturn(PLMN_ID); when(mTelephonyManager.getSimSpecificCarrierId()).thenReturn(CARRIER_ID); + + mWifiNetworkRecord = + getTestNetworkRecord( + WIFI_NETWORK_CAPABILITIES, + VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES); + mCellNetworkRecord = + getTestNetworkRecord( + CELL_NETWORK_CAPABILITIES, + VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES); + } + + private UnderlyingNetworkRecord getTestNetworkRecord( + NetworkCapabilities nc, List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates) { + return new UnderlyingNetworkRecord( + mNetwork, + nc, + LINK_PROPERTIES, + false /* isBlocked */, + mVcnContext, + underlyingNetworkTemplates, + SUB_GROUP, + mSubscriptionSnapshot, + null /* currentlySelected */, + null /* carrierConfig */); } @Test @@ -490,37 +506,74 @@ public class NetworkPriorityClassifierTest { mSubscriptionSnapshot)); } - private void verifyCalculatePriorityClass( - UnderlyingNetworkRecord networkRecord, int expectedIndex) { - final int priorityIndex = - calculatePriorityClass( + private void verifyMatchCellWithRequiredCapabilities( + VcnCellUnderlyingNetworkTemplate template, boolean expectMatch) { + assertEquals( + expectMatch, + checkMatchesPriorityRule( mVcnContext, - networkRecord, - VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES, + template, + mCellNetworkRecord, SUB_GROUP, mSubscriptionSnapshot, null /* currentlySelected */, - null /* carrierConfig */); + null /* carrierConfig */)); + } - assertEquals(expectedIndex, priorityIndex); + @Test + public void testMatchCell() { + final VcnCellUnderlyingNetworkTemplate template = + getCellNetworkPriorityBuilder().setInternet(MATCH_REQUIRED).build(); + verifyMatchCellWithRequiredCapabilities(template, true /* expectMatch */); } @Test - public void testCalculatePriorityClass() throws Exception { - verifyCalculatePriorityClass(mCellNetworkRecord, 2); + public void testMatchCellFail_RequiredCapabilitiesMissing() { + final VcnCellUnderlyingNetworkTemplate template = + getCellNetworkPriorityBuilder().setCbs(MATCH_REQUIRED).build(); + verifyMatchCellWithRequiredCapabilities(template, false /* expectMatch */); } @Test - public void testCalculatePriorityClassFailToMatchAny() throws Exception { - final NetworkCapabilities nc = + public void testMatchCellFail_ForbiddenCapabilitiesFound() { + final VcnCellUnderlyingNetworkTemplate template = + getCellNetworkPriorityBuilder().setDun(MATCH_FORBIDDEN).build(); + verifyMatchCellWithRequiredCapabilities(template, false /* expectMatch */); + } + + @Test + public void testCalculatePriorityClass() throws Exception { + assertEquals(2, mCellNetworkRecord.priorityClass); + } + + private void checkCalculatePriorityClassFailToMatchAny( + boolean hasInternet, int expectedPriorityClass) throws Exception { + final List<VcnUnderlyingNetworkTemplate> templatesRequireDun = + Collections.singletonList( + new VcnCellUnderlyingNetworkTemplate.Builder() + .setDun(MATCH_REQUIRED) + .build()); + + final NetworkCapabilities.Builder ncBuilder = new NetworkCapabilities.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .setSignalStrength(WIFI_RSSI_LOW) - .setSsid(SSID) - .build(); - final UnderlyingNetworkRecord wifiNetworkRecord = - new UnderlyingNetworkRecord(mNetwork, nc, LINK_PROPERTIES, false /* isBlocked */); + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + if (hasInternet) { + ncBuilder.addCapability(NET_CAPABILITY_INTERNET); + } + + final UnderlyingNetworkRecord nonDunNetworkRecord = + getTestNetworkRecord(ncBuilder.build(), templatesRequireDun); + + assertEquals(expectedPriorityClass, nonDunNetworkRecord.priorityClass); + } - verifyCalculatePriorityClass(wifiNetworkRecord, PRIORITY_ANY); + @Test + public void testCalculatePriorityClassFailToMatchAny_InternetNetwork() throws Exception { + checkCalculatePriorityClassFailToMatchAny(true /* hasInternet */, PRIORITY_FALLBACK); + } + + @Test + public void testCalculatePriorityClassFailToMatchAny_NonInternetNetwork() throws Exception { + checkCalculatePriorityClassFailToMatchAny(false /* hasInternet */, PRIORITY_INVALID); } } diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java index fad9669911bb..2941fdea20bb 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java @@ -16,18 +16,29 @@ package com.android.server.vcn.routeselection; +import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; +import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; +import static android.net.vcn.VcnCellUnderlyingNetworkTemplate.MATCH_ANY; +import static android.net.vcn.VcnCellUnderlyingNetworkTemplate.MATCH_FORBIDDEN; +import static android.net.vcn.VcnCellUnderlyingNetworkTemplate.MATCH_REQUIRED; + import static com.android.server.vcn.VcnTestUtils.setupSystemService; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT; import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.WIFI_EXIT_RSSI_THRESHOLD_DEFAULT; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -42,7 +53,10 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.TelephonyNetworkSpecifier; +import android.net.vcn.VcnCellUnderlyingNetworkTemplate; +import android.net.vcn.VcnCellUnderlyingNetworkTemplateTest; import android.net.vcn.VcnGatewayConnectionConfigTest; +import android.net.vcn.VcnUnderlyingNetworkTemplate; import android.os.ParcelUuid; import android.os.test.TestLooper; import android.telephony.CarrierConfigManager; @@ -64,7 +78,10 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Set; import java.util.UUID; @@ -95,11 +112,39 @@ public class UnderlyingNetworkControllerTest { .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .build(); + private static final NetworkCapabilities DUN_NETWORK_CAPABILITIES = + new NetworkCapabilities.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) + .build(); + + private static final NetworkCapabilities CBS_NETWORK_CAPABILITIES = + new NetworkCapabilities.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_CBS) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) + .build(); + private static final LinkProperties INITIAL_LINK_PROPERTIES = getLinkPropertiesWithName("initial_iface"); private static final LinkProperties UPDATED_LINK_PROPERTIES = getLinkPropertiesWithName("updated_iface"); + private static final VcnCellUnderlyingNetworkTemplate CELL_TEMPLATE_DUN = + new VcnCellUnderlyingNetworkTemplate.Builder() + .setInternet(MATCH_ANY) + .setDun(MATCH_REQUIRED) + .build(); + + private static final VcnCellUnderlyingNetworkTemplate CELL_TEMPLATE_CBS = + new VcnCellUnderlyingNetworkTemplate.Builder() + .setInternet(MATCH_ANY) + .setCbs(MATCH_REQUIRED) + .build(); + @Mock private Context mContext; @Mock private VcnNetworkProvider mVcnNetworkProvider; @Mock private ConnectivityManager mConnectivityManager; @@ -201,6 +246,107 @@ public class UnderlyingNetworkControllerTest { any()); } + private void verifyRequestBackgroundNetwork( + ConnectivityManager cm, + int expectedSubId, + Set<Integer> expectedRequiredCaps, + Set<Integer> expectedForbiddenCaps) { + verify(cm) + .requestBackgroundNetwork( + eq( + getCellRequestForSubId( + expectedSubId, + expectedRequiredCaps, + expectedForbiddenCaps)), + any(NetworkBringupCallback.class), + any()); + } + + @Test + public void testNetworkCallbacksRegisteredOnStartupForNonInternetCapabilities() { + final ConnectivityManager cm = mock(ConnectivityManager.class); + setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); + + // Build network templates + final List<VcnUnderlyingNetworkTemplate> networkTemplates = new ArrayList(); + + networkTemplates.add( + VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplateBuilder() + .setDun(MATCH_REQUIRED) + .setInternet(MATCH_ANY) + .build()); + + networkTemplates.add( + VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplateBuilder() + .setMms(MATCH_REQUIRED) + .setCbs(MATCH_FORBIDDEN) + .setInternet(MATCH_ANY) + .build()); + + // Start UnderlyingNetworkController + new UnderlyingNetworkController( + mVcnContext, + VcnGatewayConnectionConfigTest.buildTestConfig(networkTemplates), + SUB_GROUP, + mSubscriptionSnapshot, + mNetworkControllerCb); + + // Verifications + for (final int subId : INITIAL_SUB_IDS) { + verifyRequestBackgroundNetwork( + cm, + subId, + Collections.singleton(NET_CAPABILITY_INTERNET), + Collections.emptySet()); + verifyRequestBackgroundNetwork( + cm, subId, Collections.singleton(NET_CAPABILITY_DUN), Collections.emptySet()); + verifyRequestBackgroundNetwork( + cm, + subId, + Collections.singleton(NET_CAPABILITY_MMS), + Collections.singleton(NET_CAPABILITY_CBS)); + } + } + + @Test + public void testNetworkCallbacksRegisteredOnStartupWithDedupedtCapabilities() { + final ConnectivityManager cm = mock(ConnectivityManager.class); + setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); + + // Build network templates + final List<VcnUnderlyingNetworkTemplate> networkTemplates = new ArrayList(); + final VcnCellUnderlyingNetworkTemplate.Builder builder = + new VcnCellUnderlyingNetworkTemplate.Builder() + .setMms(MATCH_REQUIRED) + .setCbs(MATCH_FORBIDDEN) + .setInternet(MATCH_ANY); + + networkTemplates.add(builder.setMetered(MATCH_REQUIRED).build()); + networkTemplates.add(builder.setMetered(MATCH_FORBIDDEN).build()); + + // Start UnderlyingNetworkController + new UnderlyingNetworkController( + mVcnContext, + VcnGatewayConnectionConfigTest.buildTestConfig(networkTemplates), + SUB_GROUP, + mSubscriptionSnapshot, + mNetworkControllerCb); + + // Verifications + for (final int subId : INITIAL_SUB_IDS) { + verifyRequestBackgroundNetwork( + cm, + subId, + Collections.singleton(NET_CAPABILITY_INTERNET), + Collections.emptySet()); + verifyRequestBackgroundNetwork( + cm, + subId, + Collections.singleton(NET_CAPABILITY_MMS), + Collections.singleton(NET_CAPABILITY_CBS)); + } + } + private void verifyNetworkRequestsRegistered(Set<Integer> expectedSubIds) { verify(mConnectivityManager) .requestBackgroundNetwork( @@ -210,8 +356,13 @@ public class UnderlyingNetworkControllerTest { for (final int subId : expectedSubIds) { verify(mConnectivityManager) .requestBackgroundNetwork( - eq(getCellRequestForSubId(subId)), - any(NetworkBringupCallback.class), any()); + eq( + getCellRequestForSubId( + subId, + Collections.singleton(NET_CAPABILITY_INTERNET), + Collections.emptySet())), + any(NetworkBringupCallback.class), + any()); } verify(mConnectivityManager) @@ -253,6 +404,7 @@ public class UnderlyingNetworkControllerTest { private NetworkRequest getWifiRequest(Set<Integer> netCapsSubIds) { return getExpectedRequestBase() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .setSubscriptionIds(netCapsSubIds) .build(); } @@ -261,6 +413,7 @@ public class UnderlyingNetworkControllerTest { // TODO (b/187991063): Add tests for carrier-config based thresholds return getExpectedRequestBase() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .setSubscriptionIds(netCapsSubIds) .setSignalStrength(WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT) .build(); @@ -270,16 +423,27 @@ public class UnderlyingNetworkControllerTest { // TODO (b/187991063): Add tests for carrier-config based thresholds return getExpectedRequestBase() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .setSubscriptionIds(netCapsSubIds) .setSignalStrength(WIFI_EXIT_RSSI_THRESHOLD_DEFAULT) .build(); } - private NetworkRequest getCellRequestForSubId(int subId) { - return getExpectedRequestBase() - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)) - .build(); + private NetworkRequest getCellRequestForSubId( + int subId, Set<Integer> requiredCaps, Set<Integer> forbiddenCaps) { + final NetworkRequest.Builder nqBuilder = + getExpectedRequestBase() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .setNetworkSpecifier(new TelephonyNetworkSpecifier(subId)); + + for (int cap : requiredCaps) { + nqBuilder.addCapability(cap); + } + for (int cap : forbiddenCaps) { + nqBuilder.addForbiddenCapability(cap); + } + + return nqBuilder.build(); } private NetworkRequest getRouteSelectionRequest(Set<Integer> netCapsSubIds) { @@ -301,7 +465,6 @@ public class UnderlyingNetworkControllerTest { private NetworkRequest.Builder getExpectedRequestBase() { final NetworkRequest.Builder builder = new NetworkRequest.Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED) .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); @@ -321,16 +484,30 @@ public class UnderlyingNetworkControllerTest { .unregisterNetworkCallback(any(UnderlyingNetworkListener.class)); } + private static UnderlyingNetworkRecord getTestNetworkRecord( + Network network, + NetworkCapabilities networkCapabilities, + LinkProperties linkProperties, + boolean isBlocked) { + return new UnderlyingNetworkRecord( + network, + networkCapabilities, + linkProperties, + isBlocked, + false /* isSelected */, + 0 /* priorityClass */); + } + @Test public void testUnderlyingNetworkRecordEquals() { UnderlyingNetworkRecord recordA = - new UnderlyingNetworkRecord( + getTestNetworkRecord( mNetwork, INITIAL_NETWORK_CAPABILITIES, INITIAL_LINK_PROPERTIES, false /* isBlocked */); UnderlyingNetworkRecord recordB = - new UnderlyingNetworkRecord( + getTestNetworkRecord( mNetwork, INITIAL_NETWORK_CAPABILITIES, INITIAL_LINK_PROPERTIES, @@ -338,12 +515,24 @@ public class UnderlyingNetworkControllerTest { UnderlyingNetworkRecord recordC = new UnderlyingNetworkRecord( mNetwork, + INITIAL_NETWORK_CAPABILITIES, + INITIAL_LINK_PROPERTIES, + false /* isBlocked */, + true /* isSelected */, + -1 /* priorityClass */); + UnderlyingNetworkRecord recordD = + getTestNetworkRecord( + mNetwork, UPDATED_NETWORK_CAPABILITIES, UPDATED_LINK_PROPERTIES, false /* isBlocked */); assertEquals(recordA, recordB); - assertNotEquals(recordA, recordC); + assertEquals(recordA, recordC); + assertNotEquals(recordA, recordD); + + assertTrue(UnderlyingNetworkRecord.isEqualIncludingPriorities(recordA, recordB)); + assertFalse(UnderlyingNetworkRecord.isEqualIncludingPriorities(recordA, recordC)); } @Test @@ -366,6 +555,10 @@ public class UnderlyingNetworkControllerTest { .build(); } + private void verifyOnSelectedUnderlyingNetworkChanged(UnderlyingNetworkRecord expectedRecord) { + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + } + private UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback( NetworkCapabilities networkCapabilities) { verify(mConnectivityManager) @@ -384,12 +577,12 @@ public class UnderlyingNetworkControllerTest { cb.onBlockedStatusChanged(mNetwork, false /* isFalse */); UnderlyingNetworkRecord expectedRecord = - new UnderlyingNetworkRecord( + getTestNetworkRecord( mNetwork, responseNetworkCaps, INITIAL_LINK_PROPERTIES, false /* isBlocked */); - verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verifyOnSelectedUnderlyingNetworkChanged(expectedRecord); return cb; } @@ -402,12 +595,12 @@ public class UnderlyingNetworkControllerTest { cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); UnderlyingNetworkRecord expectedRecord = - new UnderlyingNetworkRecord( + getTestNetworkRecord( mNetwork, responseNetworkCaps, INITIAL_LINK_PROPERTIES, false /* isBlocked */); - verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verifyOnSelectedUnderlyingNetworkChanged(expectedRecord); } @Test @@ -417,12 +610,12 @@ public class UnderlyingNetworkControllerTest { cb.onLinkPropertiesChanged(mNetwork, UPDATED_LINK_PROPERTIES); UnderlyingNetworkRecord expectedRecord = - new UnderlyingNetworkRecord( + getTestNetworkRecord( mNetwork, buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS), UPDATED_LINK_PROPERTIES, false /* isBlocked */); - verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verifyOnSelectedUnderlyingNetworkChanged(expectedRecord); } @Test @@ -434,18 +627,16 @@ public class UnderlyingNetworkControllerTest { cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); UnderlyingNetworkRecord expectedRecord = - new UnderlyingNetworkRecord( + getTestNetworkRecord( mNetwork, responseNetworkCaps, INITIAL_LINK_PROPERTIES, false /* isBlocked */); - verify(mNetworkControllerCb, times(1)) - .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verifyOnSelectedUnderlyingNetworkChanged(expectedRecord); // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't // change. cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); - verify(mNetworkControllerCb, times(1)) - .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verifyOnSelectedUnderlyingNetworkChanged(expectedRecord); } @Test @@ -458,18 +649,16 @@ public class UnderlyingNetworkControllerTest { cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); UnderlyingNetworkRecord expectedRecord = - new UnderlyingNetworkRecord( + getTestNetworkRecord( mNetwork, responseNetworkCaps, INITIAL_LINK_PROPERTIES, false /* isBlocked */); - verify(mNetworkControllerCb, times(1)) - .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verifyOnSelectedUnderlyingNetworkChanged(expectedRecord); // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't // change. cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps); - verify(mNetworkControllerCb, times(1)) - .onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verifyOnSelectedUnderlyingNetworkChanged(expectedRecord); } @Test @@ -478,13 +667,7 @@ public class UnderlyingNetworkControllerTest { cb.onBlockedStatusChanged(mNetwork, true /* isBlocked */); - UnderlyingNetworkRecord expectedRecord = - new UnderlyingNetworkRecord( - mNetwork, - buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS), - INITIAL_LINK_PROPERTIES, - true /* isBlocked */); - verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord)); + verifyOnSelectedUnderlyingNetworkChanged(null); } @Test @@ -520,5 +703,132 @@ public class UnderlyingNetworkControllerTest { verify(mNetworkControllerCb, times(1)).onSelectedUnderlyingNetworkChanged(any()); } - // TODO (b/187991063): Add tests for network prioritization + private UnderlyingNetworkListener setupControllerAndGetNetworkListener( + List<VcnUnderlyingNetworkTemplate> networkTemplates) { + final ConnectivityManager cm = mock(ConnectivityManager.class); + setupSystemService(mContext, cm, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); + + new UnderlyingNetworkController( + mVcnContext, + VcnGatewayConnectionConfigTest.buildTestConfig(networkTemplates), + SUB_GROUP, + mSubscriptionSnapshot, + mNetworkControllerCb); + + verify(cm) + .registerNetworkCallback( + eq(getRouteSelectionRequest(INITIAL_SUB_IDS)), + mUnderlyingNetworkListenerCaptor.capture(), + any()); + + return mUnderlyingNetworkListenerCaptor.getValue(); + } + + private UnderlyingNetworkRecord bringupNetworkAndGetRecord( + UnderlyingNetworkListener cb, + NetworkCapabilities requestNetworkCaps, + List<VcnUnderlyingNetworkTemplate> underlyingNetworkTemplates, + UnderlyingNetworkRecord currentlySelected) { + final Network network = mock(Network.class); + final NetworkCapabilities responseNetworkCaps = + buildResponseNwCaps(requestNetworkCaps, INITIAL_SUB_IDS); + + cb.onAvailable(network); + cb.onCapabilitiesChanged(network, responseNetworkCaps); + cb.onLinkPropertiesChanged(network, INITIAL_LINK_PROPERTIES); + cb.onBlockedStatusChanged(network, false /* isFalse */); + return new UnderlyingNetworkRecord( + network, + responseNetworkCaps, + INITIAL_LINK_PROPERTIES, + false /* isBlocked */, + mVcnContext, + underlyingNetworkTemplates, + SUB_GROUP, + mSubscriptionSnapshot, + currentlySelected, + null /* carrierConfig */); + } + + @Test + public void testSelectMorePreferredNetwork() { + final List<VcnUnderlyingNetworkTemplate> networkTemplates = new ArrayList(); + networkTemplates.add(CELL_TEMPLATE_DUN); + networkTemplates.add(CELL_TEMPLATE_CBS); + + UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates); + + // Bring up CBS network + final UnderlyingNetworkRecord cbsNetworkRecord = + bringupNetworkAndGetRecord( + cb, + CBS_NETWORK_CAPABILITIES, + networkTemplates, + null /* currentlySelected */); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(cbsNetworkRecord)); + + // Bring up DUN network + final UnderlyingNetworkRecord dunNetworkRecord = + bringupNetworkAndGetRecord( + cb, DUN_NETWORK_CAPABILITIES, networkTemplates, cbsNetworkRecord); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(dunNetworkRecord)); + } + + @Test + public void testNeverSelectLessPreferredNetwork() { + final List<VcnUnderlyingNetworkTemplate> networkTemplates = new ArrayList(); + networkTemplates.add(CELL_TEMPLATE_DUN); + networkTemplates.add(CELL_TEMPLATE_CBS); + + UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates); + + // Bring up DUN network + final UnderlyingNetworkRecord dunNetworkRecord = + bringupNetworkAndGetRecord( + cb, + DUN_NETWORK_CAPABILITIES, + networkTemplates, + null /* currentlySelected */); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(dunNetworkRecord)); + + // Bring up CBS network + final UnderlyingNetworkRecord cbsNetworkRecord = + bringupNetworkAndGetRecord( + cb, CBS_NETWORK_CAPABILITIES, networkTemplates, dunNetworkRecord); + verify(mNetworkControllerCb, never()) + .onSelectedUnderlyingNetworkChanged(eq(cbsNetworkRecord)); + } + + @Test + public void testFailtoMatchTemplateAndFallBackToInternetNetwork() { + final List<VcnUnderlyingNetworkTemplate> networkTemplates = new ArrayList(); + + networkTemplates.add( + new VcnCellUnderlyingNetworkTemplate.Builder().setDun(MATCH_REQUIRED).build()); + UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates); + + // Bring up an Internet network without DUN capability + final UnderlyingNetworkRecord networkRecord = + bringupNetworkAndGetRecord( + cb, + INITIAL_NETWORK_CAPABILITIES, + networkTemplates, + null /* currentlySelected */); + verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(networkRecord)); + } + + @Test + public void testFailtoMatchTemplateAndNeverFallBackToNonInternetNetwork() { + final List<VcnUnderlyingNetworkTemplate> networkTemplates = new ArrayList(); + + networkTemplates.add( + new VcnCellUnderlyingNetworkTemplate.Builder().setDun(MATCH_REQUIRED).build()); + UnderlyingNetworkListener cb = setupControllerAndGetNetworkListener(networkTemplates); + + bringupNetworkAndGetRecord( + cb, CBS_NETWORK_CAPABILITIES, networkTemplates, null /* currentlySelected */); + + verify(mNetworkControllerCb, never()) + .onSelectedUnderlyingNetworkChanged(any(UnderlyingNetworkRecord.class)); + } } |