diff options
Diffstat (limited to 'tests')
106 files changed, 3783 insertions, 2145 deletions
diff --git a/tests/AttestationVerificationTest/Android.bp b/tests/AttestationVerificationTest/Android.bp new file mode 100644 index 000000000000..a4741eedaac0 --- /dev/null +++ b/tests/AttestationVerificationTest/Android.bp @@ -0,0 +1,44 @@ +// 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 { + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "AttestationVerificationTest", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + defaults: ["cts_defaults"], + manifest: "AndroidManifest.xml", + test_config: "AndroidTest.xml", + platform_apis: true, + certificate: "platform", + optimize: { + enabled: false, + }, + test_suites: ["device-tests"], + libs: [ + "android.test.runner", + "android.test.base", + ], + static_libs: [ + "compatibility-device-util-axt", + "androidx.test.rules", + "androidx.test.ext.junit", + "platform-test-annotations", + ], +} diff --git a/tests/AttestationVerificationTest/AndroidManifest.xml b/tests/AttestationVerificationTest/AndroidManifest.xml new file mode 100755 index 000000000000..c42bde9dca3a --- /dev/null +++ b/tests/AttestationVerificationTest/AndroidManifest.xml @@ -0,0 +1,33 @@ +<?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.security.attestationverification"> + + <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" /> + <uses-permission android:name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE" /> + + <application> + <uses-library android:name="android.test.runner"/> + <activity android:name=".SystemAttestationVerificationTest$TestActivity" /> + </application> + + <!-- self-instrumenting test package. --> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="android.security.attestationverification"> + </instrumentation> +</manifest> diff --git a/tests/AttestationVerificationTest/AndroidTest.xml b/tests/AttestationVerificationTest/AndroidTest.xml new file mode 100644 index 000000000000..132576035952 --- /dev/null +++ b/tests/AttestationVerificationTest/AndroidTest.xml @@ -0,0 +1,27 @@ +<?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 description="Platform tests for Attestation Verification Framework"> + <option name="test-tag" value="AttestationVerificationTest" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="AttestationVerificationTest.apk" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="android.security.attestationverification" /> + <option name="hidden-api-checks" value="false" /> + </test> +</configuration> diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt new file mode 100644 index 000000000000..48bfd6f5d33c --- /dev/null +++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt @@ -0,0 +1,90 @@ +package android.security.attestationverification + +import android.os.Bundle +import android.app.Activity +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import org.junit.Assert.assertThrows +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import com.google.common.truth.Truth.assertThat +import android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED +import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY +import android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN +import java.lang.IllegalArgumentException +import java.time.Duration +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit + +/** Test for system-defined attestation verifiers. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class SystemAttestationVerificationTest { + + @get:Rule + val rule = ActivityScenarioRule(TestActivity::class.java) + + private lateinit var activity: Activity + private lateinit var avm: AttestationVerificationManager + + @Before + fun setup() { + rule.getScenario().onActivity { + avm = it.getSystemService(AttestationVerificationManager::class.java) + activity = it + } + } + + @Test + fun verifyAttestation_returnsUnknown() { + val future = CompletableFuture<Int>() + val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), + activity.mainExecutor) { result, _ -> + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_UNKNOWN) + } + + @Test + fun verifyToken_returnsUnknown() { + val future = CompletableFuture<Int>() + val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), + activity.mainExecutor) { _, token -> + val result = avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), token, null) + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_UNKNOWN) + } + + @Test + fun verifyToken_tooBigMaxAgeThrows() { + val future = CompletableFuture<VerificationToken>() + val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), + activity.mainExecutor) { _, token -> + future.complete(token) + } + + assertThrows(IllegalArgumentException::class.java) { + avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), future.getSoon(), + Duration.ofSeconds(3601)) + } + } + + private fun <T> CompletableFuture<T>.getSoon(): T { + return this.get(1, TimeUnit.SECONDS) + } + + class TestActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + } +} diff --git a/tests/BatteryStatsPerfTest/AndroidManifest.xml b/tests/BatteryStatsPerfTest/AndroidManifest.xml index 7633d5283f5e..ab5728e75b9f 100644 --- a/tests/BatteryStatsPerfTest/AndroidManifest.xml +++ b/tests/BatteryStatsPerfTest/AndroidManifest.xml @@ -20,6 +20,8 @@ <application> <uses-library android:name="android.test.runner" /> + <service android:name="com.android.internal.os.BatteryUsageStatsPerfTest$BatteryUsageStatsService" + android:process=":BatteryUsageStatsService" /> </application> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java index 54d70478f762..fe2fe0b40891 100644 --- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java +++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java @@ -18,13 +18,25 @@ package com.android.internal.os; import static com.google.common.truth.Truth.assertThat; +import android.app.Service; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.BatteryConsumer; import android.os.BatteryStatsManager; import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; +import android.os.Binder; +import android.os.ConditionVariable; +import android.os.IBinder; +import android.os.Parcel; import android.os.UidBatteryConsumer; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.test.InstrumentationRegistry; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; @@ -54,7 +66,8 @@ public class BatteryUsageStatsPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { - BatteryUsageStats batteryUsageStats = batteryStatsManager.getBatteryUsageStats(); + BatteryUsageStats batteryUsageStats = batteryStatsManager.getBatteryUsageStats( + new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(0).build()); state.pauseTiming(); @@ -71,4 +84,118 @@ public class BatteryUsageStatsPerfTest { state.resumeTiming(); } } + + private final ConditionVariable mServiceConnected = new ConditionVariable(); + private IBinder mService; + + private final ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName name, IBinder service) { + mService = service; + mServiceConnected.open(); + } + + public void onServiceDisconnected(ComponentName name) { + mService = null; + } + }; + + /** + * Measures the performance of transferring BatteryUsageStats over a Binder. + */ + @Test + public void testBatteryUsageStatsTransferOverBinder() throws Exception { + final Context context = InstrumentationRegistry.getContext(); + context.bindService( + new Intent(context, BatteryUsageStatsService.class), + mConnection, Context.BIND_AUTO_CREATE); + mServiceConnected.block(30000); + assertThat(mService).isNotNull(); + + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + final Parcel data = Parcel.obtain(); + final Parcel reply = Parcel.obtain(); + mService.transact(42, data, reply, 0); + final BatteryUsageStats batteryUsageStats = + BatteryUsageStats.CREATOR.createFromParcel(reply); + reply.recycle(); + data.recycle(); + + state.pauseTiming(); + + assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000); + assertThat(batteryUsageStats.getUidBatteryConsumers()).hasSize(1000); + final UidBatteryConsumer uidBatteryConsumer = + batteryUsageStats.getUidBatteryConsumers().get(0); + assertThat(uidBatteryConsumer.getConsumedPower(1)).isEqualTo(123); + + state.resumeTiming(); + } + + context.unbindService(mConnection); + } + + /* This service runs in a separate process */ + public static class BatteryUsageStatsService extends Service { + private final BatteryUsageStats mBatteryUsageStats; + + public BatteryUsageStatsService() { + mBatteryUsageStats = buildBatteryUsageStats(); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return new Binder() { + @Override + protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, + int flags) { + mBatteryUsageStats.writeToParcel(reply, 0); + return true; + } + }; + } + } + + private static BatteryUsageStats buildBatteryUsageStats() { + final BatteryUsageStats.Builder builder = + new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false) + .setBatteryCapacity(4000) + .setDischargePercentage(20) + .setDischargedPowerRange(1000, 2000) + .setStatsStartTimestamp(1000) + .setStatsEndTimestamp(3000); + + builder.getAggregateBatteryConsumerBuilder( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) + .setConsumedPower(123) + .setConsumedPower( + BatteryConsumer.POWER_COMPONENT_CPU, 10100) + .setConsumedPowerForCustomComponent( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200) + .setUsageDurationMillis( + BatteryConsumer.POWER_COMPONENT_CPU, 10300) + .setUsageDurationForCustomComponentMillis( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400); + + for (int i = 0; i < 1000; i++) { + final UidBatteryConsumer.Builder consumerBuilder = + builder.getOrCreateUidBatteryConsumerBuilder(i) + .setPackageWithHighestDrain("example.packagename" + i) + .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, i * 2000) + .setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, i * 1000); + for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT; + componentId++) { + consumerBuilder.setConsumedPower(componentId, componentId * 123.0, + BatteryConsumer.POWER_MODEL_POWER_PROFILE); + consumerBuilder.setUsageDurationMillis(componentId, componentId * 1000); + } + + consumerBuilder.setConsumedPowerForCustomComponent( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 1234) + .setUsageDurationForCustomComponentMillis( + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 4321); + } + return builder.build(); + } } diff --git a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/MediaDecoder.java b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/MediaDecoder.java index aa5739414df4..00fe6f2f866e 100644 --- a/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/MediaDecoder.java +++ b/tests/Camera2Tests/SmartCamera/SimpleCamera/src/androidx/media/filterfw/decoder/MediaDecoder.java @@ -24,10 +24,12 @@ import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.Build; import android.util.Log; + import androidx.media.filterfw.FrameImage2D; import androidx.media.filterfw.FrameValue; import androidx.media.filterfw.RenderTarget; +import java.io.IOException; import java.util.concurrent.LinkedBlockingQueue; @TargetApi(16) @@ -276,12 +278,13 @@ public class MediaDecoder implements } @TargetApi(17) - private void retrieveDefaultRotation() { + private void retrieveDefaultRotation() throws IOException { MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever(); metadataRetriever.setDataSource(mContext, mUri); String rotationString = metadataRetriever.extractMetadata( MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); mDefaultRotation = rotationString == null ? 0 : Integer.parseInt(rotationString); + metadataRetriever.release(); } private void onStop(boolean notifyListener) { diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml index 896ec9ae922c..566c725a3414 100644 --- a/tests/FlickerTests/AndroidTest.xml +++ b/tests/FlickerTests/AndroidTest.xml @@ -16,10 +16,6 @@ <!-- restart launcher to activate TAPL --> <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" /> </target_preparer> - <target_preparer class="com.android.tradefed.targetprep.DeviceCleaner"> - <!-- reboot the device to teardown any crashed tests --> - <option name="cleanup-action" value="REBOOT" /> - </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> <option name="test-file-name" value="FlickerTests.apk"/> 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 64cb790d324b..75f1337b9388 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -141,20 +141,36 @@ fun FlickerTestParameter.statusBarLayerRotatesScales() { * * @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 * (useful mostly for app launch) + * @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, - ignoreSnapshot: Boolean = false + ignoreEntriesWithRotationLayer: Boolean = false, + ignoreSnapshot: Boolean = false, + ignoreSplashscreen: Boolean = true ) { assertLayers { val assertion = this.isVisible(originalLayer) + + if (ignoreEntriesWithRotationLayer) { + assertion.then().isVisible(FlickerComponentName.ROTATION, isOptional = true) + } if (ignoreSnapshot) { - assertion.then() - .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + assertion.then().isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) } + if (ignoreSplashscreen) { + assertion.then().isSplashScreenVisibleFor(newLayer, isOptional = true) + } + assertion.then().isVisible(newLayer) } 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 9f26c31a6d63..7076a07c8ef6 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 @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.close +import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory @@ -24,6 +25,8 @@ 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.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -65,9 +68,9 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) { - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + override val transition: FlickerBuilder.() -> Unit get() = { - super.transition(this, it) + super.transition(this) transitions { device.pressBack() wmHelper.waitForHomeActivityVisible() @@ -79,6 +82,33 @@ class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarLayerRotatesScales() { + // This test doesn't work in shell transitions because of b/206753786 + assumeFalse(isShellTransitionsEnabled) + super.statusBarLayerRotatesScales() + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun launcherLayerReplacesApp() { + // This test doesn't work in shell transitions because of b/206086894 + assumeFalse(isShellTransitionsEnabled) + super.launcherLayerReplacesApp() + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun entireScreenCovered() { + // This test doesn't work in shell transitions because of b/206086894 + assumeFalse(isShellTransitionsEnabled) + super.entireScreenCovered() + } + companion object { /** * Creates the test configurations. 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 795766fccfbd..b5d01ef24f23 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,6 +16,7 @@ package com.android.server.wm.flicker.close +import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory @@ -23,6 +24,8 @@ 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.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -64,9 +67,9 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) { - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + override val transition: FlickerBuilder.() -> Unit get() = { - super.transition(this, it) + super.transition(this) transitions { device.pressHome() wmHelper.waitForHomeActivityVisible() @@ -78,6 +81,33 @@ class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarLayerRotatesScales() { + // This test doesn't work in shell transitions because of b/206753786 + assumeFalse(isShellTransitionsEnabled) + super.statusBarLayerRotatesScales() + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun launcherLayerReplacesApp() { + // This test doesn't work in shell transitions because of b/206086894 + assumeFalse(isShellTransitionsEnabled) + super.launcherLayerReplacesApp() + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun entireScreenCovered() { + // This test doesn't work in shell transitions because of b/206086894 + assumeFalse(isShellTransitionsEnabled) + super.entireScreenCovered() + } + companion object { /** * Creates the test configurations. 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 511fc26fdb38..ca73503164e6 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 @@ -18,6 +18,7 @@ package com.android.server.wm.flicker.close import android.app.Instrumentation import android.platform.test.annotations.Presubmit +import androidx.test.filters.FlakyTest import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerBuilderProvider import com.android.server.wm.flicker.FlickerTestParameter @@ -30,11 +31,12 @@ 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.startRotation 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 com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec +import org.junit.Rule import org.junit.Test /** @@ -44,14 +46,17 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation) + @get:Rule + val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec) + /** * Specification of the test transition to execute */ - protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = { + protected open val transition: FlickerBuilder.() -> Unit = { setup { eachRun { testApp.launchViaIntent(wmHelper) - this.setRotation(testSpec.config.startRotation) + this.setRotation(testSpec.startRotation) } } teardown { @@ -68,7 +73,7 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - transition(testSpec.config) + transition() } } @@ -189,4 +194,22 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) open fun launcherLayerReplacesApp() { testSpec.replacesLayer(testApp.component, LAUNCHER_COMPONENT) } + + @FlakyTest + @Test + fun runPresubmitAssertion() { + flickerRule.checkPresubmitAssertions() + } + + @FlakyTest + @Test + fun runPostsubmitAssertion() { + flickerRule.checkPostsubmitAssertions() + } + + @FlakyTest + @Test + fun runFlakyAssertion() { + flickerRule.checkFlakyAssertions() + } }
\ 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 index 5e21aff94769..f12e4aeea4be 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt @@ -30,14 +30,15 @@ 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.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.startRotation 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.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -63,7 +64,7 @@ import org.junit.runners.Parameterized @Group2 class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation) + private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { @@ -155,7 +156,11 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter @Presubmit @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + fun statusBarLayerRotatesScales() { + // This test doesn't work in shell transitions because of b/206753786 + assumeFalse(isShellTransitionsEnabled) + testSpec.statusBarLayerRotatesScales() + } @Presubmit @Test 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 index 0582685f2c54..0529fdd08fb3 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -20,7 +20,6 @@ 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 @@ -34,11 +33,12 @@ 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.startRotation +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.FlickerComponentName +import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -64,7 +64,7 @@ import org.junit.runners.Parameterized @Group2 class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation) + private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { @@ -104,7 +104,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete } } - @FlakyTest(bugId = 190189685) + @Presubmit @Test fun imeAppWindowBecomesInvisible() { testSpec.assertWm { @@ -116,7 +116,11 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @Presubmit @Test - fun entireScreenCovered() = testSpec.entireScreenCovered() + fun entireScreenCovered() { + // This test doesn't work in shell transitions because of b/206086894 + assumeFalse(isShellTransitionsEnabled) + testSpec.entireScreenCovered() + } @Presubmit @Test @@ -154,7 +158,11 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @Presubmit @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + fun statusBarLayerRotatesScales() { + // This test doesn't work in shell transitions because of b/206753786 + assumeFalse(isShellTransitionsEnabled) + testSpec.statusBarLayerRotatesScales() + } @Presubmit @Test 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 index 91b3d3dae3cd..d975cf40657b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt @@ -32,10 +32,12 @@ 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.helpers.isShellTransitionsEnabled 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 +import org.junit.Assume.assumeFalse +import org.junit.Assume.assumeTrue import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -119,20 +121,24 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun navBarLayerRotatesAndScales() { - Assume.assumeFalse(testSpec.isRotated) + assumeFalse(testSpec.isLandscapeOrSeascapeAtStart) testSpec.navBarLayerRotatesAndScales() } @FlakyTest @Test fun navBarLayerRotatesAndScales_Flaky() { - Assume.assumeTrue(testSpec.isRotated) + assumeTrue(testSpec.isLandscapeOrSeascapeAtStart) testSpec.navBarLayerRotatesAndScales() } @Presubmit @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + fun statusBarLayerRotatesScales() { + // This test doesn't work in shell transitions because of b/206753786 + assumeFalse(isShellTransitionsEnabled) + testSpec.statusBarLayerRotatesScales() + } @Presubmit @Test 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 index b589969dee14..facca9479da9 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt @@ -20,7 +20,6 @@ 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 @@ -34,9 +33,11 @@ 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.helpers.isShellTransitionsEnabled 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.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -104,7 +105,7 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @Test fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible() - @FlakyTest + @Presubmit @Test fun imeAppWindowBecomesInvisible() { testSpec.assertWm { @@ -124,7 +125,11 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun entireScreenCovered() = testSpec.entireScreenCovered() + fun entireScreenCovered() { + // This test doesn't work in shell transitions because of b/206086894 + assumeFalse(isShellTransitionsEnabled) + testSpec.entireScreenCovered() + } @Presubmit @Test @@ -146,7 +151,11 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + fun statusBarLayerRotatesScales() { + // This test doesn't work in shell transitions because of b/206753786 + assumeFalse(isShellTransitionsEnabled) + testSpec.statusBarLayerRotatesScales() + } @Presubmit @Test 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 index a9568b325af2..005c4f59de50 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt @@ -29,7 +29,6 @@ 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.setRotation -import com.android.server.wm.flicker.startRotation import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test @@ -71,14 +70,14 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation) + private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { setup { eachRun { - this.setRotation(testSpec.config.startRotation) + this.setRotation(testSpec.startRotation) } } teardown { 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 index 7bf0186cd857..3ef4e4c375ad 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt @@ -20,7 +20,6 @@ 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 @@ -34,9 +33,11 @@ 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.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible +import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -128,7 +129,11 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + fun statusBarLayerRotatesScales() { + // This test doesn't work in shell transitions because of b/206753786 + assumeFalse(isShellTransitionsEnabled) + testSpec.statusBarLayerRotatesScales() + } @Presubmit @Test @@ -138,7 +143,7 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) { } } - @FlakyTest + @Presubmit @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() { testSpec.assertWm { 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 index f6febe9e2234..84e78ec40b29 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt @@ -17,10 +17,10 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation -import android.os.SystemProperties 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 @@ -37,12 +37,13 @@ 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.startRotation +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.FlickerComponentName -import org.junit.Assume +import org.junit.Assume.assumeFalse +import org.junit.Assume.assumeTrue import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -60,9 +61,7 @@ import org.junit.runners.Parameterized @Group2 class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation) - private val isShellTransitionsEnabled = - SystemProperties.getBoolean("persist.debug.shell_transit", false) + private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { @@ -76,7 +75,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { device.pressRecentApps() wmHelper.waitImeGone() wmHelper.waitForAppTransitionIdle() - this.setRotation(testSpec.config.startRotation) + this.setRotation(testSpec.startRotation) } } transitions { @@ -102,6 +101,8 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun visibleWindowsShownMoreThanOneConsecutiveEntry() { + // This test doesn't work in shell transitions because of b/204570898 + assumeFalse(isShellTransitionsEnabled) val component = FlickerComponentName("", "RecentTaskScreenshotSurface") testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry( @@ -115,6 +116,8 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun launcherWindowBecomesInvisible() { + // This test doesn't work in shell transitions because of b/204574221 + assumeFalse(isShellTransitionsEnabled) testSpec.assertWm { this.isAppWindowVisible(LAUNCHER_COMPONENT) .then() @@ -124,12 +127,16 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled) + fun imeWindowIsAlwaysVisible() { + // This test doesn't work in shell transitions because of b/204570898 + assumeFalse(isShellTransitionsEnabled) + testSpec.imeWindowIsAlwaysVisible(!isShellTransitionsEnabled) + } @Presubmit @Test fun imeAppWindowVisibilityLegacy() { - Assume.assumeFalse(isShellTransitionsEnabled) + 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. @@ -144,10 +151,10 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { } } - @Presubmit + @FlakyTest(bugId = 204570898) @Test fun imeAppWindowVisibility() { - Assume.assumeTrue(isShellTransitionsEnabled) + 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 @@ -173,7 +180,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun imeLayerIsBecomesVisibleLegacy() { - Assume.assumeFalse(isShellTransitionsEnabled) + assumeFalse(isShellTransitionsEnabled) testSpec.assertLayers { this.isVisible(FlickerComponentName.IME) .then() @@ -183,10 +190,10 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { } } - @Presubmit + @FlakyTest(bugId = 204570898) @Test fun imeLayerIsBecomesVisible() { - Assume.assumeTrue(isShellTransitionsEnabled) + assumeTrue(isShellTransitionsEnabled) testSpec.assertLayers { this.isVisible(FlickerComponentName.IME) } @@ -195,6 +202,8 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test fun appLayerReplacesLauncher() { + // This test doesn't work in shell transitions because of b/204574221 + assumeFalse(isShellTransitionsEnabled) testSpec.assertLayers { this.isVisible(LAUNCHER_COMPONENT) .then() @@ -210,7 +219,11 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { @Presubmit @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + fun statusBarLayerRotatesScales() { + // This test doesn't work in shell transitions because of b/206753786 + assumeFalse(isShellTransitionsEnabled) + testSpec.statusBarLayerRotatesScales() + } @Presubmit @Test 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 index 4c506b0fea4d..a7a9fe289a4a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt @@ -33,7 +33,6 @@ import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarWindowIsVisible -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName @@ -56,14 +55,14 @@ import org.junit.runners.Parameterized class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val testApp = SimpleAppHelper(instrumentation) - private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation) + private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { setup { eachRun { - this.setRotation(testSpec.config.startRotation) + this.setRotation(testSpec.startRotation) testApp.launchViaIntent(wmHelper) wmHelper.waitForFullScreenApp(testApp.component) wmHelper.waitForAppTransitionIdle() @@ -86,7 +85,7 @@ class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParame transitions { // [Step1]: Swipe right from imeTestApp to testApp task createTag(TAG_IME_VISIBLE) - val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) device.swipe(0, displayBounds.bounds.height(), displayBounds.bounds.width(), displayBounds.bounds.height(), 50) @@ -96,7 +95,7 @@ class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParame } transitions { // [Step2]: Swipe left to back to imeTestApp task - val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) device.swipe(displayBounds.bounds.width(), displayBounds.bounds.height(), 0, displayBounds.bounds.height(), 50) wmHelper.waitForFullScreenApp(imeTestApp.component) @@ -109,8 +108,12 @@ class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestParame 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) } } 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 f74a7718461f..648353e34f92 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 @@ -26,7 +26,6 @@ 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.repetitions import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper @@ -60,7 +59,7 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group4 class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) { - val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation) /** @@ -70,8 +69,6 @@ class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) { @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } setup { eachRun { testApp.launchViaIntent(wmHelper) 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 be919cd67c1e..fe434268fbc9 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,7 +16,6 @@ package com.android.server.wm.flicker.launch -import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -25,9 +24,10 @@ 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.helpers.setRotation -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome +import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -60,13 +60,13 @@ class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp /** * Defines the transition used to run the test */ - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + override val transition: FlickerBuilder.() -> Unit get() = { - super.transition(this, it) + super.transition(this) setup { eachRun { removeAllTasksButHome() - this.setRotation(testSpec.config.startRotation) + this.setRotation(testSpec.startRotation) } } teardown { @@ -81,6 +81,15 @@ class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp } /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarLayerRotatesScales() { + // This test doesn't work in shell transitions because of b/206753786 + assumeFalse(isShellTransitionsEnabled) + super.statusBarLayerRotatesScales() + } + + /** {@inheritDoc} */ @FlakyTest @Test override fun navBarLayerRotatesAndScales() { @@ -88,12 +97,12 @@ class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp } /** {@inheritDoc} */ - @Postsubmit + @Presubmit @Test override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher() /** {@inheritDoc} */ - @Postsubmit + @Presubmit @Test override fun appWindowReplacesLauncherAsTopWindow() = super.appWindowReplacesLauncherAsTopWindow() 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 663af703f76d..53b53547707a 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 @@ -17,17 +17,20 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Presubmit +import android.view.Display 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.FlickerTestParameterFactory +import com.android.server.wm.flicker.LAUNCHER_COMPONENT import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.helpers.reopenAppFromOverview import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.traces.common.WindowManagerConditionsFactory +import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -62,9 +65,9 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) : OpenAppTransitio /** * Defines the transition used to run the test */ - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + override val transition: FlickerBuilder.() -> Unit get() = { - super.transition(this, it) + super.transition(this) setup { test { testApp.launchViaIntent(wmHelper) @@ -73,30 +76,76 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) : OpenAppTransitio device.pressHome() wmHelper.waitForAppTransitionIdle() device.pressRecentApps() - wmHelper.waitForAppTransitionIdle() - this.setRotation(testSpec.config.startRotation) + wmHelper.waitFor( + WindowManagerConditionsFactory + .isAppTransitionIdle(Display.DEFAULT_DISPLAY), + WindowManagerConditionsFactory.isActivityVisible(LAUNCHER_COMPONENT), + WindowManagerConditionsFactory.hasLayersAnimating().negate() + ) + this.setRotation(testSpec.startRotation) } } transitions { device.reopenAppFromOverview(wmHelper) wmHelper.waitFor( - WindowManagerConditionsFactory.hasLayersAnimating().negate(), - WindowManagerConditionsFactory.isWMStateComplete(), - WindowManagerConditionsFactory.isHomeActivityVisible().negate() + WindowManagerConditionsFactory.hasLayersAnimating().negate(), + WindowManagerConditionsFactory.isWMStateComplete(), + WindowManagerConditionsFactory.isLayerVisible(LAUNCHER_COMPONENT).negate(), + WindowManagerConditionsFactory.isActivityVisible(LAUNCHER_COMPONENT).negate() ) wmHelper.waitForFullScreenApp(testApp.component) } } /** {@inheritDoc} */ - @FlakyTest + @Presubmit @Test - override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() + override fun statusBarLayerRotatesScales() { + // This test doesn't work in shell transitions because of b/206753786 + assumeFalse(isShellTransitionsEnabled) + super.statusBarLayerRotatesScales() + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun entireScreenCovered() { + // This test doesn't work in shell transitions because of b/204570898 + assumeFalse(isShellTransitionsEnabled) + super.entireScreenCovered() + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun appWindowReplacesLauncherAsTopWindow() { + // This test doesn't work in shell transitions because of b/206085788 + assumeFalse(isShellTransitionsEnabled) + super.appWindowReplacesLauncherAsTopWindow() + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun appLayerReplacesLauncher() { + // This test doesn't work in shell transitions because of b/206085788 + assumeFalse(isShellTransitionsEnabled) + super.appLayerReplacesLauncher() + } /** {@inheritDoc} */ @Presubmit @Test - override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher() + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + // This test doesn't work in shell transitions because of b/206090480 + assumeFalse(isShellTransitionsEnabled) + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + + /** {@inheritDoc} */ + @FlakyTest + @Test + override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() /** {@inheritDoc} */ @Presubmit 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 08aaea70762f..3914ef6633b3 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 @@ -28,9 +28,11 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.traces.common.FlickerComponentName import com.google.common.truth.Truth +import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -66,9 +68,9 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti /** * Defines the transition used to run the test */ - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit - get() = { args -> - super.transition(this, args) + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) setup { eachRun { device.sleep() @@ -89,16 +91,14 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti } /** - * Checks that the nav bar layer starts visible, becomes invisible during unlocking animation - * and becomes visible at the end + * Checks that the nav bar layer starts invisible, becomes visible during unlocking animation + * and remains visible at the end */ @Postsubmit @Test fun navBarLayerVisibilityChanges() { testSpec.assertLayers { - this.isVisible(FlickerComponentName.NAV_BAR) - .then() - .isInvisible(FlickerComponentName.NAV_BAR) + this.isInvisible(FlickerComponentName.NAV_BAR) .then() .isVisible(FlickerComponentName.NAV_BAR) } @@ -108,7 +108,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti * Checks that the app layer doesn't exist at the start of the transition, that it is * created (invisible) and becomes visible during the transition */ - @FlakyTest + @Postsubmit @Test fun appLayerBecomesVisible() { testSpec.assertLayers { @@ -151,26 +151,19 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti } /** - * Checks that the nav bar starts the transition visible, then becomes invisible during - * then unlocking animation and becomes visible at the end of the transition + * 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 */ @Postsubmit @Test fun navBarWindowsVisibilityChanges() { testSpec.assertWm { - this.isAboveAppWindowVisible(FlickerComponentName.NAV_BAR) - .then() - .isNonAppWindowInvisible(FlickerComponentName.NAV_BAR) + this.isNonAppWindowInvisible(FlickerComponentName.NAV_BAR) .then() .isAboveAppWindowVisible(FlickerComponentName.NAV_BAR) } } - /** {@inheritDoc} */ - @FlakyTest - @Test - override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() - /** * Checks that the status bar layer is visible at the end of the trace * @@ -188,26 +181,26 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti /** {@inheritDoc} */ @FlakyTest(bugId = 202936526) @Test - override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + override fun statusBarLayerRotatesScales() { + // This test doesn't work in shell transitions because of b/206753786 + assumeFalse(isShellTransitionsEnabled) + super.statusBarLayerRotatesScales() + } /** {@inheritDoc} */ @Presubmit @Test fun statusBarLayerPositionAtEnd() { + // This test doesn't work in shell transitions because of b/206753786 + assumeFalse(isShellTransitionsEnabled) testSpec.assertLayersEnd { val display = this.entry.displays.minByOrNull { it.id } - ?: throw RuntimeException("There is no display!") + ?: error("There is no display!") this.visibleRegion(FlickerComponentName.STATUS_BAR) .coversExactly(WindowUtils.getStatusBarPosition(display)) } } - /** {@inheritDoc} */ - @FlakyTest - @Test - override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = - super.visibleWindowsShownMoreThanOneConsecutiveEntry() - @FlakyTest @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() @@ -219,7 +212,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti super.visibleLayersShownMoreThanOneConsecutiveEntry() /** {@inheritDoc} */ - @Postsubmit + @FlakyTest @Test override fun entireScreenCovered() = super.entireScreenCovered() @@ -234,11 +227,11 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti * Checks that the screen is locked at the start of the transition ([colorFadComponent]) * layer is visible */ - @Postsubmit + @Presubmit @Test fun screenLockedStart() { testSpec.assertLayersStart { - isVisible(colorFadComponent) + isEmpty() } } @@ -247,7 +240,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti * it cannot use the regular assertion (check over time), because on lock screen neither * the app not the launcher are visible, and there is no top visible window. */ - @Postsubmit + @Presubmit @Test override fun appWindowReplacesLauncherAsTopWindow() { testSpec.assertWm { 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 7af7b3ab6f24..b5c81bb39ed4 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 @@ -31,9 +31,7 @@ import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen 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.repetitions import com.android.server.wm.flicker.replacesLayer -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible @@ -50,13 +48,11 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { /** * Defines the transition used to run the test */ - protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } + protected open val transition: FlickerBuilder.() -> Unit = { setup { test { device.wakeUpAndGoToHomeScreen() - this.setRotation(testSpec.config.startRotation) + this.setRotation(testSpec.startRotation) } } teardown { @@ -73,7 +69,7 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - transition(testSpec.config) + transition() } } @@ -170,7 +166,8 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { * is replaced by [testApp], which remains visible until the end */ open fun appLayerReplacesLauncher() { - testSpec.replacesLayer(LAUNCHER_COMPONENT, testApp.component) + testSpec.replacesLayer(LAUNCHER_COMPONENT, testApp.component, + ignoreEntriesWithRotationLayer = true, ignoreSnapshot = true) } /** 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 5edee0cf0ca0..4db01bf53833 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 @@ -24,8 +24,9 @@ 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.helpers.setRotation -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled +import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -59,9 +60,9 @@ class OpenAppWarmTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp /** * Defines the transition used to run the test */ - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + override val transition: FlickerBuilder.() -> Unit get() = { - super.transition(this, it) + super.transition(this) setup { test { testApp.launchViaIntent(wmHelper) @@ -69,7 +70,7 @@ class OpenAppWarmTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp eachRun { device.pressHome() wmHelper.waitForHomeActivityVisible() - this.setRotation(testSpec.config.startRotation) + this.setRotation(testSpec.startRotation) } } teardown { @@ -84,6 +85,33 @@ class OpenAppWarmTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp } /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarLayerRotatesScales() { + // This test doesn't work in shell transitions because of b/206753786 + assumeFalse(isShellTransitionsEnabled) + super.statusBarLayerRotatesScales() + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun appWindowReplacesLauncherAsTopWindow() { + // This test doesn't work in shell transitions because of b/206094140 + assumeFalse(isShellTransitionsEnabled) + super.appWindowReplacesLauncherAsTopWindow() + } + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + // This test doesn't work in shell transitions because of b/206094140 + assumeFalse(isShellTransitionsEnabled) + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + + /** {@inheritDoc} */ @FlakyTest @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() 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 495e2d62a11d..769cb1ad959b 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 @@ -33,8 +33,6 @@ 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.repetitions -import com.android.server.wm.flicker.startRotation 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 @@ -71,8 +69,6 @@ class TaskTransitionTest(val testSpec: FlickerTestParameter) { @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } setup { eachRun { mTestApp.launchViaIntent(wmHelper) @@ -149,7 +145,7 @@ class TaskTransitionTest(val testSpec: FlickerTestParameter) { @Test fun colorLayerIsVisibleDuringTransition() { val bgColorLayer = FlickerComponentName("", "colorBackgroundLayer") - val displayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) testSpec.assertLayers { this.coversExactly(displayBounds, LAUNCH_NEW_TASK_ACTIVITY) 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 52904cce8772..0a64939cb4ca 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 @@ -18,6 +18,7 @@ package com.android.server.wm.flicker.quickswitch 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 @@ -32,11 +33,9 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder 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.isRotated 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.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName @@ -68,7 +67,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet private val testApp1 = SimpleAppHelper(instrumentation) private val testApp2 = NonResizeableAppHelper(instrumentation) - private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { @@ -92,7 +91,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet startDisplayBounds.bounds.bottom, 2 * startDisplayBounds.bounds.right / 3, startDisplayBounds.bounds.bottom, - if (testSpec.config.startRotation.isRotated()) 75 else 30 + if (testSpec.isLandscapeOrSeascapeAtStart) 75 else 30 ) wmHelper.waitForFullScreenApp(testApp1.component) @@ -135,7 +134,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet /** * Checks that the transition starts with [testApp2] being the top window. */ - @Postsubmit + @Presubmit @Test fun startsWithApp2WindowBeingOnTop() { testSpec.assertWmStart { @@ -286,7 +285,7 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet /** * Checks that the navbar layer is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible() @@ -295,21 +294,21 @@ class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParamet * * NOTE: This doesn't check that the navbar is visible or not. */ - @Postsubmit + @Presubmit @Test fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales() /** * Checks that the status bar window is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible() /** * Checks that the status bar layer is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible() 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 842aa2b548db..ded80a0b2509 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 @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.quickswitch 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 @@ -32,12 +32,9 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder 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.isRotated 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.repetitions -import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName @@ -69,13 +66,11 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara private val testApp1 = SimpleAppHelper(instrumentation) private val testApp2 = NonResizeableAppHelper(instrumentation) - private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - withTestName { testSpec.name } - repeat { testSpec.config.repetitions } setup { eachRun { testApp1.launchViaIntent(wmHelper) @@ -93,7 +88,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara startDisplayBounds.bounds.bottom, 2 * startDisplayBounds.bounds.right / 3, startDisplayBounds.bounds.bottom, - if (testSpec.config.startRotation.isRotated()) 75 else 30 + if (testSpec.isLandscapeOrSeascapeAtStart) 75 else 30 ) wmHelper.waitForFullScreenApp(testApp1.component) @@ -110,7 +105,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara startDisplayBounds.bounds.bottom, startDisplayBounds.bounds.right / 3, startDisplayBounds.bounds.bottom, - if (testSpec.config.startRotation.isRotated()) 75 else 30 + if (testSpec.isLandscapeOrSeascapeAtStart) 75 else 30 ) wmHelper.waitForFullScreenApp(testApp2.component) @@ -130,7 +125,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Checks that the transition starts with [testApp1]'s windows filling/covering exactly the * entirety of the display. */ - @Postsubmit + @Presubmit @Test fun startsWithApp1WindowsCoverFullScreen() { testSpec.assertWmStart { @@ -142,7 +137,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Checks that the transition starts with [testApp1]'s layers filling/covering exactly the * entirety of the display. */ - @Postsubmit + @Presubmit @Test fun startsWithApp1LayersCoverFullScreen() { testSpec.assertLayersStart { @@ -153,7 +148,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara /** * Checks that the transition starts with [testApp1] being the top window. */ - @Postsubmit + @Presubmit @Test fun startsWithApp1WindowBeingOnTop() { testSpec.assertWmStart { @@ -165,7 +160,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Checks that [testApp2] windows fill the entire screen (i.e. is "fullscreen") at the end of the * transition once we have fully quick switched from [testApp1] back to the [testApp2]. */ - @Postsubmit + @Presubmit @Test fun endsWithApp2WindowsCoveringFullScreen() { testSpec.assertWmEnd { @@ -177,7 +172,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Checks that [testApp2] layers fill the entire screen (i.e. is "fullscreen") at the end of the * transition once we have fully quick switched from [testApp1] back to the [testApp2]. */ - @Postsubmit + @Presubmit @Test fun endsWithApp2LayersCoveringFullScreen() { testSpec.assertLayersEnd { @@ -189,7 +184,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Checks that [testApp2] is the top window at the end of the transition once we have fully quick * switched from [testApp1] back to the [testApp2]. */ - @Postsubmit + @Presubmit @Test fun endsWithApp2BeingOnTop() { testSpec.assertWmEnd { @@ -201,7 +196,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Checks that [testApp2]'s window starts off invisible and becomes visible at some point before * the end of the transition and then stays visible until the end of the transition. */ - @Postsubmit + @Presubmit @Test fun app2WindowBecomesAndStaysVisible() { testSpec.assertWm { @@ -217,7 +212,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Checks that [testApp2]'s layer starts off invisible and becomes visible at some point before * the end of the transition and then stays visible until the end of the transition. */ - @Postsubmit + @Presubmit @Test fun app2LayerBecomesAndStaysVisible() { testSpec.assertLayers { @@ -231,7 +226,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Checks that [testApp1]'s 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. */ - @Postsubmit + @Presubmit @Test fun app1WindowBecomesAndStaysInvisible() { testSpec.assertWm { @@ -245,7 +240,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Checks that [testApp1]'s 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. */ - @Postsubmit + @Presubmit @Test fun app1LayerBecomesAndStaysInvisible() { testSpec.assertLayers { @@ -260,7 +255,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Ensures that at any point, either [testApp2] or [testApp1]'s windows are at least partially * visible. */ - @Postsubmit + @Presubmit @Test fun app2WindowIsVisibleOnceApp1WindowIsInvisible() { testSpec.assertWm { @@ -279,7 +274,7 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * Ensures that at any point, either [testApp2] or [testApp1]'s windows are at least partially * visible. */ - @Postsubmit + @Presubmit @Test fun app2LayerIsVisibleOnceApp1LayerIsInvisible() { testSpec.assertLayers { @@ -296,14 +291,14 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara /** * Checks that the navbar window is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsVisible() /** * Checks that the navbar layer is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible() @@ -312,21 +307,21 @@ class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestPara * * NOTE: This doesn't check that the navbar is visible or not. */ - @Postsubmit + @Presubmit @Test fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales() /** * Checks that the status bar window is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible() /** * Checks that the status bar layer is visible throughout the entire transition. */ - @Postsubmit + @Presubmit @Test fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible() 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 10ca0d9b323b..dcb5c86f32ad 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 @@ -35,7 +35,6 @@ 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.startRotation import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarWindowIsVisible import com.android.server.wm.traces.common.FlickerComponentName @@ -64,7 +63,7 @@ import org.junit.runners.Parameterized class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val testApp = SimpleAppHelper(instrumentation) - private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.config.startRotation) + private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { 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 fd8abc621b33..c18798f0a4b0 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,7 +16,6 @@ package com.android.server.wm.flicker.rotation -import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -26,11 +25,13 @@ 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.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.rules.WMFlickerServiceRuleForTestSpec 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.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Rule import org.junit.Test @@ -85,9 +86,9 @@ class ChangeAppRotationTest( val flickerRule = WMFlickerServiceRuleForTestSpec(testSpec) override val testApp = SimpleAppHelper(instrumentation) - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + override val transition: FlickerBuilder.() -> Unit get() = { - super.transition(this, it) + super.transition(this) setup { test { testApp.launchViaIntent(wmHelper) @@ -95,13 +96,13 @@ class ChangeAppRotationTest( } } - @Postsubmit + @FlakyTest @Test fun runPresubmitAssertion() { flickerRule.checkPresubmitAssertions() } - @Postsubmit + @FlakyTest @Test fun runPostsubmitAssertion() { flickerRule.checkPostsubmitAssertions() @@ -113,11 +114,16 @@ class ChangeAppRotationTest( flickerRule.checkFlakyAssertions() } - /** {@inheritDoc} */ + /** + * Windows maybe recreated when rotated. Checks that the focus does not change or if it does, + * focus returns to [testApp] + */ @FlakyTest(bugId = 190185577) @Test - override fun focusDoesNotChange() { - super.focusDoesNotChange() + fun focusChanges() { + testSpec.assertEventLog { + this.focusChanges(testApp.`package`) + } } /** @@ -161,7 +167,11 @@ class ChangeAppRotationTest( */ @Presubmit @Test - fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + fun statusBarLayerRotatesScales() { + // This test doesn't work in shell transitions because of b/206753786 + assumeFalse(isShellTransitionsEnabled) + testSpec.statusBarLayerRotatesScales() + } /** {@inheritDoc} */ @FlakyTest 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 e850632ed8af..d1bdeed81b78 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 @@ -22,14 +22,12 @@ 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.endRotation 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.startRotation import com.android.server.wm.traces.common.FlickerComponentName import org.junit.Test @@ -41,10 +39,10 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - protected open val transition: FlickerBuilder.(Map<String, Any?>) -> Unit = { + protected open val transition: FlickerBuilder.() -> Unit = { setup { eachRun { - this.setRotation(testSpec.config.startRotation) + this.setRotation(testSpec.startRotation) } } teardown { @@ -53,7 +51,7 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) } } transitions { - this.setRotation(testSpec.config.endRotation) + this.setRotation(testSpec.endRotation) } } @@ -64,7 +62,7 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { - transition(testSpec.config) + transition() } } @@ -131,17 +129,6 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter) open fun entireScreenCovered() = testSpec.entireScreenCovered() /** - * Checks that the focus doesn't change during animation - */ - @Presubmit - @Test - open fun focusDoesNotChange() { - testSpec.assertEventLog { - this.focusDoesNotChange() - } - } - - /** * Checks that [testApp] layer covers the entire screen at the start of the transition */ @Presubmit 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 310f04b9710f..e44bee644ceb 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 @@ -26,8 +26,10 @@ 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.SeamlessRotationAppHelper +import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.Assume.assumeFalse import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -81,14 +83,14 @@ class SeamlessAppRotationTest( ) : RotationTransition(testSpec) { override val testApp = SeamlessRotationAppHelper(instrumentation) - override val transition: FlickerBuilder.(Map<String, Any?>) -> Unit + override val transition: FlickerBuilder.() -> Unit get() = { - super.transition(this, it) + super.transition(this) setup { test { testApp.launchViaIntent(wmHelper, - stringExtras = mapOf( - ActivityOptions.EXTRA_STARVE_UI_THREAD to it.starveUiThread.toString()) + stringExtras = mapOf(ActivityOptions.EXTRA_STARVE_UI_THREAD + to testSpec.starveUiThread.toString()) ) } } @@ -100,6 +102,8 @@ class SeamlessAppRotationTest( @Presubmit @Test fun appWindowFullScreen() { + // This test doesn't work in shell transitions because of b/206101151 + assumeFalse(isShellTransitionsEnabled) testSpec.assertWm { this.invoke("isFullScreen") { val appWindow = it.windowState(testApp.`package`) @@ -135,6 +139,8 @@ class SeamlessAppRotationTest( @Presubmit @Test fun appLayerAlwaysVisible() { + // This test doesn't work in shell transitions because of b/206101151 + assumeFalse(isShellTransitionsEnabled) testSpec.assertLayers { isVisible(testApp.component) } @@ -146,6 +152,8 @@ class SeamlessAppRotationTest( @Presubmit @Test fun appLayerRotates() { + // This test doesn't work in shell transitions because of b/206101151 + assumeFalse(isShellTransitionsEnabled) testSpec.assertLayers { this.invoke("entireScreenCovered") { entry -> entry.entry.displays.map { display -> @@ -179,21 +187,36 @@ class SeamlessAppRotationTest( } } + /** + * Checks that the focus doesn't change during animation + */ + @Presubmit + @Test + fun focusDoesNotChange() { + // This test doesn't work in shell transitions because of b/206101151 + assumeFalse(isShellTransitionsEnabled) + testSpec.assertEventLog { + this.focusDoesNotChange() + } + } + /** {@inheritDoc} */ @FlakyTest @Test override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales() companion object { - private val Map<String, Any?>.starveUiThread - get() = this.getOrDefault(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) as Boolean + private val FlickerTestParameter.starveUiThread + get() = config.getOrDefault(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) as Boolean - private fun FlickerTestParameter.createConfig( + private fun createConfig( + sourceConfig: FlickerTestParameter, starveUiThread: Boolean - ): MutableMap<String, Any?> { - val config = this.config.toMutableMap() - config[ActivityOptions.EXTRA_STARVE_UI_THREAD] = starveUiThread - return config + ): FlickerTestParameter { + val newConfig = sourceConfig.config.toMutableMap() + .also { it[ActivityOptions.EXTRA_STARVE_UI_THREAD] = starveUiThread } + val nameExt = if (starveUiThread) "_BUSY_UI_THREAD" else "" + return FlickerTestParameter(newConfig, nameOverride = "$sourceConfig$nameExt") } /** @@ -206,15 +229,10 @@ class SeamlessAppRotationTest( private fun getConfigurations(): List<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigRotationTests(repetitions = 2) - .flatMap { - val defaultRun = it.createConfig(starveUiThread = false) - val busyUiRun = it.createConfig(starveUiThread = true) - listOf( - FlickerTestParameter(defaultRun), - FlickerTestParameter(busyUiRun, - name = "${FlickerTestParameter.defaultName(busyUiRun)}_BUSY_UI_THREAD" - ) - ) + .flatMap { sourceConfig -> + val defaultRun = createConfig(sourceConfig, starveUiThread = false) + val busyUiRun = createConfig(sourceConfig, starveUiThread = true) + listOf(defaultRun, busyUiRun) } } diff --git a/tests/HwAccelerationTest/Android.bp b/tests/HwAccelerationTest/Android.bp index 76063227eac1..e618ed1e3ab9 100644 --- a/tests/HwAccelerationTest/Android.bp +++ b/tests/HwAccelerationTest/Android.bp @@ -25,7 +25,13 @@ package { android_test { name: "HwAccelerationTest", - srcs: ["**/*.java"], + jni_libs: [ + "libhwaccelerationtest_jni", + ], + srcs: [ + "**/*.java", + "**/*.kt", + ], platform_apis: true, certificate: "platform", } diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 04a55d6038b0..22fe4242fbbb 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -436,6 +436,15 @@ </intent-filter> </activity> + <activity android:name=".PenStylusActivity" + android:label="Pen/Draw" + 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="GLTextureViewActivity" android:label="TextureView/OpenGL" android:exported="true"> diff --git a/tests/HwAccelerationTest/jni/Android.bp b/tests/HwAccelerationTest/jni/Android.bp new file mode 100644 index 000000000000..8edddab0ad1f --- /dev/null +++ b/tests/HwAccelerationTest/jni/Android.bp @@ -0,0 +1,44 @@ +// 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_test_library { + + name: "libhwaccelerationtest_jni", + + cflags: [ + "-Werror", + "-Wno-error=deprecated-declarations", + ], + + gtest: false, + + srcs: [ + "native-lib.cpp", + ], + + shared_libs: [ + "libnativehelper", + "libandroid", + "liblog", + ], + + stl: "c++_static", + + sdk_version: "current", + +} diff --git a/tests/HwAccelerationTest/jni/native-lib.cpp b/tests/HwAccelerationTest/jni/native-lib.cpp new file mode 100644 index 000000000000..407d4bf76336 --- /dev/null +++ b/tests/HwAccelerationTest/jni/native-lib.cpp @@ -0,0 +1,62 @@ +/* + * 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. + */ + +#include <android/hardware_buffer.h> +#include <android/hardware_buffer_jni.h> +#include <android/native_window.h> +#include <android/native_window_jni.h> +#include <android/surface_control.h> +#include <jni.h> + +struct MyWrapper { + MyWrapper(ANativeWindow* parent) { + surfaceControl = ASurfaceControl_createFromWindow(parent, "PenLayer"); + } + + ~MyWrapper() { ASurfaceControl_release(surfaceControl); } + + void setBuffer(AHardwareBuffer* buffer) { + ASurfaceTransaction* transaction = ASurfaceTransaction_create(); + ASurfaceTransaction_setBuffer(transaction, surfaceControl, buffer); + ASurfaceTransaction_setVisibility(transaction, surfaceControl, + ASURFACE_TRANSACTION_VISIBILITY_SHOW); + ASurfaceTransaction_apply(transaction); + ASurfaceTransaction_delete(transaction); + } + + ASurfaceControl* surfaceControl = nullptr; +}; + +extern "C" JNIEXPORT jlong JNICALL +Java_com_android_test_hwui_FrontBufferedLayer_nCreate(JNIEnv* env, jclass, jobject jSurface) { + ANativeWindow* window = ANativeWindow_fromSurface(env, jSurface); + MyWrapper* wrapper = new MyWrapper(window); + ANativeWindow_release(window); + return reinterpret_cast<jlong>(wrapper); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_android_test_hwui_FrontBufferedLayer_nDestroy(JNIEnv*, jclass, jlong ptr) { + MyWrapper* wrapper = reinterpret_cast<MyWrapper*>(ptr); + delete wrapper; +} + +extern "C" JNIEXPORT void JNICALL Java_com_android_test_hwui_FrontBufferedLayer_nUpdateBuffer( + JNIEnv* env, jclass, jlong ptr, jobject jbuffer) { + MyWrapper* wrapper = reinterpret_cast<MyWrapper*>(ptr); + AHardwareBuffer* buffer = AHardwareBuffer_fromHardwareBuffer(env, jbuffer); + wrapper->setBuffer(buffer); +}
\ No newline at end of file diff --git a/tests/HwAccelerationTest/res/layout/pen_stylus.xml b/tests/HwAccelerationTest/res/layout/pen_stylus.xml new file mode 100644 index 000000000000..37aafed208fb --- /dev/null +++ b/tests/HwAccelerationTest/res/layout/pen_stylus.xml @@ -0,0 +1,20 @@ +<?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. +--> +<com.android.test.hwui.FrontBufferedLayer + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" +/>
\ No newline at end of file diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java index c06f8fd44c03..46d3a3d669c0 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java @@ -63,7 +63,7 @@ public class ColorFiltersMutateActivity extends Activity { "uniform shader bitmapShader;\n" + "uniform float param1;\n" + "half4 main(float2 xy) {\n" - + " return half4(sample(bitmapShader, xy).rgb, param1);\n" + + " return half4(bitmapShader.eval(xy).rgb, param1);\n" + "}\n"; BitmapsView(Context c) { diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColoredRectsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColoredRectsActivity.java index 7ea2a62d7494..d4bc2a6d3317 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColoredRectsActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColoredRectsActivity.java @@ -42,7 +42,7 @@ public class ColoredRectsActivity extends Activity { swView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); frame.addView(swView); final RectsView hwBothView = new RectsView(this, 850, Color.GREEN); - // Don't actually need to render to a hw layer, but it's a good sanity-check that + // Don't actually need to render to a hw layer, but it's a good check that // we're rendering to/from layers correctly hwBothView.setLayerType(View.LAYER_TYPE_HARDWARE, null); frame.addView(hwBothView); diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/FrontBufferedLayer.kt b/tests/HwAccelerationTest/src/com/android/test/hwui/FrontBufferedLayer.kt new file mode 100644 index 000000000000..ebec22e29d69 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/FrontBufferedLayer.kt @@ -0,0 +1,132 @@ +/* + * 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.test.hwui + +import android.content.Context +import android.graphics.BlendMode +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Rect +import android.hardware.HardwareBuffer +import android.util.AttributeSet +import android.view.InputDevice +import android.view.MotionEvent +import android.view.Surface +import android.view.SurfaceHolder +import android.view.SurfaceView +import android.view.WindowInsets +import android.view.WindowInsetsController + +class FrontBufferedLayer : SurfaceView, SurfaceHolder.Callback { + var mRenderer: PenStylusActivity.SingleBufferedCanvas? = null + + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + init { + holder.addCallback(this) + setZOrderOnTop(true) + } + + override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { + nDestroy(mNativePtr) + mNativePtr = nCreate(holder.surface) + mRenderer = PenStylusActivity.SingleBufferedCanvas(width, height) + clearOverlay() + + if ((false)) { + val canvas = holder.lockCanvas() + canvas.drawColor(Color.LTGRAY) + holder.unlockCanvasAndPost(canvas) + } + } + + override fun surfaceDestroyed(holder: SurfaceHolder) { + mRenderer = null + nDestroy(mNativePtr) + mNativePtr = 0 + } + + override fun surfaceCreated(holder: SurfaceHolder) { + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + requestUnbufferedDispatch(InputDevice.SOURCE_CLASS_POINTER) + this.windowInsetsController?.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_DEFAULT) + this.windowInsetsController?.hide(WindowInsets.Type.navigationBars()) + this.windowInsetsController?.hide(WindowInsets.Type.statusBars()) + } + + private fun clearOverlay() { + mRenderer?.let { + it.update(null) { + drawColor(Color.WHITE, BlendMode.SRC) + } + nUpdateBuffer(mNativePtr, it.mHardwareBuffer) + } + } + + private var prevX: Float = 0f + private var prevY: Float = 0f + private val paint = Paint().also { + it.color = Color.BLACK + it.strokeWidth = 10f + it.isAntiAlias = true + } + + override fun onTouchEvent(event: MotionEvent): Boolean { + if (!event.isFromSource(InputDevice.SOURCE_STYLUS)) { + if (event.action == MotionEvent.ACTION_DOWN) { + clearOverlay() + } + return true + } + val action = event.actionMasked + if (action == MotionEvent.ACTION_DOWN || + action == MotionEvent.ACTION_MOVE) { + mRenderer?.let { + val left = minOf(prevX, event.x).toInt() - 10 + val top = minOf(prevY, event.y).toInt() - 10 + val right = maxOf(prevX, event.x).toInt() + 10 + val bottom = maxOf(prevY, event.y).toInt() + 10 + it.update(Rect(left, top, right, bottom)) { + if (action == MotionEvent.ACTION_MOVE) { + drawLine(prevX, prevY, event.x, event.y, paint) + } + drawCircle(event.x, event.y, 5f, paint) + } + nUpdateBuffer(mNativePtr, it.mHardwareBuffer) + } + prevX = event.x + prevY = event.y + } + return true + } + + private var mNativePtr: Long = 0 + + private external fun nCreate(surface: Surface): Long + private external fun nDestroy(ptr: Long) + private external fun nUpdateBuffer(ptr: Long, buffer: HardwareBuffer) + + companion object { + init { + System.loadLibrary("hwaccelerationtest_jni") + } + } +}
\ No newline at end of file diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java index 7173a85f73e7..584ab596836c 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java @@ -42,7 +42,7 @@ public class Lines2Activity extends Activity { swView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); frame.addView(swView); final LinesView hwBothView = new LinesView(this, 850, Color.GREEN); - // Don't actually need to render to a hw layer, but it's a good sanity-check that + // Don't actually need to render to a hw layer, but it's a good check that // we're rendering to/from layers correctly hwBothView.setLayerType(View.LAYER_TYPE_HARDWARE, null); frame.addView(hwBothView); diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PenStylusActivity.kt b/tests/HwAccelerationTest/src/com/android/test/hwui/PenStylusActivity.kt new file mode 100644 index 000000000000..1445b1db801e --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PenStylusActivity.kt @@ -0,0 +1,85 @@ +/* + * 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.test.hwui + +import android.app.Activity +import android.os.Bundle +import android.hardware.HardwareBuffer +import android.graphics.Canvas +import android.graphics.PixelFormat +import android.graphics.Rect +import android.media.ImageReader +import android.view.Surface +import java.lang.IllegalArgumentException + +const val USAGE_HWC = 0x800L + +class PenStylusActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(FrontBufferedLayer(this)) + } + + class SingleBufferedCanvas : AutoCloseable { + val mHardwareBuffer: HardwareBuffer + private var mCanvas: Canvas? + private val mImageReader: ImageReader + private val mSurface: Surface + + constructor(width: Int, height: Int) { + mImageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1, + HardwareBuffer.USAGE_CPU_READ_RARELY or HardwareBuffer.USAGE_CPU_WRITE_RARELY or + HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or USAGE_HWC) + + mSurface = mImageReader.surface + mSurface.unlockCanvasAndPost(mSurface.lockCanvas(null)) + val image = mImageReader.acquireNextImage() + mHardwareBuffer = image.hardwareBuffer!! + image.close() + mCanvas = mSurface.lockCanvas(null) + } + + fun lockCanvas(rect: Rect?): Canvas { + if (mCanvas != null) { + unlockCanvas(mCanvas!!) + } + mCanvas = mSurface.lockCanvas(rect) + return mCanvas!! + } + + fun unlockCanvas(canvas: Canvas) { + if (this.mCanvas !== canvas) throw IllegalArgumentException() + mSurface.unlockCanvasAndPost(canvas) + this.mCanvas = null + mImageReader.acquireNextImage().close() + } + + inline fun update(area: Rect?, func: Canvas.() -> Unit) { + val canvas = lockCanvas(area) + func(canvas) + unlockCanvas(canvas) + } + + override fun close() { + mHardwareBuffer.close() + mSurface.unlockCanvasAndPost(mCanvas) + mImageReader.close() + } + } +}
\ No newline at end of file diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java index d925541c76d6..83e2de925ceb 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/RippleActivity.java @@ -91,7 +91,7 @@ public class RippleActivity extends Activity { + " d = rand(float2(x, y)) > density ? d : d * .2;\n" + " d = d * rand(float2(fraction, x * y));\n" + " float alpha = 1. - pow(fraction, 3.);\n" - + " return float4(sample(in_paintColor, p).rgb, d * alpha);\n" + + " return float4(in_paintColor.eval(p).rgb, d * alpha);\n" + "}"; RippleView(Context c) { diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java index 3307c36d9d1a..fcdee63a99a8 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/StretchShaderActivity.java @@ -532,6 +532,6 @@ public class StretchShaderActivity extends Activity { + " uv.y = outV;\n" + " coord.x = uv.x * viewportWidth;\n" + " coord.y = uv.y * viewportHeight;\n" - + " return sample(uContentTexture, coord);\n" + + " return uContentTexture.eval(coord);\n" + "}"; } diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp index eacf5b287a2e..de9bbb6ef9fa 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -18,6 +18,8 @@ android_test { static_libs: [ "androidx.test.ext.junit", "androidx.test.rules", + "services.core.unboosted", + "testables", "truth-prebuilt", "ub-uiautomator", ], diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt index 4da3eca25ea0..1d65cc35c3bc 100644 --- a/tests/Input/src/com/android/test/input/AnrTest.kt +++ b/tests/Input/src/com/android/test/input/AnrTest.kt @@ -19,7 +19,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import androidx.test.filters.MediumTest +import android.app.ActivityManager +import android.app.ApplicationExitInfo import android.graphics.Rect +import android.os.Build +import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS import android.os.SystemClock import android.provider.Settings import android.provider.Settings.Global.HIDE_ERROR_DIALOGS @@ -27,10 +31,13 @@ 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 org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.Before import org.junit.Test @@ -51,28 +58,99 @@ import org.junit.runner.RunWith class AnrTest { companion object { private const val TAG = "AnrTest" + private const val ALL_PIDS = 0 + private const val NO_MAX = 0 } - val mInstrumentation = InstrumentationRegistry.getInstrumentation() - var mHideErrorDialogs = 0 + private val instrumentation = InstrumentationRegistry.getInstrumentation() + private var hideErrorDialogs = 0 + private lateinit var PACKAGE_NAME: String + private val DISPATCHING_TIMEOUT = (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS * + Build.HW_TIMEOUT_MULTIPLIER) @Before fun setUp() { - val contentResolver = mInstrumentation.targetContext.contentResolver - mHideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0) + 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() } @After fun tearDown() { - val contentResolver = mInstrumentation.targetContext.contentResolver - Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, mHideErrorDialogs) + val contentResolver = instrumentation.targetContext.contentResolver + Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, hideErrorDialogs) } @Test - fun testGestureMonitorAnr() { + fun testGestureMonitorAnr_Close() { + triggerAnr() + clickCloseAppOnAnrDialog() + } + + @Test + fun testGestureMonitorAnr_Wait() { + triggerAnr() + clickWaitOnAnrDialog() + SystemClock.sleep(500) // Wait at least 500ms after tapping on wait + // ANR dialog should reappear after a delay - find the close button on it to verify + clickCloseAppOnAnrDialog() + } + + private fun clickCloseAppOnAnrDialog() { + // Find anr dialog and kill app + val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) + val closeAppButton: UiObject2? = + uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000) + if (closeAppButton == null) { + fail("Could not find anr dialog") + return + } + val initialReasons = getExitReasons() + closeAppButton.click() + /** + * We must wait for the app to be fully closed before exiting this test. This is because + * another test may again invoke 'am start' for the same activity. + * If the 1st process that got ANRd isn't killed by the time second 'am start' runs, + * the killing logic will apply to the newly launched 'am start' instance, and the second + * test will fail because the unresponsive activity will never be launched. + */ + waitForNewExitReason(initialReasons[0].timestamp) + } + + private fun clickWaitOnAnrDialog() { + // Find anr dialog and tap on wait + val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) + val waitButton: UiObject2? = + uiDevice.wait(Until.findObject(By.res("android:id/aerr_wait")), 20000) + if (waitButton == null) { + fail("Could not find anr dialog/wait button") + return + } + waitButton.click() + } + + private fun getExitReasons(): List<ApplicationExitInfo> { + lateinit var infos: List<ApplicationExitInfo> + instrumentation.runOnMainSync { + val am = instrumentation.getContext().getSystemService(ActivityManager::class.java) + infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, ALL_PIDS, NO_MAX) + } + return infos + } + + private fun waitForNewExitReason(previousExitTimestamp: Long) { + PollingCheck.waitFor { + getExitReasons()[0].timestamp > previousExitTimestamp + } + val reasons = getExitReasons() + assertTrue(reasons[0].timestamp > previousExitTimestamp) + assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason) + } + + private fun triggerAnr() { startUnresponsiveActivity() - val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation) + val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) val obj: UiObject2? = uiDevice.wait(Until.findObject( By.text("Unresponsive gesture monitor")), 10000) @@ -87,29 +165,14 @@ class AnrTest { MotionEvent.ACTION_DOWN, rect.left.toFloat(), rect.top.toFloat(), 0 /* metaState */) downEvent.source = InputDevice.SOURCE_TOUCHSCREEN - mInstrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/) - - // Todo: replace using timeout from android.hardware.input.IInputManager - SystemClock.sleep(5000) // default ANR timeout for gesture monitors - - clickCloseAppOnAnrDialog() - } + instrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/) - private fun clickCloseAppOnAnrDialog() { - // Find anr dialog and kill app - val uiDevice: UiDevice = UiDevice.getInstance(mInstrumentation) - val closeAppButton: UiObject2? = - uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000) - if (closeAppButton == null) { - fail("Could not find anr dialog") - return - } - closeAppButton.click() + SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors } private fun startUnresponsiveActivity() { val flags = " -W -n " - val startCmd = "am start $flags com.android.test.input/.UnresponsiveGestureMonitorActivity" - mInstrumentation.uiAutomation.executeShellCommand(startCmd) + val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity" + instrumentation.uiAutomation.executeShellCommand(startCmd) } -}
\ No newline at end of file +} diff --git a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt index 014efc2b954c..37b67f4c183c 100644 --- a/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt +++ b/tests/Input/src/com/android/test/input/InputEventSenderAndReceiverTest.kt @@ -17,16 +17,11 @@ package com.android.test.input import android.os.HandlerThread -import android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS import android.os.Looper import android.view.InputChannel import android.view.InputEvent import android.view.InputEventReceiver -import android.view.InputEventSender import android.view.KeyEvent -import android.view.MotionEvent -import java.util.concurrent.LinkedBlockingQueue -import java.util.concurrent.TimeUnit import org.junit.Assert.assertEquals import org.junit.After import org.junit.Before @@ -46,54 +41,19 @@ private fun assertKeyEvent(expected: KeyEvent, received: KeyEvent) { assertEquals(expected.displayId, received.displayId) } -private fun <T> getEvent(queue: LinkedBlockingQueue<T>): T { - try { - return queue.poll(DEFAULT_DISPATCHING_TIMEOUT_MILLIS.toLong(), TimeUnit.MILLISECONDS) - } catch (e: InterruptedException) { - throw RuntimeException("Unexpectedly interrupted while waiting for event") - } +private fun getTestKeyEvent(): KeyEvent { + return KeyEvent(1 /*downTime*/, 1 /*eventTime*/, KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_A, 0 /*repeat*/) } -class TestInputEventReceiver(channel: InputChannel, looper: Looper) : +private class CrashingInputEventReceiver(channel: InputChannel, looper: Looper) : InputEventReceiver(channel, looper) { - private val mInputEvents = LinkedBlockingQueue<InputEvent>() - override fun onInputEvent(event: InputEvent) { - when (event) { - is KeyEvent -> mInputEvents.put(KeyEvent.obtain(event)) - is MotionEvent -> mInputEvents.put(MotionEvent.obtain(event)) - else -> throw Exception("Received $event is neither a key nor a motion") + try { + throw IllegalArgumentException("This receiver crashes when it receives input event") + } finally { + finishInputEvent(event, true /*handled*/) } - finishInputEvent(event, true /*handled*/) - } - - fun getInputEvent(): InputEvent { - return getEvent(mInputEvents) - } -} - -class TestInputEventSender(channel: InputChannel, looper: Looper) : - InputEventSender(channel, looper) { - data class FinishedSignal(val seq: Int, val handled: Boolean) - data class Timeline(val inputEventId: Int, val gpuCompletedTime: Long, val presentTime: Long) - - private val mFinishedSignals = LinkedBlockingQueue<FinishedSignal>() - private val mTimelines = LinkedBlockingQueue<Timeline>() - - override fun onInputEventFinished(seq: Int, handled: Boolean) { - mFinishedSignals.put(FinishedSignal(seq, handled)) - } - - override fun onTimelineReported(inputEventId: Int, gpuCompletedTime: Long, presentTime: Long) { - mTimelines.put(Timeline(inputEventId, gpuCompletedTime, presentTime)) - } - - fun getFinishedSignal(): FinishedSignal { - return getEvent(mFinishedSignals) - } - - fun getTimeline(): Timeline { - return getEvent(mTimelines) } } @@ -102,8 +62,8 @@ class InputEventSenderAndReceiverTest { private const val TAG = "InputEventSenderAndReceiverTest" } private val mHandlerThread = HandlerThread("Process input events") - private lateinit var mReceiver: TestInputEventReceiver - private lateinit var mSender: TestInputEventSender + private lateinit var mReceiver: SpyInputEventReceiver + private lateinit var mSender: SpyInputEventSender @Before fun setUp() { @@ -111,8 +71,8 @@ class InputEventSenderAndReceiverTest { mHandlerThread.start() val looper = mHandlerThread.getLooper() - mSender = TestInputEventSender(channels[0], looper) - mReceiver = TestInputEventReceiver(channels[1], looper) + mSender = SpyInputEventSender(channels[0], looper) + mReceiver = SpyInputEventReceiver(channels[1], looper) } @After @@ -122,8 +82,7 @@ class InputEventSenderAndReceiverTest { @Test fun testSendAndReceiveKey() { - val key = KeyEvent(1 /*downTime*/, 1 /*eventTime*/, KeyEvent.ACTION_DOWN, - KeyEvent.KEYCODE_A, 0 /*repeat*/) + val key = getTestKeyEvent() val seq = 10 mSender.sendInputEvent(seq, key) val receivedKey = mReceiver.getInputEvent() as KeyEvent @@ -133,13 +92,13 @@ class InputEventSenderAndReceiverTest { assertKeyEvent(key, receivedKey) // Check sender - assertEquals(TestInputEventSender.FinishedSignal(seq, handled = true), finishedSignal) + assertEquals(SpyInputEventSender.FinishedSignal(seq, handled = true), finishedSignal) } // The timeline case is slightly unusual because it goes from InputConsumer to InputPublisher. @Test fun testSendAndReceiveTimeline() { - val sent = TestInputEventSender.Timeline( + val sent = SpyInputEventSender.Timeline( inputEventId = 1, gpuCompletedTime = 2, presentTime = 3) mReceiver.reportTimeline(sent.inputEventId, sent.gpuCompletedTime, sent.presentTime) val received = mSender.getTimeline() @@ -151,7 +110,7 @@ class InputEventSenderAndReceiverTest { // event processing. @Test fun testSendAndReceiveInvalidTimeline() { - val sent = TestInputEventSender.Timeline( + val sent = SpyInputEventSender.Timeline( inputEventId = 1, gpuCompletedTime = 3, presentTime = 2) mReceiver.reportTimeline(sent.inputEventId, sent.gpuCompletedTime, sent.presentTime) val received = mSender.getTimeline() @@ -162,4 +121,41 @@ class InputEventSenderAndReceiverTest { val receivedSecondTimeline = mSender.getTimeline() assertEquals(null, receivedSecondTimeline) } + + /** + * If a receiver throws an exception during 'onInputEvent' execution, the 'finally' block still + * completes, and therefore, finishInputEvent is called. Make sure that there's no crash in the + * native layer in these circumstances. + * In this test, we are reusing the 'mHandlerThread', but we are creating new sender and + * receiver. + */ + @Test + fun testCrashingReceiverDoesNotCrash() { + val channels = InputChannel.openInputChannelPair("TestChannel2") + val sender = SpyInputEventSender(channels[0], mHandlerThread.getLooper()) + + // Need a separate thread for the receiver so that the sender can still get the response + // after the receiver crashes + val receiverThread = HandlerThread("Receive input events") + receiverThread.start() + val crashingReceiver = CrashingInputEventReceiver(channels[1], receiverThread.getLooper()) + receiverThread.setUncaughtExceptionHandler { thread, exception -> + if (thread == receiverThread && exception is IllegalArgumentException) { + // do nothing - this is the exception that we need to ignore + } else { + throw exception + } + } + + val key = getTestKeyEvent() + val seq = 11 + sender.sendInputEvent(seq, key) + val finishedSignal = sender.getFinishedSignal() + assertEquals(SpyInputEventSender.FinishedSignal(seq, handled = true), finishedSignal) + + // Clean up + crashingReceiver.dispose() + sender.dispose() + receiverThread.quitSafely() + } } diff --git a/tests/Input/src/com/android/test/input/PointerEventDispatcherTest.kt b/tests/Input/src/com/android/test/input/PointerEventDispatcherTest.kt new file mode 100644 index 000000000000..1099878a1954 --- /dev/null +++ b/tests/Input/src/com/android/test/input/PointerEventDispatcherTest.kt @@ -0,0 +1,93 @@ +/* + * 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.test.input + +import android.os.HandlerThread +import android.view.InputChannel +import android.view.InputDevice +import android.view.MotionEvent +import android.view.WindowManagerPolicyConstants.PointerEventListener + +import com.android.server.UiThread +import com.android.server.wm.PointerEventDispatcher +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.After +import org.junit.Before +import org.junit.Test + +private class CrashingPointerEventListener : PointerEventListener { + override fun onPointerEvent(motionEvent: MotionEvent) { + throw IllegalArgumentException("This listener crashes when input event occurs") + } +} + +class PointerEventDispatcherTest { + companion object { + private const val TAG = "PointerEventDispatcherTest" + } + private val mHandlerThread = HandlerThread("Process input events") + private lateinit var mSender: SpyInputEventSender + private lateinit var mPointerEventDispatcher: PointerEventDispatcher + private val mListener = CrashingPointerEventListener() + + @Before + fun setUp() { + val channels = InputChannel.openInputChannelPair("TestChannel") + + mHandlerThread.start() + val looper = mHandlerThread.getLooper() + mSender = SpyInputEventSender(channels[0], looper) + + mPointerEventDispatcher = PointerEventDispatcher(channels[1]) + mPointerEventDispatcher.registerInputEventListener(mListener) + } + + @After + fun tearDown() { + mHandlerThread.quitSafely() + } + + @Test + fun testSendMotionToCrashingListenerDoesNotCrash() { + // The exception will occur on the UiThread, so we can't catch it here on the test thread + UiThread.get().setUncaughtExceptionHandler { thread, exception -> + if (thread == UiThread.get() && exception is IllegalArgumentException) { + // do nothing - this is the exception that we need to ignore + } else { + throw exception + } + } + + // The MotionEvent properties aren't important for this test, as long as the event + // is a pointer event, so that it gets processed by CrashingPointerEventListener + val downTime = 0L + val motionEvent = MotionEvent.obtain(downTime, downTime, + MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, 0 /* metaState */) + motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN + val seq = 10 + mSender.sendInputEvent(seq, motionEvent) + val finishedSignal = mSender.getFinishedSignal() + + // Since the listener raises an exception during the event handling, the event should be + // marked as 'not handled'. + assertEquals(SpyInputEventSender.FinishedSignal(seq, handled = false), finishedSignal) + // Ensure that there aren't double finish calls. This would crash if there's a call + // to finish twice. + assertNull(mSender.getFinishedSignal()) + } +} diff --git a/tests/Input/src/com/android/test/input/SpyInputEventSenderAndReceiver.kt b/tests/Input/src/com/android/test/input/SpyInputEventSenderAndReceiver.kt new file mode 100644 index 000000000000..2d9af9a65d33 --- /dev/null +++ b/tests/Input/src/com/android/test/input/SpyInputEventSenderAndReceiver.kt @@ -0,0 +1,75 @@ +/* + * 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.test.input + +import android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS +import android.os.Looper +import android.view.InputChannel +import android.view.InputEvent +import android.view.InputEventReceiver +import android.view.InputEventSender +import android.view.KeyEvent +import android.view.MotionEvent +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.TimeUnit + +private fun <T> getEvent(queue: LinkedBlockingQueue<T>): T? { + return queue.poll(DEFAULT_DISPATCHING_TIMEOUT_MILLIS.toLong(), TimeUnit.MILLISECONDS) +} + +class SpyInputEventReceiver(channel: InputChannel, looper: Looper) : + InputEventReceiver(channel, looper) { + private val mInputEvents = LinkedBlockingQueue<InputEvent>() + + override fun onInputEvent(event: InputEvent) { + when (event) { + is KeyEvent -> mInputEvents.put(KeyEvent.obtain(event)) + is MotionEvent -> mInputEvents.put(MotionEvent.obtain(event)) + else -> throw Exception("Received $event is neither a key nor a motion") + } + finishInputEvent(event, true /*handled*/) + } + + fun getInputEvent(): InputEvent? { + return getEvent(mInputEvents) + } +} + +class SpyInputEventSender(channel: InputChannel, looper: Looper) : + InputEventSender(channel, looper) { + data class FinishedSignal(val seq: Int, val handled: Boolean) + data class Timeline(val inputEventId: Int, val gpuCompletedTime: Long, val presentTime: Long) + + private val mFinishedSignals = LinkedBlockingQueue<FinishedSignal>() + private val mTimelines = LinkedBlockingQueue<Timeline>() + + override fun onInputEventFinished(seq: Int, handled: Boolean) { + mFinishedSignals.put(FinishedSignal(seq, handled)) + } + + override fun onTimelineReported(inputEventId: Int, gpuCompletedTime: Long, presentTime: Long) { + mTimelines.put(Timeline(inputEventId, gpuCompletedTime, presentTime)) + } + + fun getFinishedSignal(): FinishedSignal? { + return getEvent(mFinishedSignals) + } + + fun getTimeline(): Timeline? { + return getEvent(mTimelines) + } +} diff --git a/tests/InputMethodStressTest/Android.bp b/tests/InputMethodStressTest/Android.bp new file mode 100644 index 000000000000..131611d28f0a --- /dev/null +++ b/tests/InputMethodStressTest/Android.bp @@ -0,0 +1,35 @@ +// 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 { + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "InputMethodStressTest", + srcs: ["src/**/*.java"], + libs: ["android.test.runner"], + static_libs: [ + "androidx.test.ext.junit", + "androidx.test.uiautomator_uiautomator", + "compatibility-device-util-axt", + "platform-test-annotations", + "truth-prebuilt", + ], + test_suites: [ + "general-tests", + "vts", + ], + sdk_version: "31", +} diff --git a/tests/InputMethodStressTest/AndroidManifest.xml b/tests/InputMethodStressTest/AndroidManifest.xml new file mode 100644 index 000000000000..f5fe8f2e8e76 --- /dev/null +++ b/tests/InputMethodStressTest/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?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="com.android.inputmethod.stresstest"> + + <application> + <activity android:name=".AutoShowTest$TestActivity"/> + <activity android:name=".ImeOpenCloseStressTest$TestActivity"/> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.inputmethod.stresstest"> + </instrumentation> +</manifest> diff --git a/tests/InputMethodStressTest/AndroidTest.xml b/tests/InputMethodStressTest/AndroidTest.xml new file mode 100644 index 000000000000..b194010a985a --- /dev/null +++ b/tests/InputMethodStressTest/AndroidTest.xml @@ -0,0 +1,29 @@ +<?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 description="InputMethod integration/regression test"> + <option name="test-suite-tag" value="apct" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="InputMethodStressTest.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="com.android.inputmethod.stresstest" /> + </test> +</configuration> diff --git a/tests/InputMethodStressTest/OWNERS b/tests/InputMethodStressTest/OWNERS new file mode 100644 index 000000000000..6bb4b17ed4eb --- /dev/null +++ b/tests/InputMethodStressTest/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 34867 + +include /services/core/java/com/android/server/inputmethod/OWNERS diff --git a/tests/InputMethodStressTest/TEST_MAPPING b/tests/InputMethodStressTest/TEST_MAPPING new file mode 100644 index 000000000000..ad07205ab02d --- /dev/null +++ b/tests/InputMethodStressTest/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "InputMethodStressTest" + } + ] +} diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java new file mode 100644 index 000000000000..33cad78ef73c --- /dev/null +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java @@ -0,0 +1,83 @@ +/* + * 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.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.waitOnMainUntilImeIsShown; + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.Intent; +import android.os.Bundle; +import android.platform.test.annotations.RootPermissionTest; +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.Test; +import org.junit.runner.RunWith; + +@RootPermissionTest +@RunWith(AndroidJUnit4.class) +public final class AutoShowTest { + + @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); + 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(); + } + + public EditText getEditText() { + return mEditText; + } + } +} diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java new file mode 100644 index 000000000000..0e86bc8de152 --- /dev/null +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java @@ -0,0 +1,96 @@ +/* + * 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.inputmethod.stresstest; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; + +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 android.app.Activity; +import android.app.Instrumentation; +import android.content.Intent; +import android.os.Bundle; +import android.platform.test.annotations.RootPermissionTest; +import android.view.inputmethod.InputMethodManager; +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.Test; +import org.junit.runner.RunWith; + +@RootPermissionTest +@RunWith(AndroidJUnit4.class) +public final class ImeOpenCloseStressTest { + + private static final int NUM_TEST_ITERATIONS = 100; + + @Test + public void test() { + 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); + EditText editText = activity.getEditText(); + waitOnMainUntil("activity should gain focus", editText::hasWindowFocus); + for (int i = 0; i < NUM_TEST_ITERATIONS; i++) { + instrumentation.runOnMainSync(activity::showIme); + waitOnMainUntilImeIsShown(editText); + instrumentation.runOnMainSync(activity::hideIme); + waitOnMainUntilImeIsHidden(editText); + } + } + + public static class TestActivity extends Activity { + + private EditText mEditText; + + @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); + } + + public EditText getEditText() { + return mEditText; + } + + public void showIme() { + mEditText.requestFocus(); + InputMethodManager imm = getSystemService(InputMethodManager.class); + imm.showSoftInput(mEditText, 0); + } + + public void hideIme() { + InputMethodManager imm = getSystemService(InputMethodManager.class); + imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); + } + } +} diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java new file mode 100644 index 000000000000..ba2ba3c75bc2 --- /dev/null +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeStressTestUtil.java @@ -0,0 +1,80 @@ +/* + * 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.inputmethod.stresstest; + +import static com.android.compatibility.common.util.SystemUtil.eventually; + +import static com.google.common.truth.Truth.assertWithMessage; + +import android.view.View; +import android.view.WindowInsets; + +import androidx.test.platform.app.InstrumentationRegistry; + +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +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 ImeStressTestUtil() { + } + + /** 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(); + 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); + } + }); + return result.get(); + } + + /** + * Waits until {@code pred} returns true, or throws on timeout. + * + * <p>The given {@code pred} will be called on the main thread. + */ + public static void waitOnMainUntil(String message, Callable<Boolean> pred) { + eventually(() -> assertWithMessage(message).that(pred.call()).isTrue(), TIMEOUT); + } + + /** 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); + } + + /** 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); + } +} diff --git a/tests/Internal/src/com/android/internal/util/ParcellingTests.java b/tests/Internal/src/com/android/internal/util/ParcellingTests.java new file mode 100644 index 000000000000..65a3436a4c5e --- /dev/null +++ b/tests/Internal/src/com/android/internal/util/ParcellingTests.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.internal.util; + +import android.os.Parcel; +import android.platform.test.annotations.Presubmit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import androidx.test.filters.SmallTest; + +import com.android.internal.util.Parcelling.BuiltIn.ForInstant; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.time.Instant; + +/** Tests for {@link Parcelling}. */ +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class ParcellingTests { + + private Parcel mParcel = Parcel.obtain(); + + @Test + public void forInstant_normal() { + testForInstant(Instant.ofEpochSecond(500L, 10)); + } + + @Test + public void forInstant_minimum() { + testForInstant(Instant.MIN); + } + + @Test + public void forInstant_maximum() { + testForInstant(Instant.MAX); + } + + @Test + public void forInstant_null() { + testForInstant(null); + } + + private void testForInstant(Instant instant) { + Parcelling<Instant> parcelling = new ForInstant(); + parcelling.parcel(instant, mParcel, 0); + mParcel.setDataPosition(0); + + Instant created = parcelling.unparcel(mParcel); + + if (instant == null) { + assertNull(created); + } else { + assertEquals(instant, created); + } + } + +} 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 e956be339bc4..dd9b294a9596 100644 --- a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java +++ b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java @@ -82,11 +82,10 @@ public class JobStorePerfTests { long elapsedTimeNs = 0; while (benchmarkState.keepRunning(elapsedTimeNs)) { - sJobStore.clear(); + sJobStore.clearForTesting(); for (JobStatus job : jobList) { - sJobStore.add(job); + sJobStore.addForTesting(job); } - sJobStore.waitForWriteToCompleteForTesting(10_000); final long startTime = SystemClock.elapsedRealtimeNanos(); sJobStore.writeStatusToDiskForTesting(); @@ -110,11 +109,11 @@ public class JobStorePerfTests { long elapsedTimeNs = 0; while (benchmarkState.keepRunning(elapsedTimeNs)) { - sJobStore.clear(); + sJobStore.clearForTesting(); for (JobStatus job : jobList) { - sJobStore.add(job); + sJobStore.addForTesting(job); } - sJobStore.waitForWriteToCompleteForTesting(10_000); + sJobStore.writeStatusToDiskForTesting(); JobSet jobSet = new JobSet(); diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp index 6e1cef496f40..9f6ce4e8425b 100644 --- a/tests/RollbackTest/Android.bp +++ b/tests/RollbackTest/Android.bp @@ -31,7 +31,8 @@ android_test { test_config: "RollbackTest.xml", java_resources: [ ":com.android.apex.apkrollback.test_v2", - ":com.android.apex.apkrollback.test_v2Crashing" + ":com.android.apex.apkrollback.test_v2Crashing", + ":test.rebootless_apex_v2", ], } @@ -47,7 +48,10 @@ java_test_host { ], test_suites: ["general-tests"], test_config: "StagedRollbackTest.xml", - data: [":com.android.apex.apkrollback.test_v1"], + data: [ + ":com.android.apex.apkrollback.test_v1", + ":test.rebootless_apex_v1", + ], } java_test_host { diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java index 7b2a07fd80f8..cbdcb8869628 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java @@ -16,7 +16,6 @@ package com.android.tests.rollback; -import static com.android.cts.install.lib.InstallUtils.processUserData; import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat; import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoForPackage; import static com.android.cts.rollback.lib.RollbackUtils.waitForAvailableRollback; @@ -31,11 +30,9 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageManager; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; import android.os.UserManager; -import android.provider.DeviceConfig; import android.util.Log; import androidx.test.InstrumentationRegistry; @@ -55,7 +52,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -71,12 +67,6 @@ public class RollbackTest { private static final String INSTRUMENTED_APP = "com.android.tests.rollback"; - // copied from PackageManagerService#PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS - // TODO: find a better place for the property so that it can be imported in tests - // maybe android.content.pm.PackageManager? - private static final String PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS = - "enable_rollback_timeout"; - private static boolean hasRollbackInclude(List<RollbackInfo> rollbacks, String packageName) { return rollbacks.stream().anyMatch( ri -> ri.getPackages().stream().anyMatch( @@ -203,740 +193,6 @@ public class RollbackTest { } } - /** - * Test that multiple available rollbacks are properly persisted. - */ - @Test - public void testAvailableRollbackPersistence() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - - RollbackManager rm = RollbackUtils.getRollbackManager(); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - Install.single(TestApp.A2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - Uninstall.packages(TestApp.B); - Install.single(TestApp.B1).commit(); - Install.single(TestApp.B2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - - // Both test apps should now be available for rollback. - RollbackInfo rollbackA = waitForAvailableRollback(TestApp.A); - assertThat(rollbackA).isNotNull(); - assertThat(rollbackA).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - - RollbackInfo rollbackB = waitForAvailableRollback(TestApp.B); - assertThat(rollbackB).isNotNull(); - assertThat(rollbackB).packagesContainsExactly( - Rollback.from(TestApp.B2).to(TestApp.B1)); - - // Reload the persisted data. - rm.reloadPersistedData(); - - // The apps should still be available for rollback. - rollbackA = waitForAvailableRollback(TestApp.A); - assertThat(rollbackA).isNotNull(); - assertThat(rollbackA).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - - rollbackB = waitForAvailableRollback(TestApp.B); - assertThat(rollbackB).isNotNull(); - assertThat(rollbackB).packagesContainsExactly( - Rollback.from(TestApp.B2).to(TestApp.B1)); - - // Rollback of B should not rollback A - RollbackUtils.rollback(rollbackB.getRollbackId()); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test that available multi-package rollbacks are properly persisted. - */ - @Test - public void testAvailableMultiPackageRollbackPersistence() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - - RollbackManager rm = RollbackUtils.getRollbackManager(); - - Uninstall.packages(TestApp.A, TestApp.B); - Install.multi(TestApp.A1, TestApp.B1).commit(); - Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit(); - - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - - // The app should now be available for rollback. - RollbackInfo availableA = waitForAvailableRollback(TestApp.A); - assertThat(availableA).isNotNull(); - assertThat(availableA).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1), - Rollback.from(TestApp.B2).to(TestApp.B1)); - - RollbackInfo availableB = waitForAvailableRollback(TestApp.B); - assertThat(availableB).isNotNull(); - assertThat(availableB).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1), - Rollback.from(TestApp.B2).to(TestApp.B1)); - - // Assert they're both the same rollback - assertThat(availableA).hasRollbackId(availableB.getRollbackId()); - - // Reload the persisted data. - rm.reloadPersistedData(); - - // The apps should still be available for rollback. - availableA = waitForAvailableRollback(TestApp.A); - assertThat(availableA).isNotNull(); - assertThat(availableA).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1), - Rollback.from(TestApp.B2).to(TestApp.B1)); - - availableB = waitForAvailableRollback(TestApp.B); - assertThat(availableB).isNotNull(); - assertThat(availableB).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1), - Rollback.from(TestApp.B2).to(TestApp.B1)); - - // Rollback of B should rollback A as well - RollbackUtils.rollback(availableB.getRollbackId()); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); - - RollbackInfo committedA = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TestApp.A); - assertThat(committedA).isNotNull(); - assertThat(committedA).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1), - Rollback.from(TestApp.B2).to(TestApp.B1)); - - RollbackInfo committedB = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TestApp.A); - assertThat(committedB).isNotNull(); - assertThat(committedB).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1), - Rollback.from(TestApp.B2).to(TestApp.B1)); - - // Assert they're both the same rollback - assertThat(committedA).hasRollbackId(committedB.getRollbackId()); - assertThat(committedA).hasRollbackId(availableA.getRollbackId()); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test that recently committed rollback data is properly persisted. - */ - @Test - public void testRecentlyCommittedRollbackPersistence() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - - RollbackManager rm = RollbackUtils.getRollbackManager(); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - Install.single(TestApp.A2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - // The app should now be available for rollback. - RollbackInfo available = waitForAvailableRollback(TestApp.A); - assertThat(available).isNotNull(); - - // Roll back the app. - TestApp cause = new TestApp("Foo", "com.android.tests.rollback.testapp.Foo", - /*versionCode*/ 42, /*isApex*/ false); - RollbackUtils.rollback(available.getRollbackId(), cause); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - - // Verify the recent rollback has been recorded. - RollbackInfo committed = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TestApp.A); - assertThat(committed).isNotNull(); - assertThat(committed).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - assertThat(committed).causePackagesContainsExactly(cause); - - // Reload the persisted data. - rm.reloadPersistedData(); - - // Verify the recent rollback is still recorded. - committed = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TestApp.A); - assertThat(committed).isNotNull(); - assertThat(committed).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - assertThat(committed).causePackagesContainsExactly(cause); - assertThat(committed).hasRollbackId(available.getRollbackId()); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test the scheduling aspect of rollback expiration. - */ - @Test - public void testRollbackExpiresAfterLifetime() throws Exception { - long expirationTime = TimeUnit.SECONDS.toMillis(30); - long defaultExpirationTime = TimeUnit.HOURS.toMillis(48); - RollbackManager rm = RollbackUtils.getRollbackManager(); - - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS, - Manifest.permission.WRITE_DEVICE_CONFIG); - - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, - RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, - Long.toString(expirationTime), false /* makeDefault*/); - - // Uninstall TestApp.A - Uninstall.packages(TestApp.A); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); - - // Install v1 of the app (without rollbacks enabled). - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - - // Upgrade from v1 to v2, with rollbacks enabled. - Install.single(TestApp.A2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - // Check that the rollback data has not expired - Thread.sleep(1000); - RollbackInfo rollback = waitForAvailableRollback(TestApp.A); - assertThat(rollback).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - - // Give it a little more time, but still not long enough to expire - Thread.sleep(expirationTime / 2); - rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.A); - assertThat(rollback).isNotNull(); - assertThat(rollback).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - - // Check that the data has expired after the expiration time (with a buffer of 1 second) - Thread.sleep(expirationTime / 2); - rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.A); - assertThat(rollback).isNull(); - - } finally { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, - RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, - Long.toString(defaultExpirationTime), false /* makeDefault*/); - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test that available rollbacks should expire correctly when the property - * {@link RollbackManager#PROPERTY_ROLLBACK_LIFETIME_MILLIS} is changed - */ - @Test - public void testRollbackExpiresWhenLifetimeChanges() throws Exception { - long defaultExpirationTime = TimeUnit.HOURS.toMillis(48); - RollbackManager rm = RollbackUtils.getRollbackManager(); - - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS, - Manifest.permission.WRITE_DEVICE_CONFIG); - - Uninstall.packages(TestApp.A); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - Install.single(TestApp.A2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - RollbackInfo rollback = waitForAvailableRollback(TestApp.A); - assertThat(rollback).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1)); - - // Change the lifetime to 0 which should expire rollbacks immediately - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, - RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, - Long.toString(0), false /* makeDefault*/); - - // Keep polling until device config changes has happened (which might take more than - // 5 sec depending how busy system_server is) and rollbacks have expired - for (int i = 0; i < 30; ++i) { - if (hasRollbackInclude(rm.getAvailableRollbacks(), TestApp.A)) { - Thread.sleep(1000); - } - } - rollback = getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A); - assertThat(rollback).isNull(); - } finally { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, - RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, - Long.toString(defaultExpirationTime), false /* makeDefault*/); - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test that changing time on device does not affect the duration of time that we keep - * rollback available - */ - @Test - public void testTimeChangeDoesNotAffectLifetime() throws Exception { - long expirationTime = TimeUnit.SECONDS.toMillis(30); - long defaultExpirationTime = TimeUnit.HOURS.toMillis(48); - RollbackManager rm = RollbackUtils.getRollbackManager(); - - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS, - Manifest.permission.WRITE_DEVICE_CONFIG, - Manifest.permission.SET_TIME); - - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, - RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, - Long.toString(expirationTime), false /* makeDefault*/); - - // Install app A with rollback enabled - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - Install.single(TestApp.A2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - Thread.sleep(expirationTime / 2); - - // Install app B with rollback enabled - Uninstall.packages(TestApp.B); - Install.single(TestApp.B1).commit(); - Install.single(TestApp.B2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - - // 1 second buffer - Thread.sleep(1000); - - try { - // Change the time - RollbackUtils.forwardTimeBy(expirationTime); - - // 1 second buffer to allow Rollback Manager to handle time change before loading - // persisted data - Thread.sleep(1000); - - // Load timestamps from storage - rm.reloadPersistedData(); - - // Wait until rollback for app A has expired - // This will trigger an expiration run that should expire app A but not B - Thread.sleep(expirationTime / 2); - RollbackInfo rollbackA = - getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A); - Log.i(TAG, "Checking if the rollback for TestApp.A is null"); - - // Rollback for app B should not be expired - RollbackInfo rollbackB1 = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.B); - - // Wait until rollback for app B has expired - Thread.sleep(expirationTime / 2); - RollbackInfo rollbackB2 = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.B); - - assertThat(rollbackA).isNull(); - assertThat(rollbackB1).isNotNull(); - assertThat(rollbackB1).packagesContainsExactly( - Rollback.from(TestApp.B2).to(TestApp.B1)); - assertThat(rollbackB2).isNull(); - } finally { - RollbackUtils.forwardTimeBy(-expirationTime); - } - } finally { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT, - RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS, - Long.toString(defaultExpirationTime), false /* makeDefault*/); - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test explicit expiration of rollbacks. - * Does not test the scheduling aspects of rollback expiration. - */ - @Test - public void testRollbackExpiration() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - - RollbackManager rm = RollbackUtils.getRollbackManager(); - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - Install.single(TestApp.A2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - // The app should now be available for rollback. - RollbackInfo rollback = waitForAvailableRollback(TestApp.A); - assertThat(rollback).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - - // Expire the rollback. - rm.expireRollbackForPackage(TestApp.A); - - // The rollback should no longer be available. - assertThat(getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.A)).isNull(); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test that app user data is rolled back. - */ - @Test - public void testUserDataRollback() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - processUserData(TestApp.A); - Install.single(TestApp.A2).setEnableRollback().commit(); - processUserData(TestApp.A); - - RollbackInfo rollback = waitForAvailableRollback(TestApp.A); - RollbackUtils.rollback(rollback.getRollbackId()); - processUserData(TestApp.A); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test rollback of apks involving splits. - */ - @Test - public void testRollbackWithSplits() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.ASplit1).commit(); - processUserData(TestApp.A); - - Install.single(TestApp.ASplit2).setEnableRollback().commit(); - processUserData(TestApp.A); - - RollbackInfo rollback = waitForAvailableRollback(TestApp.A); - RollbackUtils.rollback(rollback.getRollbackId()); - processUserData(TestApp.A); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test restrictions on rollback broadcast sender. - * A random app should not be able to send a ROLLBACK_COMMITTED broadcast. - */ - @Test - public void testRollbackBroadcastRestrictions() throws Exception { - RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver(); - Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED); - try { - InstrumentationRegistry.getContext().sendBroadcast(broadcast); - fail("Succeeded in sending restricted broadcast from app context."); - } catch (SecurityException se) { - // Expected behavior. - } - - // Confirm that we really haven't received the broadcast. - // TODO: How long to wait for the expected timeout? - assertThat(broadcastReceiver.poll(5, TimeUnit.SECONDS)).isNull(); - - // TODO: Do we need to do this? Do we need to ensure this is always - // called, even when the test fails? - broadcastReceiver.unregister(); - } - - /** - * Regression test for rollback in the case when multiple apps are - * available for rollback at the same time. - */ - @Test - public void testMultipleRollbackAvailable() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - - // Prep installation of the test apps. - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - Install.single(TestApp.A2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - Uninstall.packages(TestApp.B); - Install.single(TestApp.B1).commit(); - Install.single(TestApp.B2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - - // Both test apps should now be available for rollback, and the - // RollbackInfo returned for the rollbacks should be correct. - RollbackInfo rollbackA = waitForAvailableRollback(TestApp.A); - assertThat(rollbackA).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - - RollbackInfo rollbackB = waitForAvailableRollback(TestApp.B); - assertThat(rollbackB).packagesContainsExactly( - Rollback.from(TestApp.B2).to(TestApp.B1)); - - // Executing rollback should roll back the correct package. - RollbackUtils.rollback(rollbackA.getRollbackId()); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - Install.single(TestApp.A2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - RollbackUtils.rollback(rollbackB.getRollbackId()); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test that the MANAGE_ROLLBACKS permission is required to call - * RollbackManager APIs. - */ - @Test - public void testManageRollbacksPermission() throws Exception { - // We shouldn't be allowed to call any of the RollbackManager APIs - // without the MANAGE_ROLLBACKS permission. - RollbackManager rm = RollbackUtils.getRollbackManager(); - - try { - rm.getAvailableRollbacks(); - fail("expected SecurityException"); - } catch (SecurityException e) { - // Expected. - } - - try { - rm.getRecentlyCommittedRollbacks(); - fail("expected SecurityException"); - } catch (SecurityException e) { - // Expected. - } - - try { - // TODO: What if the implementation checks arguments for non-null - // first? Then this test isn't valid. - rm.commitRollback(0, Collections.emptyList(), null); - fail("expected SecurityException"); - } catch (SecurityException e) { - // Expected. - } - - try { - rm.reloadPersistedData(); - fail("expected SecurityException"); - } catch (SecurityException e) { - // Expected. - } - - try { - rm.expireRollbackForPackage(TestApp.A); - fail("expected SecurityException"); - } catch (SecurityException e) { - // Expected. - } - } - - /** - * Test that you cannot enable rollback for a package without the - * MANAGE_ROLLBACKS permission. - */ - @Test - public void testEnableRollbackPermission() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - - Install.single(TestApp.A2).setEnableRollback().commit(); - - // We expect v2 of the app was installed, but rollback has not - // been enabled. - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat( - getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull(); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test that you cannot enable rollback for a non-module package when - * holding the MANAGE_ROLLBACKS permission. - */ - @Test - public void testNonModuleEnableRollback() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.MANAGE_ROLLBACKS); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - - Install.single(TestApp.A2).setEnableRollback().commit(); - - // We expect v2 of the app was installed, but rollback has not - // been enabled because the test app is not a module. - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat( - getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull(); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test rollback of multi-package installs is implemented. - */ - @Test - public void testMultiPackage() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackManager rm = RollbackUtils.getRollbackManager(); - - // Prep installation of the test apps. - Uninstall.packages(TestApp.A, TestApp.B); - Install.multi(TestApp.A1, TestApp.B1).commit(); - processUserData(TestApp.A); - processUserData(TestApp.B); - Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit(); - processUserData(TestApp.A); - processUserData(TestApp.B); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - - // TestApp.A should now be available for rollback. - RollbackInfo rollback = waitForAvailableRollback(TestApp.A); - assertThat(rollback).isNotNull(); - assertThat(rollback).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1), - Rollback.from(TestApp.B2).to(TestApp.B1)); - - // Rollback the app. It should cause both test apps to be rolled - // back. - RollbackUtils.rollback(rollback.getRollbackId()); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); - - // We should see recent rollbacks listed for both A and B. - Thread.sleep(1000); - RollbackInfo rollbackA = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TestApp.A); - - RollbackInfo rollbackB = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TestApp.B); - assertThat(rollback).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1), - Rollback.from(TestApp.B2).to(TestApp.B1)); - - assertThat(rollbackA).hasRollbackId(rollbackB.getRollbackId()); - - processUserData(TestApp.A); - processUserData(TestApp.B); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test failure to enable rollback for multi-package installs. - * If any one of the packages fail to enable rollback, we shouldn't enable - * rollback for any package. - */ - @Test - public void testMultiPackageEnableFail() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - RollbackManager rm = RollbackUtils.getRollbackManager(); - - Uninstall.packages(TestApp.A, TestApp.B); - Install.single(TestApp.A1).commit(); - // We should fail to enable rollback here because TestApp B is not - // already installed. - Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit(); - - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - - assertThat(getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.A)).isNull(); - assertThat(getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.B)).isNull(); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - @Test @Ignore("b/120200473") /** @@ -1074,195 +330,4 @@ public class RollbackTest { InstallUtils.dropShellPermissionIdentity(); } } - - @Test - public void testEnableRollbackTimeoutFailsRollback() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS, - Manifest.permission.MANAGE_ROLLBACKS, - Manifest.permission.WRITE_DEVICE_CONFIG); - - //setting the timeout to a very short amount that will definitely be triggered - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, - PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS, - Long.toString(0), false /* makeDefault*/); - RollbackManager rm = RollbackUtils.getRollbackManager(); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - waitForUnavailableRollback(TestApp.A); - - // Block the RollbackManager to make extra sure it will not be - // able to enable the rollback in time. - rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(1)); - Install.single(TestApp.A2).setEnableRollback().commit(); - - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - // Give plenty of time for RollbackManager to unblock and attempt - // to make the rollback available before asserting that the - // rollback was not made available. - Thread.sleep(TimeUnit.SECONDS.toMillis(2)); - assertThat( - getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TestApp.A)).isNull(); - } finally { - //setting the timeout back to default - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, - PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS, - null, false /* makeDefault*/); - InstallUtils.dropShellPermissionIdentity(); - } - } - - @Test - public void testEnableRollbackTimeoutFailsRollback_MultiPackage() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS, - Manifest.permission.MANAGE_ROLLBACKS, - Manifest.permission.WRITE_DEVICE_CONFIG); - - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, - PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS, - Long.toString(5000), false /* makeDefault*/); - RollbackManager rm = RollbackUtils.getRollbackManager(); - - Uninstall.packages(TestApp.A, TestApp.B); - Install.multi(TestApp.A1, TestApp.B1).commit(); - waitForUnavailableRollback(TestApp.A); - - // Block the 2nd session for 10s so it will not be able to enable the rollback in time. - rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(0)); - rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(10)); - Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit(); - - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - - // Give plenty of time for RollbackManager to unblock and attempt - // to make the rollback available before asserting that the - // rollback was not made available. - Thread.sleep(TimeUnit.SECONDS.toMillis(2)); - - List<RollbackInfo> available = rm.getAvailableRollbacks(); - assertThat(getUniqueRollbackInfoForPackage(available, TestApp.A)).isNull(); - assertThat(getUniqueRollbackInfoForPackage(available, TestApp.B)).isNull(); - } finally { - //setting the timeout back to default - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, - PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS, - null, false /* makeDefault*/); - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Test we can't enable rollback for non-whitelisted app without - * TEST_MANAGE_ROLLBACKS permission - */ - @Test - public void testNonRollbackWhitelistedApp() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.MANAGE_ROLLBACKS); - - Uninstall.packages(TestApp.A); - Install.single(TestApp.A1).commit(); - assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull(); - - Install.single(TestApp.A2).setEnableRollback().commit(); - Thread.sleep(TimeUnit.SECONDS.toMillis(2)); - assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull(); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - @Test - public void testRollbackDataPolicy() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS); - - Uninstall.packages(TestApp.A, TestApp.B, TestApp.C); - Install.multi(TestApp.A1, TestApp.B1, TestApp.C1).commit(); - // Write user data version = 1 - InstallUtils.processUserData(TestApp.A); - InstallUtils.processUserData(TestApp.B); - InstallUtils.processUserData(TestApp.C); - - Install a2 = Install.single(TestApp.A2) - .setEnableRollback(PackageManager.RollbackDataPolicy.WIPE); - Install b2 = Install.single(TestApp.B2) - .setEnableRollback(PackageManager.RollbackDataPolicy.RESTORE); - // The rollback data policy of C2 is specified in the manifest - Install c2 = Install.single(TestApp.C2).setEnableRollback(); - Install.multi(a2, b2, c2).setEnableRollback().commit(); - // Write user data version = 2 - InstallUtils.processUserData(TestApp.A); - InstallUtils.processUserData(TestApp.B); - InstallUtils.processUserData(TestApp.C); - - RollbackInfo info = RollbackUtils.getAvailableRollback(TestApp.A); - RollbackUtils.rollback(info.getRollbackId()); - // Read user data version from userdata.txt - // A's user data version is -1 for user data is wiped. - // B's user data version is 1 as rollback committed. - // C's user data version is -1 for user data is wiped. - assertThat(InstallUtils.getUserDataVersion(TestApp.A)).isEqualTo(-1); - assertThat(InstallUtils.getUserDataVersion(TestApp.B)).isEqualTo(1); - assertThat(InstallUtils.getUserDataVersion(TestApp.C)).isEqualTo(-1); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } - - /** - * Tests an app can be rolled back to the previous signing key. - * - * <p>The rollback capability in the signing lineage allows an app to be updated to an APK - * signed with a previous signing key in the lineage; however this often defeats the purpose - * of key rotation as a compromised key could then be used to roll an app back to the previous - * key. To avoid requiring the rollback capability to support app rollbacks the PackageManager - * allows an app to be rolled back to the previous signing key if the rollback install reason - * is set. - */ - @Test - public void testRollbackAfterKeyRotation() throws Exception { - try { - InstallUtils.adoptShellPermissionIdentity( - Manifest.permission.INSTALL_PACKAGES, - Manifest.permission.DELETE_PACKAGES, - Manifest.permission.TEST_MANAGE_ROLLBACKS, - Manifest.permission.MANAGE_ROLLBACKS); - - // Uninstall TestApp.A - Uninstall.packages(TestApp.A); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); - - // Install v1 of the app with the original signing key (without rollbacks enabled). - Install.single(TestApp.AOriginal1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - - // Upgrade from v1 to v2 with the rotated signing key, with rollbacks enabled. - Install.single(TestApp.ARotated2).setEnableRollback().commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - - // Roll back the app. - RollbackInfo available = waitForAvailableRollback(TestApp.A); - RollbackUtils.rollback(available.getRollbackId()); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - } finally { - InstallUtils.dropShellPermissionIdentity(); - } - } } diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java index 642b19e6d961..bb4866f2cac4 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -22,12 +22,10 @@ import static com.android.cts.rollback.lib.RollbackUtils.getUniqueRollbackInfoFo import static com.google.common.truth.Truth.assertThat; import android.Manifest; -import android.content.Context; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.rollback.RollbackInfo; import android.content.rollback.RollbackManager; -import android.os.storage.StorageManager; import android.provider.DeviceConfig; import androidx.test.platform.app.InstrumentationRegistry; @@ -60,6 +58,7 @@ import java.util.concurrent.TimeUnit; public class StagedRollbackTest { private static final String PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT = "watchdog_trigger_failure_count"; + private static final String REBOOTLESS_APEX_NAME = "test.apex.rebootless"; /** * Adopts common shell permissions needed for rollback tests. @@ -82,155 +81,6 @@ public class StagedRollbackTest { InstallUtils.dropShellPermissionIdentity(); } - /** - * Test rollbacks of staged installs involving only apks with bad update. - * Enable rollback phase. - */ - @Test - public void testBadApkOnly_Phase1_Install() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); - - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - InstallUtils.processUserData(TestApp.A); - - Install.single(TestApp.ACrashing2).setEnableRollback().setStaged().commit(); - } - - /** - * Test rollbacks of staged installs involving only apks with bad update. - * Confirm that rollback was successfully enabled. - */ - @Test - public void testBadApkOnly_Phase2_VerifyInstall() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - InstallUtils.processUserData(TestApp.A); - - RollbackManager rm = RollbackUtils.getRollbackManager(); - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.A); - assertThat(rollback).isNotNull(); - assertThat(rollback).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - assertThat(rollback.isStaged()).isTrue(); - - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, - PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT, - Integer.toString(5), false); - RollbackUtils.sendCrashBroadcast(TestApp.A, 4); - // Sleep for a while to make sure we don't trigger rollback - Thread.sleep(TimeUnit.SECONDS.toMillis(30)); - } - - /** - * Test rollbacks of staged installs involving only apks. - * Confirm rollback phase. - */ - @Test - public void testBadApkOnly_Phase3_VerifyRollback() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - InstallUtils.processUserData(TestApp.A); - - RollbackManager rm = RollbackUtils.getRollbackManager(); - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getRecentlyCommittedRollbacks(), TestApp.A); - assertThat(rollback).isNotNull(); - assertThat(rollback).packagesContainsExactly( - Rollback.from(TestApp.A2).to(TestApp.A1)); - assertThat(rollback).causePackagesContainsExactly(TestApp.ACrashing2); - assertThat(rollback).isStaged(); - assertThat(rollback.getCommittedSessionId()).isNotEqualTo(-1); - } - - /** - * Stage install an apk with rollback that will be later triggered by unattributable crash. - */ - @Test - public void testNativeWatchdogTriggersRollback_Phase1_Install() throws Exception { - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - - Install.single(TestApp.A2).setEnableRollback().setStaged().commit(); - } - - /** - * Verify the rollback is available. - */ - @Test - public void testNativeWatchdogTriggersRollback_Phase2_VerifyInstall() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), - TestApp.A)).isNotNull(); - } - - /** - * Verify the rollback is committed after crashing. - */ - @Test - public void testNativeWatchdogTriggersRollback_Phase3_VerifyRollback() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), - TestApp.A)).isNotNull(); - } - - /** - * Stage install an apk with rollback that will be later triggered by unattributable crash. - */ - @Test - public void testNativeWatchdogTriggersRollbackForAll_Phase1_InstallA() throws Exception { - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - - Install.single(TestApp.A2).setEnableRollback().setStaged().commit(); - } - - /** - * Verify the rollback is available and then install another package with rollback. - */ - @Test - public void testNativeWatchdogTriggersRollbackForAll_Phase2_InstallB() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), - TestApp.A)).isNotNull(); - - // Install another package with rollback - Install.single(TestApp.B1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); - - Install.single(TestApp.B2).setEnableRollback().setStaged().commit(); - } - - /** - * Verify the rollbacks are available. - */ - @Test - public void testNativeWatchdogTriggersRollbackForAll_Phase3_VerifyInstall() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), - TestApp.A)).isNotNull(); - assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), - TestApp.B)).isNotNull(); - } - - /** - * Verify the rollbacks are committed after crashing. - */ - @Test - public void testNativeWatchdogTriggersRollbackForAll_Phase4_VerifyRollback() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); - RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), - TestApp.A)).isNotNull(); - assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), - TestApp.B)).isNotNull(); - } - @Test public void testPreviouslyAbandonedRollbacks_Phase1_InstallAndAbandon() throws Exception { Install.single(TestApp.A1).commit(); @@ -293,50 +143,6 @@ public class StagedRollbackTest { } @Test - public void testRollbackDataPolicy_Phase1_Install() throws Exception { - Install.multi(TestApp.A1, TestApp.B1, TestApp.C1).commit(); - // Write user data version = 1 - InstallUtils.processUserData(TestApp.A); - InstallUtils.processUserData(TestApp.B); - InstallUtils.processUserData(TestApp.C); - - Install a2 = Install.single(TestApp.A2).setStaged() - .setEnableRollback(PackageManager.RollbackDataPolicy.WIPE); - Install b2 = Install.single(TestApp.B2).setStaged() - .setEnableRollback(PackageManager.RollbackDataPolicy.RESTORE); - // The rollback data policy of C2 is specified in the manifest - Install c2 = Install.single(TestApp.C2).setStaged().setEnableRollback(); - Install.multi(a2, b2, c2).setEnableRollback().setStaged().commit(); - } - - @Test - public void testRollbackDataPolicy_Phase2_Rollback() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2); - // Write user data version = 2 - InstallUtils.processUserData(TestApp.A); - InstallUtils.processUserData(TestApp.B); - InstallUtils.processUserData(TestApp.C); - - RollbackInfo info = RollbackUtils.getAvailableRollback(TestApp.A); - RollbackUtils.rollback(info.getRollbackId()); - } - - @Test - public void testRollbackDataPolicy_Phase3_VerifyRollback() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1); - assertThat(InstallUtils.getInstalledVersion(TestApp.C)).isEqualTo(1); - // Read user data version from userdata.txt - // A's user data version is -1 for user data is wiped. - // B's user data version is 1 as rollback committed. - // C's user data version is -1 for user data is wiped. - assertThat(InstallUtils.getUserDataVersion(TestApp.A)).isEqualTo(-1); - assertThat(InstallUtils.getUserDataVersion(TestApp.B)).isEqualTo(1); - assertThat(InstallUtils.getUserDataVersion(TestApp.C)).isEqualTo(-1); - } - - @Test public void expireRollbacks() throws Exception { // testNativeWatchdogTriggersRollback will fail if multiple staged sessions are // committed on a device which doesn't support checkpoint. Let's clean up all rollbacks @@ -359,51 +165,6 @@ public class StagedRollbackTest { "TestApexWithApkV2Crashing", APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2Crashing.apex"); - @Test - public void testRollbackApexWithApk_Phase1_Install() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - InstallUtils.processUserData(TestApp.A); - - int sessionId = Install.single(TEST_APEX_WITH_APK_V2).setStaged().setEnableRollback() - .commit(); - InstallUtils.waitForSessionReady(sessionId); - } - - @Test - public void testRollbackApexWithApk_Phase2_Rollback() throws Exception { - assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - InstallUtils.processUserData(TestApp.A); - - RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME); - assertThat(available).isStaged(); - assertThat(available).packagesContainsExactly( - Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), - Rollback.from(TestApp.A, 0).to(TestApp.A1)); - - RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2); - RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId()); - assertThat(committed).isNotNull(); - assertThat(committed).isStaged(); - assertThat(committed).packagesContainsExactly( - Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1), - Rollback.from(TestApp.A, 0).to(TestApp.A1)); - assertThat(committed).causePackagesContainsExactly(TEST_APEX_WITH_APK_V2); - assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1); - - // Note: The app is not rolled back until after the rollback is staged - // and the device has been rebooted. - InstallUtils.waitForSessionReady(committed.getCommittedSessionId()); - assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2); - } - - @Test - public void testRollbackApexWithApk_Phase3_VerifyRollback() throws Exception { - assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - InstallUtils.processUserData(TestApp.A); - } - /** * Installs an apex with an apk that can crash. */ @@ -442,52 +203,6 @@ public class StagedRollbackTest { } @Test - public void testRollbackApexDataDirectories_Phase1_Install() throws Exception { - int sessionId = Install.single(TEST_APEX_WITH_APK_V2).setStaged().setEnableRollback() - .commit(); - InstallUtils.waitForSessionReady(sessionId); - } - - @Test - public void testRollbackApexDataDirectories_Phase2_Rollback() throws Exception { - RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME); - - RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2); - RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId()); - - // Note: The app is not rolled back until after the rollback is staged - // and the device has been rebooted. - InstallUtils.waitForSessionReady(committed.getCommittedSessionId()); - } - - @Test - public void testRollbackApkDataDirectories_Phase1_InstallV1() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - } - - @Test - public void testRollbackApkDataDirectories_Phase2_InstallV2() throws Exception { - Install.single(TestApp.A2).setStaged().setEnableRollback().commit(); - } - - @Test - public void testRollbackApkDataDirectories_Phase3_Rollback() throws Exception { - RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A); - RollbackUtils.rollback(available.getRollbackId(), TestApp.A2); - RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId()); - InstallUtils.waitForSessionReady(committed.getCommittedSessionId()); - } - - @Test - public void isCheckpointSupported() { - Context context = InstrumentationRegistry.getInstrumentation().getContext(); - StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); - assertThat(sm.isCheckpointSupported()).isTrue(); - } - - @Test public void testWatchdogMonitorsAcrossReboots_Phase1_Install() throws Exception { assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); Install.single(TestApp.A1).commit(); @@ -527,30 +242,49 @@ public class StagedRollbackTest { } @Test - public void testExpireSession_Phase1_Install() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1); - Install.single(TestApp.A1).commit(); - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1); - Install.single(TestApp.A2).setEnableRollback().setStaged().commit(); + public void testRollbackRebootlessApex() throws Exception { + final String packageName = REBOOTLESS_APEX_NAME; + assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(1); + + // install + TestApp apex1 = new TestApp("TestRebootlessApexV1", packageName, 1, + /* isApex= */ true, "test.rebootless_apex_v1.apex"); + TestApp apex2 = new TestApp("TestRebootlessApexV2", packageName, 2, + /* isApex= */ true, "test.rebootless_apex_v2.apex"); + Install.single(apex2).setEnableRollback(PackageManager.ROLLBACK_DATA_POLICY_RETAIN) + .commit(); + + // verify rollback + assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(2); + RollbackInfo rollback = RollbackUtils.waitForAvailableRollback(packageName); + assertThat(rollback).packagesContainsExactly(Rollback.from(apex2).to(apex1)); + assertThat(rollback).isNotStaged(); + + // rollback + RollbackUtils.rollback(rollback.getRollbackId()); + assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(1); } @Test - public void testExpireSession_Phase2_VerifyInstall() throws Exception { - assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); - RollbackManager rm = RollbackUtils.getRollbackManager(); - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.A); - assertThat(rollback).isNotNull(); - assertThat(rollback).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1)); - assertThat(rollback.isStaged()).isTrue(); + public void testNativeWatchdogTriggersRebootlessApexRollback_Phase1_Install() throws Exception { + assertThat(InstallUtils.getInstalledVersion(REBOOTLESS_APEX_NAME)).isEqualTo(1); + + TestApp apex2 = new TestApp("TestRebootlessApexV2", REBOOTLESS_APEX_NAME, 2, + /* isApex= */ true, "test.rebootless_apex_v2.apex"); + Install.single(apex2).setEnableRollback(PackageManager.ROLLBACK_DATA_POLICY_RETAIN) + .commit(); + Install.single(TestApp.A1).commit(); + Install.single(TestApp.A2).setEnableRollback().commit(); + + RollbackUtils.waitForAvailableRollback(TestApp.A); + RollbackUtils.waitForAvailableRollback(REBOOTLESS_APEX_NAME); } @Test - public void testExpireSession_Phase3_VerifyRollback() throws Exception { - RollbackManager rm = RollbackUtils.getRollbackManager(); - RollbackInfo rollback = getUniqueRollbackInfoForPackage( - rm.getAvailableRollbacks(), TestApp.A); - assertThat(rollback).isNotNull(); + public void testNativeWatchdogTriggersRebootlessApexRollback_Phase2_Verify() throws Exception { + // Check only rebootless apex is rolled back. Other rollbacks should remain unchanged. + assertThat(RollbackUtils.getCommittedRollback(REBOOTLESS_APEX_NAME)).isNotNull(); + assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNotNull(); } @Test diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java index 1aa5c249ff18..05e8bde23008 100644 --- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java +++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java @@ -21,13 +21,9 @@ import static com.android.tests.rollback.host.WatchdogEventLogger.Subject.assert import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; -import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper; -import com.android.ddmlib.Log; -import com.android.tradefed.device.DeviceNotAvailableException; -import com.android.tradefed.device.IFileEntry; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; import com.android.tradefed.util.CommandResult; @@ -40,12 +36,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; -import java.time.Instant; -import java.util.Collections; -import java.util.Date; -import java.util.List; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; /** * Runs the staged rollback tests. @@ -72,17 +63,6 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test"; private static final String TESTAPP_A = "com.android.cts.install.lib.testapp.A"; - private static final String TEST_SUBDIR = "/subdir/"; - - private static final String TEST_FILENAME_1 = "test_file.txt"; - private static final String TEST_STRING_1 = "hello this is a test"; - private static final String TEST_FILENAME_2 = "another_file.txt"; - private static final String TEST_STRING_2 = "this is a different file"; - private static final String TEST_FILENAME_3 = "also.xyz"; - private static final String TEST_STRING_3 = "also\n a\n test\n string"; - private static final String TEST_FILENAME_4 = "one_more.test"; - private static final String TEST_STRING_4 = "once more unto the test"; - private static final String REASON_APP_CRASH = "REASON_APP_CRASH"; private static final String REASON_NATIVE_CRASH = "REASON_NATIVE_CRASH"; @@ -116,7 +96,9 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + "*", - apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + "*"); + apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + "*", + "/system/apex/test.rebootless_apex_v*.apex", + "/data/apex/active/test.apex.rebootless*.apex"); } /** @@ -124,26 +106,26 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { * @param files the paths of files which might contain wildcards */ private void deleteFiles(String... files) throws Exception { - boolean found = false; - for (String file : files) { - CommandResult result = getDevice().executeShellV2Command("ls " + file); - if (result.getStatus() == CommandStatus.SUCCESS) { - found = true; - break; + try { + getDevice().enableAdbRoot(); + boolean found = false; + for (String file : files) { + CommandResult result = getDevice().executeShellV2Command("ls " + file); + if (result.getStatus() == CommandStatus.SUCCESS) { + found = true; + break; + } } - } - if (found) { - try { - getDevice().enableAdbRoot(); + if (found) { getDevice().remountSystemWritable(); for (String file : files) { getDevice().executeShellCommand("rm -rf " + file); } - } finally { - getDevice().disableAdbRoot(); + getDevice().reboot(); } - getDevice().reboot(); + } finally { + getDevice().disableAdbRoot(); } } @@ -153,98 +135,6 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { } /** - * Tests watchdog triggered staged rollbacks involving only apks. - */ - @Test - public void testBadApkOnly() throws Exception { - runPhase("testBadApkOnly_Phase1_Install"); - getDevice().reboot(); - runPhase("testBadApkOnly_Phase2_VerifyInstall"); - - // Launch the app to crash to trigger rollback - startActivity(TESTAPP_A); - // Wait for reboot to happen - waitForDeviceNotAvailable(2, TimeUnit.MINUTES); - - getDevice().waitForDeviceAvailable(); - - runPhase("testBadApkOnly_Phase3_VerifyRollback"); - - assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_APP_CRASH, TESTAPP_A); - assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null); - assertThat(mLogger).eventOccurred(ROLLBACK_SUCCESS, null, null, null); - } - - @Test - public void testNativeWatchdogTriggersRollback() throws Exception { - runPhase("testNativeWatchdogTriggersRollback_Phase1_Install"); - - // Reboot device to activate staged package - getDevice().reboot(); - - runPhase("testNativeWatchdogTriggersRollback_Phase2_VerifyInstall"); - - // crash system_server enough times to trigger a rollback - crashProcess("system_server", NATIVE_CRASHES_THRESHOLD); - - // Rollback should be committed automatically now. - // Give time for rollback to be committed. This could take a while, - // because we need all of the following to happen: - // 1. system_server comes back up and boot completes. - // 2. Rollback health observer detects updatable crashing signal. - // 3. Staged rollback session becomes ready. - // 4. Device actually reboots. - // So we give a generous timeout here. - waitForDeviceNotAvailable(5, TimeUnit.MINUTES); - getDevice().waitForDeviceAvailable(); - - // verify rollback committed - runPhase("testNativeWatchdogTriggersRollback_Phase3_VerifyRollback"); - - assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_NATIVE_CRASH, null); - assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null); - assertThat(mLogger).eventOccurred(ROLLBACK_SUCCESS, null, null, null); - } - - @Test - public void testNativeWatchdogTriggersRollbackForAll() throws Exception { - // This test requires committing multiple staged rollbacks - assumeTrue(isCheckpointSupported()); - - // Install a package with rollback enabled. - runPhase("testNativeWatchdogTriggersRollbackForAll_Phase1_InstallA"); - getDevice().reboot(); - - // Once previous staged install is applied, install another package - runPhase("testNativeWatchdogTriggersRollbackForAll_Phase2_InstallB"); - getDevice().reboot(); - - // Verify the new staged install has also been applied successfully. - runPhase("testNativeWatchdogTriggersRollbackForAll_Phase3_VerifyInstall"); - - // crash system_server enough times to trigger a rollback - crashProcess("system_server", NATIVE_CRASHES_THRESHOLD); - - // Rollback should be committed automatically now. - // Give time for rollback to be committed. This could take a while, - // because we need all of the following to happen: - // 1. system_server comes back up and boot completes. - // 2. Rollback health observer detects updatable crashing signal. - // 3. Staged rollback session becomes ready. - // 4. Device actually reboots. - // So we give a generous timeout here. - waitForDeviceNotAvailable(5, TimeUnit.MINUTES); - getDevice().waitForDeviceAvailable(); - - // verify all available rollbacks have been committed - runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4_VerifyRollback"); - - assertThat(mLogger).eventOccurred(ROLLBACK_INITIATE, null, REASON_NATIVE_CRASH, null); - assertThat(mLogger).eventOccurred(ROLLBACK_BOOT_TRIGGERED, null, null, null); - assertThat(mLogger).eventOccurred(ROLLBACK_SUCCESS, null, null, null); - } - - /** * Tests rolling back user data where there are multiple rollbacks for that package. */ @Test @@ -267,44 +157,12 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { runPhase("testRollbackAllowlistedApp_Phase2_VerifyInstall"); } - @Test - public void testRollbackDataPolicy() throws Exception { - List<String> before = getSnapshotDirectories("/data/misc_ce/0/rollback"); - - runPhase("testRollbackDataPolicy_Phase1_Install"); - getDevice().reboot(); - runPhase("testRollbackDataPolicy_Phase2_Rollback"); - getDevice().reboot(); - runPhase("testRollbackDataPolicy_Phase3_VerifyRollback"); - - // Verify snapshots are deleted after restoration - List<String> after = getSnapshotDirectories("/data/misc_ce/0/rollback"); - // Only check directories newly created during the test - after.removeAll(before); - // There should be only one /data/misc_ce/0/rollback/<rollbackId> created during test - assertThat(after).hasSize(1); - assertDirectoryIsEmpty(after.get(0)); - } - - /** - * Tests that userdata of apk-in-apex is restored when apex is rolled back. - */ - @Test - public void testRollbackApexWithApk() throws Exception { - pushTestApex(); - runPhase("testRollbackApexWithApk_Phase1_Install"); - getDevice().reboot(); - runPhase("testRollbackApexWithApk_Phase2_Rollback"); - getDevice().reboot(); - runPhase("testRollbackApexWithApk_Phase3_VerifyRollback"); - } - /** * Tests that RollbackPackageHealthObserver is observing apk-in-apex. */ @Test public void testRollbackApexWithApkCrashing() throws Exception { - pushTestApex(); + pushTestApex(APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"); // Install an apex with apk that crashes runPhase("testRollbackApexWithApkCrashing_Phase1_Install"); @@ -325,239 +183,24 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { } /** - * Tests that data in DE_sys apex data directory is restored when apex is rolled back. - */ - @Test - public void testRollbackApexDataDirectories_DeSys() throws Exception { - List<String> before = getSnapshotDirectories("/data/misc/apexrollback"); - pushTestApex(); - - // Push files to apex data directory - String oldFilePath1 = apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + "/" + TEST_FILENAME_1; - String oldFilePath2 = - apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + TEST_SUBDIR + TEST_FILENAME_2; - runAsRoot(() -> { - pushString(TEST_STRING_1, oldFilePath1); - pushString(TEST_STRING_2, oldFilePath2); - }); - - // Install new version of the APEX with rollback enabled - runPhase("testRollbackApexDataDirectories_Phase1_Install"); - getDevice().reboot(); - - // Replace files in data directory - String newFilePath3 = apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + "/" + TEST_FILENAME_3; - String newFilePath4 = - apexDataDirDeSys(APK_IN_APEX_TESTAPEX_NAME) + TEST_SUBDIR + TEST_FILENAME_4; - runAsRoot(() -> { - getDevice().deleteFile(oldFilePath1); - getDevice().deleteFile(oldFilePath2); - pushString(TEST_STRING_3, newFilePath3); - pushString(TEST_STRING_4, newFilePath4); - }); - - // Roll back the APEX - runPhase("testRollbackApexDataDirectories_Phase2_Rollback"); - getDevice().reboot(); - - // Verify that old files have been restored and new files are gone - runAsRoot(() -> { - assertFileContents(TEST_STRING_1, oldFilePath1); - assertFileContents(TEST_STRING_2, oldFilePath2); - assertFileNotExists(newFilePath3); - assertFileNotExists(newFilePath4); - }); - - // Verify snapshots are deleted after restoration - List<String> after = getSnapshotDirectories("/data/misc/apexrollback"); - // Only check directories newly created during the test - after.removeAll(before); - // There should be only one /data/misc/apexrollback/<rollbackId> created during test - assertThat(after).hasSize(1); - assertDirectoryIsEmpty(after.get(0)); - } - - /** - * Tests that data in DE (user) apex data directory is restored when apex is rolled back. + * Tests rollback is supported correctly for rebootless apex */ @Test - public void testRollbackApexDataDirectories_DeUser() throws Exception { - List<String> before = getSnapshotDirectories("/data/misc_de/0/apexrollback"); - pushTestApex(); - - // Push files to apex data directory - String oldFilePath1 = apexDataDirDeUser( - APK_IN_APEX_TESTAPEX_NAME, 0) + "/" + TEST_FILENAME_1; - String oldFilePath2 = - apexDataDirDeUser(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_2; - runAsRoot(() -> { - pushString(TEST_STRING_1, oldFilePath1); - pushString(TEST_STRING_2, oldFilePath2); - }); - - // Install new version of the APEX with rollback enabled - runPhase("testRollbackApexDataDirectories_Phase1_Install"); - getDevice().reboot(); - - // Replace files in data directory - String newFilePath3 = - apexDataDirDeUser(APK_IN_APEX_TESTAPEX_NAME, 0) + "/" + TEST_FILENAME_3; - String newFilePath4 = - apexDataDirDeUser(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_4; - runAsRoot(() -> { - getDevice().deleteFile(oldFilePath1); - getDevice().deleteFile(oldFilePath2); - pushString(TEST_STRING_3, newFilePath3); - pushString(TEST_STRING_4, newFilePath4); - }); - - // Roll back the APEX - runPhase("testRollbackApexDataDirectories_Phase2_Rollback"); - getDevice().reboot(); - - // Verify that old files have been restored and new files are gone - runAsRoot(() -> { - assertFileContents(TEST_STRING_1, oldFilePath1); - assertFileContents(TEST_STRING_2, oldFilePath2); - assertFileNotExists(newFilePath3); - assertFileNotExists(newFilePath4); - }); - - // Verify snapshots are deleted after restoration - List<String> after = getSnapshotDirectories("/data/misc_de/0/apexrollback"); - // Only check directories newly created during the test - after.removeAll(before); - // There should be only one /data/misc_de/0/apexrollback/<rollbackId> created during test - assertThat(after).hasSize(1); - assertDirectoryIsEmpty(after.get(0)); - } - - /** - * Tests that data in CE apex data directory is restored when apex is rolled back. - */ - @Test - public void testRollbackApexDataDirectories_Ce() throws Exception { - List<String> before = getSnapshotDirectories("/data/misc_ce/0/apexrollback"); - pushTestApex(); - - // Push files to apex data directory - String oldFilePath1 = apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + "/" + TEST_FILENAME_1; - String oldFilePath2 = - apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_2; - runAsRoot(() -> { - pushString(TEST_STRING_1, oldFilePath1); - pushString(TEST_STRING_2, oldFilePath2); - }); - - // Install new version of the APEX with rollback enabled - runPhase("testRollbackApexDataDirectories_Phase1_Install"); - getDevice().reboot(); - - // Replace files in data directory - String newFilePath3 = apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + "/" + TEST_FILENAME_3; - String newFilePath4 = - apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_4; - runAsRoot(() -> { - getDevice().deleteFile(oldFilePath1); - getDevice().deleteFile(oldFilePath2); - pushString(TEST_STRING_3, newFilePath3); - pushString(TEST_STRING_4, newFilePath4); - }); - - // Roll back the APEX - runPhase("testRollbackApexDataDirectories_Phase2_Rollback"); - getDevice().reboot(); - - // Verify that old files have been restored and new files are gone - runAsRoot(() -> { - assertFileContents(TEST_STRING_1, oldFilePath1); - assertFileContents(TEST_STRING_2, oldFilePath2); - assertFileNotExists(newFilePath3); - assertFileNotExists(newFilePath4); - }); - - // Verify snapshots are deleted after restoration - List<String> after = getSnapshotDirectories("/data/misc_ce/0/apexrollback"); - // Only check directories newly created during the test - after.removeAll(before); - // There should be only one /data/misc_ce/0/apexrollback/<rollbackId> created during test - assertThat(after).hasSize(1); - assertDirectoryIsEmpty(after.get(0)); + public void testRollbackRebootlessApex() throws Exception { + pushTestApex("test.rebootless_apex_v1.apex"); + runPhase("testRollbackRebootlessApex"); } /** - * Tests that data in DE apk data directory is restored when apk is rolled back. + * Tests only rebootless apex (if any) is rolled back when native crash happens */ @Test - public void testRollbackApkDataDirectories_De() throws Exception { - // Install version 1 of TESTAPP_A - runPhase("testRollbackApkDataDirectories_Phase1_InstallV1"); - - // Push files to apk data directory - String oldFilePath1 = apkDataDirDe(TESTAPP_A, 0) + "/" + TEST_FILENAME_1; - String oldFilePath2 = apkDataDirDe(TESTAPP_A, 0) + TEST_SUBDIR + TEST_FILENAME_2; - runAsRoot(() -> { - pushString(TEST_STRING_1, oldFilePath1); - pushString(TEST_STRING_2, oldFilePath2); - }); - - // Install version 2 of TESTAPP_A with rollback enabled - runPhase("testRollbackApkDataDirectories_Phase2_InstallV2"); - getDevice().reboot(); - - // Replace files in data directory - String newFilePath3 = apkDataDirDe(TESTAPP_A, 0) + "/" + TEST_FILENAME_3; - String newFilePath4 = apkDataDirDe(TESTAPP_A, 0) + TEST_SUBDIR + TEST_FILENAME_4; - runAsRoot(() -> { - getDevice().deleteFile(oldFilePath1); - getDevice().deleteFile(oldFilePath2); - pushString(TEST_STRING_3, newFilePath3); - pushString(TEST_STRING_4, newFilePath4); - }); - - // Roll back the APK - runPhase("testRollbackApkDataDirectories_Phase3_Rollback"); - getDevice().reboot(); - - // Verify that old files have been restored and new files are gone - runAsRoot(() -> { - assertFileContents(TEST_STRING_1, oldFilePath1); - assertFileContents(TEST_STRING_2, oldFilePath2); - assertFileNotExists(newFilePath3); - assertFileNotExists(newFilePath4); - }); - } - - @Test - public void testExpireApexRollback() throws Exception { - List<String> before = getSnapshotDirectories("/data/misc_ce/0/apexrollback"); - pushTestApex(); - - // Push files to apex data directory - String oldFilePath1 = apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + "/" + TEST_FILENAME_1; - String oldFilePath2 = - apexDataDirCe(APK_IN_APEX_TESTAPEX_NAME, 0) + TEST_SUBDIR + TEST_FILENAME_2; - runAsRoot(() -> { - pushString(TEST_STRING_1, oldFilePath1); - pushString(TEST_STRING_2, oldFilePath2); - }); - - // Install new version of the APEX with rollback enabled - runPhase("testRollbackApexDataDirectories_Phase1_Install"); - getDevice().reboot(); - - List<String> after = getSnapshotDirectories("/data/misc_ce/0/apexrollback"); - // Only check directories newly created during the test - after.removeAll(before); - // There should be only one /data/misc_ce/0/apexrollback/<rollbackId> created during test - assertThat(after).hasSize(1); - // Expire all rollbacks and check CE snapshot directories are deleted - runPhase("expireRollbacks"); - runAsRoot(() -> { - for (String dir : after) { - assertFileNotExists(dir); - } - }); + public void testNativeWatchdogTriggersRebootlessApexRollback() throws Exception { + pushTestApex("test.rebootless_apex_v1.apex"); + runPhase("testNativeWatchdogTriggersRebootlessApexRollback_Phase1_Install"); + crashProcess("system_server", NATIVE_CRASHES_THRESHOLD); + getDevice().waitForDeviceAvailable(); + runPhase("testNativeWatchdogTriggersRebootlessApexRollback_Phase2_Verify"); } /** @@ -584,30 +227,8 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { runPhase("testWatchdogMonitorsAcrossReboots_Phase3_VerifyRollback"); } - /** - * Tests an available rollback shouldn't be deleted when its session expires. - */ - @Test - public void testExpireSession() throws Exception { - runPhase("testExpireSession_Phase1_Install"); - getDevice().reboot(); - runPhase("testExpireSession_Phase2_VerifyInstall"); - - // Advance system clock by 7 days to expire the staged session - Instant t1 = Instant.ofEpochMilli(getDevice().getDeviceDate()); - Instant t2 = t1.plusMillis(TimeUnit.DAYS.toMillis(7)); - runAsRoot(() -> getDevice().setDate(Date.from(t2))); - - // Somehow we need to wait for a while before reboot. Otherwise the change to the - // system clock will be reset after reboot. - Thread.sleep(3000); - getDevice().reboot(); - runPhase("testExpireSession_Phase3_VerifyRollback"); - } - - private void pushTestApex() throws Exception { + private void pushTestApex(String fileName) throws Exception { CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild()); - final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex"; final File apex = buildHelper.getTestFile(fileName); try { getDevice().enableAdbRoot(); @@ -619,101 +240,20 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { getDevice().reboot(); } - private void pushString(String contents, String path) throws Exception { - assertWithMessage("Failed to push file to device, content=%s path=%s", contents, path) - .that(getDevice().pushString(contents, path)).isTrue(); - } - - private void assertFileContents(String expectedContents, String path) throws Exception { - String actualContents = getDevice().pullFileContents(path); - assertWithMessage("Failed to retrieve file=%s", path).that(actualContents).isNotNull(); - assertWithMessage("Mismatched file contents, path=%s", path) - .that(actualContents).isEqualTo(expectedContents); - } - - private void assertFileNotExists(String path) throws Exception { - assertWithMessage("File shouldn't exist, path=%s", path) - .that(getDevice().getFileEntry(path)).isNull(); - } - private static String apexDataDirDeSys(String apexName) { return String.format("/data/misc/apexdata/%s", apexName); } - private static String apexDataDirDeUser(String apexName, int userId) { - return String.format("/data/misc_de/%d/apexdata/%s", userId, apexName); - } - private static String apexDataDirCe(String apexName, int userId) { return String.format("/data/misc_ce/%d/apexdata/%s", userId, apexName); } - private static String apkDataDirDe(String apexName, int userId) { - return String.format("/data/user_de/%d/%s", userId, apexName); - } - - private List<String> getSnapshotDirectories(String baseDir) throws Exception { - try { - getDevice().enableAdbRoot(); - IFileEntry f = getDevice().getFileEntry(baseDir); - if (f == null) { - Log.d(TAG, "baseDir doesn't exist: " + baseDir); - return Collections.EMPTY_LIST; - } - List<String> list = f.getChildren(false) - .stream().filter(entry -> entry.getName().matches("\\d+(-prerestore)?")) - .map(entry -> entry.getFullPath()) - .collect(Collectors.toList()); - Log.d(TAG, "getSnapshotDirectories=" + list); - return list; - } finally { - getDevice().disableAdbRoot(); - } - } - - private void assertDirectoryIsEmpty(String path) throws Exception { - try { - getDevice().enableAdbRoot(); - IFileEntry file = getDevice().getFileEntry(path); - assertWithMessage("Not a directory: " + path).that(file.isDirectory()).isTrue(); - assertWithMessage("Directory not empty: " + path) - .that(file.getChildren(false)).isEmpty(); - } catch (DeviceNotAvailableException e) { - fail("Can't access directory: " + path); - } finally { - getDevice().disableAdbRoot(); - } - } - private void startActivity(String packageName) throws Exception { String cmd = "am start -S -a android.intent.action.MAIN " + "-c android.intent.category.LAUNCHER " + packageName; getDevice().executeShellCommand(cmd); } - private void crashProcess(String processName, int numberOfCrashes) throws Exception { - String pid = ""; - String lastPid = "invalid"; - for (int i = 0; i < numberOfCrashes; ++i) { - // This condition makes sure before we kill the process, the process is running AND - // the last crash was finished. - while ("".equals(pid) || lastPid.equals(pid)) { - pid = getDevice().executeShellCommand("pidof " + processName); - } - getDevice().executeShellCommand("kill " + pid); - lastPid = pid; - } - } - - private boolean isCheckpointSupported() throws Exception { - try { - runPhase("isCheckpointSupported"); - return true; - } catch (AssertionError ignore) { - return false; - } - } - /** * True if this build has mainline modules installed. */ @@ -726,17 +266,17 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { } } - @FunctionalInterface - private interface ExceptionalRunnable { - void run() throws Exception; - } - - private void runAsRoot(ExceptionalRunnable runnable) throws Exception { - try { - getDevice().enableAdbRoot(); - runnable.run(); - } finally { - getDevice().disableAdbRoot(); + private void crashProcess(String processName, int numberOfCrashes) throws Exception { + String pid = ""; + String lastPid = "invalid"; + for (int i = 0; i < numberOfCrashes; ++i) { + // This condition makes sure before we kill the process, the process is running AND + // the last crash was finished. + while ("".equals(pid) || lastPid.equals(pid)) { + pid = getDevice().executeShellCommand("pidof " + processName); + } + getDevice().executeShellCommand("kill " + pid); + lastPid = pid; } } } diff --git a/tests/SoundTriggerTestApp/res/layout/main.xml b/tests/SoundTriggerTestApp/res/layout/main.xml index 2c6c8d7cae20..1381c0a43c30 100644 --- a/tests/SoundTriggerTestApp/res/layout/main.xml +++ b/tests/SoundTriggerTestApp/res/layout/main.xml @@ -73,6 +73,14 @@ android:text="@string/play_trigger" android:onClick="onPlayTriggerButtonClicked" android:padding="20dp" /> + + <Button + android:id="@+id/get_state_id" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/get_model_state" + android:onClick="onGetModelStateButtonClicked" + android:padding="20dp" /> </LinearLayout> <LinearLayout diff --git a/tests/SoundTriggerTestApp/res/values/strings.xml b/tests/SoundTriggerTestApp/res/values/strings.xml index c48b64884c5e..adb0fcf8e185 100644 --- a/tests/SoundTriggerTestApp/res/values/strings.xml +++ b/tests/SoundTriggerTestApp/res/values/strings.xml @@ -22,6 +22,7 @@ <string name="start_recog">Start</string> <string name="stop_recog">Stop</string> <string name="play_trigger">Play Trigger Audio</string> + <string name="get_model_state">Get State</string> <string name="capture">Capture Audio</string> <string name="stop_capture">Stop Capturing Audio</string> <string name="play_capture">Play Captured Audio</string> diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java index c3c4cf556986..72aa38dc7e4b 100644 --- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java +++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java @@ -257,6 +257,14 @@ public class SoundTriggerTestActivity extends Activity implements SoundTriggerTe } } + public synchronized void onGetModelStateButtonClicked(View v) { + if (mService == null) { + Log.e(TAG, "Can't get model state: not bound to SoundTriggerTestService"); + } else { + mService.getModelState(mSelectedModelUuid); + } + } + public synchronized void onCaptureAudioCheckboxClicked(View v) { // See if we have the right permissions if (!mService.hasMicrophonePermission()) { diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java index 380e29984c63..6d4ffcff7d45 100644 --- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java +++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java @@ -23,6 +23,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; +import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; import android.media.AudioAttributes; import android.media.AudioFormat; import android.media.AudioManager; @@ -46,6 +48,7 @@ import java.util.Properties; import java.util.Random; import java.util.UUID; + public class SoundTriggerTestService extends Service { private static final String TAG = "SoundTriggerTestSrv"; private static final String INTENT_ACTION = "com.android.intent.action.MANAGE_SOUND_TRIGGER"; @@ -57,6 +60,8 @@ public class SoundTriggerTestService extends Service { private Random mRandom; private UserActivity mUserActivity; + private static int captureCount; + public interface UserActivity { void addModel(UUID modelUuid, String state); void setModelState(UUID modelUuid, String state); @@ -131,6 +136,8 @@ public class SoundTriggerTestService extends Service { } else if (command.equals("set_capture_timeout")) { setCaptureAudioTimeout(getModelUuidFromIntent(intent), intent.getIntExtra("timeout", 5000)); + } else if (command.equals("get_model_state")) { + getModelState(getModelUuidFromIntent(intent)); } else { Log.e(TAG, "Unknown command '" + command + "'"); } @@ -432,6 +439,17 @@ public class SoundTriggerTestService extends Service { return modelInfo != null && modelInfo.captureAudioTrack != null; } + public synchronized void getModelState(UUID modelUuid) { + ModelInfo modelInfo = mModelInfoMap.get(modelUuid); + if (modelInfo == null) { + postError("Could not find model for: " + modelUuid.toString()); + return; + } + int status = mSoundTriggerUtil.getModelState(modelUuid); + postMessage("GetModelState for: " + modelInfo.name + " returns: " + + status); + } + private void loadModelsInDataDir() { // Load all the models in the data dir. boolean loadedModel = false; @@ -527,18 +545,29 @@ public class SoundTriggerTestService extends Service { } } + private class CaptureAudioRecorder implements Runnable { private final ModelInfo mModelInfo; + + // EventPayload and RecognitionEvent are equivalant. Only one will be non-null. private final SoundTriggerDetector.EventPayload mEvent; + private final RecognitionEvent mRecognitionEvent; public CaptureAudioRecorder(ModelInfo modelInfo, SoundTriggerDetector.EventPayload event) { mModelInfo = modelInfo; mEvent = event; + mRecognitionEvent = null; + } + + public CaptureAudioRecorder(ModelInfo modelInfo, RecognitionEvent event) { + mModelInfo = modelInfo; + mEvent = null; + mRecognitionEvent = event; } @Override public void run() { - AudioFormat format = mEvent.getCaptureAudioFormat(); + AudioFormat format = getAudioFormat(); if (format == null) { postErrorToast("No audio format in recognition event."); return; @@ -600,18 +629,21 @@ public class SoundTriggerTestService extends Service { } audioRecord = new AudioRecord(attributes, format, bytesRequired, - mEvent.getCaptureSession()); + getCaptureSession()); byte[] buffer = new byte[bytesRequired]; // Create a file so we can save the output data there for analysis later. FileOutputStream fos = null; try { - fos = new FileOutputStream( new File( - getFilesDir() + File.separator - + mModelInfo.name.replace(' ', '_') - + "_capture_" + format.getChannelCount() + "ch_" - + format.getSampleRate() + "hz_" + encoding + ".pcm")); + File file = new File( + getFilesDir() + File.separator + + mModelInfo.name.replace(' ', '_') + + "_capture_" + format.getChannelCount() + "ch_" + + format.getSampleRate() + "hz_" + encoding + + "_" + (++captureCount) + ".pcm"); + Log.i(TAG, "Writing audio to: " + file); + fos = new FileOutputStream(file); } catch (IOException e) { Log.e(TAG, "Failed to open output for saving PCM data", e); postErrorToast("Failed to open output for saving PCM data: " @@ -635,6 +667,10 @@ public class SoundTriggerTestService extends Service { bytesRequired -= bytesRead; } audioRecord.stop(); + if (fos != null) { + fos.flush(); + fos.close(); + } } catch (Exception e) { Log.e(TAG, "Error recording trigger audio", e); postErrorToast("Error recording trigger audio: " + e.getMessage()); @@ -651,6 +687,26 @@ public class SoundTriggerTestService extends Service { setModelState(mModelInfo, "Recording finished"); } } + + private AudioFormat getAudioFormat() { + if (mEvent != null) { + return mEvent.getCaptureAudioFormat(); + } + if (mRecognitionEvent != null) { + return mRecognitionEvent.captureFormat; + } + return null; + } + + private int getCaptureSession() { + if (mEvent != null) { + return mEvent.getCaptureSession(); + } + if (mRecognitionEvent != null) { + return mRecognitionEvent.captureSession; + } + return 0; + } } // Implementation of SoundTriggerDetector.Callback. diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java index cfe8c855ac81..996a78f2e42a 100644 --- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java +++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java @@ -18,6 +18,8 @@ package com.android.test.soundtrigger; import android.annotation.Nullable; import android.content.Context; +import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; +import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; import android.media.soundtrigger.SoundTriggerDetector; import android.media.soundtrigger.SoundTriggerManager; import android.os.RemoteException; @@ -27,6 +29,7 @@ import android.util.Log; import com.android.internal.app.ISoundTriggerService; +import java.lang.reflect.Method; import java.lang.RuntimeException; import java.util.UUID; @@ -50,13 +53,31 @@ public class SoundTriggerUtil { * The sound model must contain a valid UUID. * * @param soundModel The sound model to add/update. + * @return The true if the model was loaded successfully, false otherwise. */ public boolean addOrUpdateSoundModel(SoundTriggerManager.Model soundModel) { if (soundModel == null) { throw new RuntimeException("Bad sound model"); } mSoundTriggerManager.updateModel(soundModel); - return true; + // TODO: call loadSoundModel in the soundtrigger manager updateModel method + // instead of here. It is needed to keep soundtrigger manager internal + // state consistent. + return mSoundTriggerManager + .loadSoundModel(getGenericSoundModel(soundModel)) == 0; + } + + private GenericSoundModel getGenericSoundModel( + SoundTriggerManager.Model soundModel) { + try { + Method method = SoundTriggerManager.Model.class + .getDeclaredMethod("getGenericSoundModel"); + method.setAccessible(true); + return (GenericSoundModel) method.invoke(soundModel); + } catch (ReflectiveOperationException e) { + Log.e(TAG, "Failed to getGenericSoundModel: " + soundModel, e); + return null; + } } /** @@ -92,6 +113,16 @@ public class SoundTriggerUtil { return true; } + /** + * Get the current model state + * + * @param modelId The model ID to look-up the sound model for. + * @return 0 if the call succeeds, or an error code if it fails. + */ + public int getModelState(UUID modelId) { + return mSoundTriggerManager.getModelState(modelId); + } + public SoundTriggerDetector createSoundTriggerDetector(UUID modelId, SoundTriggerDetector.Callback callback) { return mSoundTriggerManager.createSoundTriggerDetector(modelId, callback, null); diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp index a5852b52ff19..7906f0973ae0 100644 --- a/tests/StagedInstallTest/Android.bp +++ b/tests/StagedInstallTest/Android.bp @@ -61,6 +61,7 @@ java_test_host { ":StagedInstallTestApexV2_WrongSha", ":TestAppAv1", ":test.rebootless_apex_v1", + ":test.rebootless_apex_v2", ], test_suites: ["general-tests"], test_config: "StagedInstallInternalTest.xml", 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 f0ab63eb41b5..b21d7b5d1f4a 100644 --- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java @@ -44,6 +44,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.cts.install.lib.Install; import com.android.cts.install.lib.InstallUtils; import com.android.cts.install.lib.TestApp; +import com.android.cts.install.lib.Uninstall; import org.junit.After; import org.junit.Before; @@ -108,6 +109,7 @@ public class StagedInstallInternalTest { @Test public void cleanUp() throws Exception { Files.deleteIfExists(mTestStateFile.toPath()); + Uninstall.packages(TestApp.A, TestApp.B); } @Test @@ -157,8 +159,18 @@ public class StagedInstallInternalTest { @Test public void testStagedSessionShouldCleanUpOnVerificationFailure() throws Exception { + // APEX verification InstallUtils.commitExpectingFailure(AssertionError.class, "apexd verification failed", Install.single(APEX_WRONG_SHA_V2).setStaged()); + InstallUtils.commitExpectingFailure(AssertionError.class, "apexd verification failed", + Install.multi(APEX_WRONG_SHA_V2, TestApp.A1).setStaged()); + // APK verification + Install.single(TestApp.A2).commit(); + assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2); + InstallUtils.commitExpectingFailure(AssertionError.class, "Downgrade detected", + Install.single(TestApp.A1).setStaged()); + InstallUtils.commitExpectingFailure(AssertionError.class, "Downgrade detected", + Install.multi(TestApp.A1, TestApp.B1).setStaged()); } @Test @@ -176,6 +188,12 @@ public class StagedInstallInternalTest { } @Test + public void testStagedSessionShouldCleanUpOnOnSuccessMultiPackage_Commit() throws Exception { + int sessionId = Install.multi(TestApp.A1, TestApp.Apex2).setStaged().commit(); + storeSessionId(sessionId); + } + + @Test public void testStagedInstallationShouldCleanUpOnValidationFailure() throws Exception { InstallUtils.commitExpectingFailure(AssertionError.class, "INSTALL_FAILED_INVALID_APK", Install.single(TestApp.AIncompleteSplit).setStaged()); @@ -483,6 +501,18 @@ public class StagedInstallInternalTest { assertThat(captor.getValue().stagedApexModuleNames).hasLength(0); } + @Test + public void testRebootlessDowngrade() throws Exception { + final String packageName = "test.apex.rebootless"; + assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(2); + TestApp apex1 = new TestApp("TestRebootlessApexV1", packageName, 1, + /* isApex= */ true, "test.rebootless_apex_v1.apex"); + InstallUtils.commitExpectingFailure(AssertionError.class, + "INSTALL_FAILED_VERSION_DOWNGRADE", Install.single(apex1)); + Install.single(apex1).setRequestDowngrade().commit(); + assertThat(InstallUtils.getInstalledVersion(packageName)).isEqualTo(1); + } + 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 cd0078363c4b..f06fa81e790c 100644 --- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java @@ -92,7 +92,7 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { deleteFiles("/system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex", "/data/apex/active/" + SHIM_APEX_PACKAGE_NAME + "*.apex", - "/system/apex/test.rebootless_apex_v1.apex", + "/system/apex/test.rebootless_apex_v*.apex", "/data/apex/active/test.apex.rebootless*.apex", TEST_VENDOR_APEX_ALLOW_LIST); } @@ -112,6 +112,10 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { * @param files the paths of files which might contain wildcards */ private void deleteFiles(String... files) throws Exception { + if (!getDevice().isAdbRoot()) { + getDevice().enableAdbRoot(); + } + boolean found = false; for (String file : files) { CommandResult result = getDevice().executeShellV2Command("ls " + file); @@ -122,9 +126,6 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { } if (found) { - if (!getDevice().isAdbRoot()) { - getDevice().enableAdbRoot(); - } getDevice().remountSystemWritable(); for (String file : files) { getDevice().executeShellCommand("rm -rf " + file); @@ -301,6 +302,18 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { } @Test + @LargeTest + public void testStagedSessionShouldCleanUpOnOnSuccessMultiPackage() throws Exception { + List<String> before = getStagingDirectories(); + runPhase("testStagedSessionShouldCleanUpOnOnSuccessMultiPackage_Commit"); + assertThat(getStagingDirectories()).isNotEqualTo(before); + getDevice().reboot(); + runPhase("testStagedSessionShouldCleanUpOnOnSuccess_Verify"); + List<String> after = getStagingDirectories(); + assertThat(after).isEqualTo(before); + } + + @Test public void testStagedInstallationShouldCleanUpOnValidationFailure() throws Exception { List<String> before = getStagingDirectories(); runPhase("testStagedInstallationShouldCleanUpOnValidationFailure"); @@ -507,6 +520,13 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test { runPhase("testStagedApexObserver"); } + @Test + public void testRebootlessDowngrade() throws Exception { + pushTestApex("test.rebootless_apex_v2.apex"); + getDevice().reboot(); + runPhase("testRebootlessDowngrade"); + } + private List<String> getStagingDirectories() throws DeviceNotAvailableException { String baseDir = "/data/app-staging"; try { diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp index e07fbbf7a1c1..c59a41ee0cd3 100644 --- a/tests/UpdatableSystemFontTest/Android.bp +++ b/tests/UpdatableSystemFontTest/Android.bp @@ -37,15 +37,19 @@ android_test { "vts", ], data: [ - ":NotoColorEmojiTtf", + ":NotoSerif-Regular.ttf", + ":NotoSerif-Bold.ttf", ":UpdatableSystemFontTestCertDer", - ":UpdatableSystemFontTestNotoColorEmojiTtfFsvSig", - ":UpdatableSystemFontTestNotoColorEmojiV0Ttf", - ":UpdatableSystemFontTestNotoColorEmojiV0TtfFsvSig", - ":UpdatableSystemFontTestNotoColorEmojiVPlus1Ttf", - ":UpdatableSystemFontTestNotoColorEmojiVPlus1TtfFsvSig", - ":UpdatableSystemFontTestNotoColorEmojiVPlus2Ttf", - ":UpdatableSystemFontTestNotoColorEmojiVPlus2TtfFsvSig", + ":UpdatableSystemFontTest_NotoColorEmoji.ttf", + ":UpdatableSystemFontTest_NotoColorEmoji.sig", + ":UpdatableSystemFontTest_NotoColorEmojiV0.ttf", + ":UpdatableSystemFontTest_NotoColorEmojiV0.sig", + ":UpdatableSystemFontTest_NotoColorEmojiVPlus1.ttf", + ":UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig", + ":UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf", + ":UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig", + ":UpdatableSystemFontTest_NotoSerif-Regular.sig", + ":UpdatableSystemFontTest_NotoSerif-Bold.sig", ], sdk_version: "test_current", } diff --git a/tests/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml index 4f6487e7e953..6effa7bd0a50 100644 --- a/tests/UpdatableSystemFontTest/AndroidTest.xml +++ b/tests/UpdatableSystemFontTest/AndroidTest.xml @@ -29,13 +29,17 @@ <option name="cleanup" value="true" /> <option name="push" value="UpdatableSystemFontTestCert.der->/data/local/tmp/UpdatableSystemFontTestCert.der" /> <option name="push" value="NotoColorEmoji.ttf->/data/local/tmp/NotoColorEmoji.ttf" /> - <option name="push" value="UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig" /> - <option name="push" value="UpdatableSystemFontTestNotoColorEmojiV0.ttf->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV0.ttf" /> - <option name="push" value="UpdatableSystemFontTestNotoColorEmojiV0.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV0.ttf.fsv_sig" /> - <option name="push" value="UpdatableSystemFontTestNotoColorEmojiVPlus1.ttf->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus1.ttf" /> - <option name="push" value="UpdatableSystemFontTestNotoColorEmojiVPlus1.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus1.ttf.fsv_sig" /> - <option name="push" value="UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf" /> - <option name="push" value="UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf.fsv_sig" /> + <option name="push" value="NotoSerif-Regular.ttf->/data/local/tmp/NotoSerif-Regular.ttf" /> + <option name="push" value="NotoSerif-Bold.ttf->/data/local/tmp/NotoSerif-Bold.ttf" /> + <option name="push" value="UpdatableSystemFontTest_NotoSerif-Regular.sig->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Regular.sig" /> + <option name="push" value="UpdatableSystemFontTest_NotoSerif-Bold.sig->/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Bold.sig" /> + <option name="push" value="UpdatableSystemFontTest_NotoColorEmoji.sig->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmoji.sig" /> + <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiV0.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiV0.ttf" /> + <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiV0.sig->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiV0.sig" /> + <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiVPlus1.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus1.ttf" /> + <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig" /> + <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf" /> + <option name="push" value="UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig->/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig" /> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest"> diff --git a/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/src/com/android/emojirenderingtestapp/EmojiRenderingTestActivity.java b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/src/com/android/emojirenderingtestapp/EmojiRenderingTestActivity.java index 947e9c2ff56a..a8c27fb0f116 100644 --- a/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/src/com/android/emojirenderingtestapp/EmojiRenderingTestActivity.java +++ b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/src/com/android/emojirenderingtestapp/EmojiRenderingTestActivity.java @@ -20,6 +20,7 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import android.app.Activity; +import android.graphics.Typeface; import android.os.Bundle; import android.widget.LinearLayout; import android.widget.TextView; @@ -27,14 +28,20 @@ import android.widget.TextView; /** Test app to render an emoji. */ public class EmojiRenderingTestActivity extends Activity { + private static final String TEST_NOTO_SERIF = "test-noto-serif"; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LinearLayout container = new LinearLayout(this); container.setOrientation(LinearLayout.VERTICAL); - TextView textView = new TextView(this); - textView.setText("\uD83E\uDD72"); // 🥲 - container.addView(textView, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); + TextView emojiTextView = new TextView(this); + emojiTextView.setText("\uD83E\uDD72"); // 🥲 + container.addView(emojiTextView, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); + TextView serifTextView = new TextView(this); + serifTextView.setTypeface(Typeface.create(TEST_NOTO_SERIF, Typeface.NORMAL)); + serifTextView.setText(TEST_NOTO_SERIF); + container.addView(serifTextView, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); setContentView(container); } } diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java index 6bd07d0a84fd..87fda0d220e5 100644 --- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java +++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java @@ -16,6 +16,9 @@ package com.android.updatablesystemfont; +import static android.graphics.fonts.FontStyle.FONT_SLANT_UPRIGHT; +import static android.graphics.fonts.FontStyle.FONT_WEIGHT_BOLD; +import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL; import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; import static com.google.common.truth.Truth.assertThat; @@ -30,6 +33,7 @@ import android.content.Context; import android.graphics.fonts.FontFamilyUpdateRequest; import android.graphics.fonts.FontFileUpdateRequest; import android.graphics.fonts.FontManager; +import android.graphics.fonts.FontStyle; import android.os.ParcelFileDescriptor; import android.platform.test.annotations.RootPermissionTest; import android.security.FileIntegrityManager; @@ -77,31 +81,45 @@ public class UpdatableSystemFontTest { private static final String SYSTEM_FONTS_DIR = "/system/fonts/"; private static final String DATA_FONTS_DIR = "/data/fonts/files/"; private static final String CERT_PATH = "/data/local/tmp/UpdatableSystemFontTestCert.der"; - private static final String NOTO_COLOR_EMOJI_POSTSCRIPT_NAME = "NotoColorEmoji"; - private static final String ORIGINAL_NOTO_COLOR_EMOJI_TTF = + private static final String NOTO_COLOR_EMOJI_POSTSCRIPT_NAME = "NotoColorEmoji"; + private static final String NOTO_COLOR_EMOJI_TTF = "/data/local/tmp/NotoColorEmoji.ttf"; - private static final String ORIGINAL_NOTO_COLOR_EMOJI_TTF_FSV_SIG = - "/data/local/tmp/UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig"; + private static final String NOTO_COLOR_EMOJI_SIG = + "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmoji.sig"; // A font with revision == 0. private static final String TEST_NOTO_COLOR_EMOJI_V0_TTF = - "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV0.ttf"; - private static final String TEST_NOTO_COLOR_EMOJI_V0_TTF_FSV_SIG = - "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiV0.ttf.fsv_sig"; + "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiV0.ttf"; + private static final String TEST_NOTO_COLOR_EMOJI_V0_SIG = + "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiV0.sig"; // A font with revision == original + 1 private static final String TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF = - "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus1.ttf"; - private static final String TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG = - "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus1.ttf.fsv_sig"; + "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus1.ttf"; + private static final String TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG = + "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig"; // A font with revision == original + 2 private static final String TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF = - "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf"; - private static final String TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG = - "/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf.fsv_sig"; + "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf"; + private static final String TEST_NOTO_COLOR_EMOJI_VPLUS2_SIG = + "/data/local/tmp/UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig"; + + private static final String NOTO_SERIF_REGULAR_POSTSCRIPT_NAME = "NotoSerif"; + private static final String NOTO_SERIF_REGULAR_TTF = + "/data/local/tmp/NotoSerif-Regular.ttf"; + private static final String NOTO_SERIF_REGULAR_SIG = + "/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Regular.sig"; + + private static final String NOTO_SERIF_BOLD_POSTSCRIPT_NAME = "NotoSerif-Bold"; + private static final String NOTO_SERIF_BOLD_TTF = + "/data/local/tmp/NotoSerif-Bold.ttf"; + private static final String NOTO_SERIF_BOLD_SIG = + "/data/local/tmp/UpdatableSystemFontTest_NotoSerif-Bold.sig"; private static final String EMOJI_RENDERING_TEST_APP_ID = "com.android.emojirenderingtestapp"; private static final String EMOJI_RENDERING_TEST_ACTIVITY = EMOJI_RENDERING_TEST_APP_ID + "/.EmojiRenderingTestActivity"; + // This should be the same as the one in EmojiRenderingTestActivity. + private static final String TEST_NOTO_SERIF = "test-noto-serif"; private static final long ACTIVITY_TIMEOUT_MILLIS = SECONDS.toMillis(10); private static final String GET_AVAILABLE_FONTS_TEST_ACTIVITY = @@ -141,11 +159,20 @@ public class UpdatableSystemFontTest { @Test public void updateFont() throws Exception { + FontConfig oldFontConfig = + SystemUtil.callWithShellPermissionIdentity(mFontManager::getFontConfig); assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); + // Check that font config is updated. String fontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(fontPath).startsWith(DATA_FONTS_DIR); + FontConfig newFontConfig = + SystemUtil.callWithShellPermissionIdentity(mFontManager::getFontConfig); + assertThat(newFontConfig.getConfigVersion()) + .isGreaterThan(oldFontConfig.getConfigVersion()); + assertThat(newFontConfig.getLastModifiedTimeMillis()) + .isGreaterThan(oldFontConfig.getLastModifiedTimeMillis()); // The updated font should be readable and unmodifiable. expectCommandToSucceed("dd status=none if=" + fontPath + " of=/dev/null"); expectCommandToFail("dd status=none if=" + CERT_PATH + " of=" + fontPath); @@ -154,11 +181,11 @@ public class UpdatableSystemFontTest { @Test public void updateFont_twice() throws Exception { assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); String fontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(fontPath2).startsWith(DATA_FONTS_DIR); @@ -173,16 +200,16 @@ public class UpdatableSystemFontTest { public void updateFont_allowSameVersion() throws Exception { // Update original font to the same version assertThat(updateFontFile( - ORIGINAL_NOTO_COLOR_EMOJI_TTF, ORIGINAL_NOTO_COLOR_EMOJI_TTF_FSV_SIG)) + NOTO_COLOR_EMOJI_TTF, NOTO_COLOR_EMOJI_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); String fontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); // Update updated font to the same version assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); String fontPath3 = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(fontPath).startsWith(DATA_FONTS_DIR); @@ -195,28 +222,58 @@ public class UpdatableSystemFontTest { @Test public void updateFont_invalidCert() throws Exception { assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_SIG)) .isEqualTo(FontManager.RESULT_ERROR_VERIFICATION_FAILURE); } @Test public void updateFont_downgradeFromSystem() throws Exception { assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_V0_TTF, TEST_NOTO_COLOR_EMOJI_V0_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_V0_TTF, TEST_NOTO_COLOR_EMOJI_V0_SIG)) .isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING); } @Test public void updateFont_downgradeFromData() throws Exception { assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)) .isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING); } @Test + public void updateFontFamily() throws Exception { + assertThat(updateNotoSerifAs("serif")).isEqualTo(FontManager.RESULT_SUCCESS); + FontConfig.FontFamily family = findFontFamilyOrThrow("serif"); + assertThat(family.getFontList()).hasSize(2); + assertThat(family.getFontList().get(0).getPostScriptName()) + .isEqualTo(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME); + assertThat(family.getFontList().get(0).getFile().getAbsolutePath()) + .startsWith(DATA_FONTS_DIR); + assertThat(family.getFontList().get(0).getStyle().getWeight()) + .isEqualTo(FONT_WEIGHT_NORMAL); + assertThat(family.getFontList().get(1).getPostScriptName()) + .isEqualTo(NOTO_SERIF_BOLD_POSTSCRIPT_NAME); + assertThat(family.getFontList().get(1).getFile().getAbsolutePath()) + .startsWith(DATA_FONTS_DIR); + assertThat(family.getFontList().get(1).getStyle().getWeight()).isEqualTo(FONT_WEIGHT_BOLD); + } + + @Test + public void updateFontFamily_asNewFont() throws Exception { + assertThat(updateNotoSerifAs("UpdatableSystemFontTest-serif")) + .isEqualTo(FontManager.RESULT_SUCCESS); + FontConfig.FontFamily family = findFontFamilyOrThrow("UpdatableSystemFontTest-serif"); + assertThat(family.getFontList()).hasSize(2); + assertThat(family.getFontList().get(0).getPostScriptName()) + .isEqualTo(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME); + assertThat(family.getFontList().get(1).getPostScriptName()) + .isEqualTo(NOTO_SERIF_BOLD_POSTSCRIPT_NAME); + } + + @Test public void launchApp() throws Exception { String fontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(fontPath).startsWith(SYSTEM_FONTS_DIR); @@ -231,22 +288,25 @@ public class UpdatableSystemFontTest { String originalFontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(originalFontPath).startsWith(SYSTEM_FONTS_DIR); assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); String updatedFontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(updatedFontPath).startsWith(DATA_FONTS_DIR); + updateNotoSerifAs(TEST_NOTO_SERIF); + String notoSerifPath = getFontPath(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME); startActivity(EMOJI_RENDERING_TEST_APP_ID, EMOJI_RENDERING_TEST_ACTIVITY); // The original font should NOT be opened by the app. SystemUtil.eventually(() -> { assertThat(isFileOpenedBy(updatedFontPath, EMOJI_RENDERING_TEST_APP_ID)).isTrue(); assertThat(isFileOpenedBy(originalFontPath, EMOJI_RENDERING_TEST_APP_ID)).isFalse(); + assertThat(isFileOpenedBy(notoSerifPath, EMOJI_RENDERING_TEST_APP_ID)).isTrue(); }, ACTIVITY_TIMEOUT_MILLIS); } @Test public void reboot() throws Exception { expectCommandToSucceed(String.format("cmd font update %s %s", - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)); + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)); String fontPath = getFontPath(NOTO_COLOR_EMOJI_POSTSCRIPT_NAME); assertThat(fontPath).startsWith(DATA_FONTS_DIR); @@ -264,7 +324,7 @@ public class UpdatableSystemFontTest { Pattern.compile(Pattern.quote(TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF)); for (int i = 0; i < 10; i++) { assertThat(updateFontFile( - TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)) + TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)) .isEqualTo(FontManager.RESULT_SUCCESS); List<String> openFiles = getOpenFiles("system_server"); for (Pattern p : Arrays.asList(PATTERN_FONT_FILES, PATTERN_SYSTEM_FONT_FILES, @@ -285,7 +345,7 @@ public class UpdatableSystemFontTest { public void fdLeakTest_withoutPermission() throws Exception { Pattern patternEmojiVPlus1 = Pattern.compile(Pattern.quote(TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF)); - byte[] signature = Files.readAllBytes(Paths.get(TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG)); + byte[] signature = Files.readAllBytes(Paths.get(TEST_NOTO_COLOR_EMOJI_VPLUS1_SIG)); try (ParcelFileDescriptor fd = ParcelFileDescriptor.open( new File(TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF), MODE_READ_ONLY)) { assertThrows(SecurityException.class, @@ -340,18 +400,56 @@ public class UpdatableSystemFontTest { configVersion); } + private int updateNotoSerifAs(String familyName) throws IOException { + List<FontFamilyUpdateRequest.Font> fonts = Arrays.asList( + new FontFamilyUpdateRequest.Font.Builder(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME, + new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT)).build(), + new FontFamilyUpdateRequest.Font.Builder(NOTO_SERIF_BOLD_POSTSCRIPT_NAME, + new FontStyle(FONT_WEIGHT_BOLD, FONT_SLANT_UPRIGHT)).build()); + FontFamilyUpdateRequest.FontFamily fontFamily = + new FontFamilyUpdateRequest.FontFamily.Builder(familyName, fonts).build(); + byte[] regularSig = Files.readAllBytes(Paths.get(NOTO_SERIF_REGULAR_SIG)); + byte[] boldSig = Files.readAllBytes(Paths.get(NOTO_SERIF_BOLD_SIG)); + try (ParcelFileDescriptor regularFd = ParcelFileDescriptor.open( + new File(NOTO_SERIF_REGULAR_TTF), MODE_READ_ONLY); + ParcelFileDescriptor boldFd = ParcelFileDescriptor.open( + new File(NOTO_SERIF_BOLD_TTF), MODE_READ_ONLY)) { + return SystemUtil.runWithShellPermissionIdentity(() -> { + FontConfig fontConfig = mFontManager.getFontConfig(); + return mFontManager.updateFontFamily(new FontFamilyUpdateRequest.Builder() + .addFontFileUpdateRequest( + new FontFileUpdateRequest(regularFd, regularSig)) + .addFontFileUpdateRequest( + new FontFileUpdateRequest(boldFd, boldSig)) + .addFontFamily(fontFamily) + .build(), fontConfig.getConfigVersion()); + }); + } + } + private String getFontPath(String psName) { - return SystemUtil.runWithShellPermissionIdentity(() -> { - FontConfig fontConfig = mFontManager.getFontConfig(); - for (FontConfig.FontFamily family : fontConfig.getFontFamilies()) { - for (FontConfig.Font font : family.getFontList()) { - if (psName.equals(font.getPostScriptName())) { - return font.getFile().getAbsolutePath(); - } - } - } - throw new AssertionError("Font not found: " + psName); - }); + FontConfig fontConfig = + SystemUtil.runWithShellPermissionIdentity(mFontManager::getFontConfig); + return fontConfig.getFontFamilies().stream() + .flatMap(family -> family.getFontList().stream()) + .filter(font -> 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) + .orElseThrow(() -> new AssertionError("Font not found: " + psName)) + .getFile() + .getAbsolutePath(); + } + + private FontConfig.FontFamily findFontFamilyOrThrow(String familyName) { + FontConfig fontConfig = + SystemUtil.runWithShellPermissionIdentity(mFontManager::getFontConfig); + return fontConfig.getFontFamilies().stream() + .filter(family -> familyName.equals(family.getName())) + // Return the last match, because the latter family takes precedence if two families + // have the same name. + .reduce((first, second) -> second) + .orElseThrow(() -> new AssertionError("Family not found: " + familyName)); } private static void startActivity(String appId, String activityId) throws Exception { diff --git a/tests/UpdatableSystemFontTest/testdata/Android.bp b/tests/UpdatableSystemFontTest/testdata/Android.bp index 426464ea574e..64b698dd0db0 100644 --- a/tests/UpdatableSystemFontTest/testdata/Android.bp +++ b/tests/UpdatableSystemFontTest/testdata/Android.bp @@ -22,9 +22,9 @@ package { } // An existing module name is reused to avoid merge conflicts. -// TODO: fix the font and module name. +// TODO: fix the font file name. filegroup { - name: "NotoColorEmojiTtf", + name: "UpdatableSystemFontTest_NotoColorEmoji.ttf", srcs: ["NotoColorEmoji.ttf"], } @@ -48,9 +48,9 @@ genrule_defaults { } genrule { - name: "UpdatableSystemFontTestNotoColorEmojiV0Ttf", - srcs: [":NotoColorEmojiTtf"], - out: ["UpdatableSystemFontTestNotoColorEmojiV0.ttf"], + name: "UpdatableSystemFontTest_NotoColorEmojiV0.ttf", + srcs: [":UpdatableSystemFontTest_NotoColorEmoji.ttf"], + out: ["UpdatableSystemFontTest_NotoColorEmojiV0.ttf"], tools: ["update_font_metadata"], cmd: "$(location update_font_metadata) " + "--input=$(in) " + @@ -59,9 +59,9 @@ genrule { } genrule { - name: "UpdatableSystemFontTestNotoColorEmojiVPlus1Ttf", - srcs: [":NotoColorEmojiTtf"], - out: ["UpdatableSystemFontTestNotoColorEmojiVPlus1.ttf"], + name: "UpdatableSystemFontTest_NotoColorEmojiVPlus1.ttf", + srcs: [":UpdatableSystemFontTest_NotoColorEmoji.ttf"], + out: ["UpdatableSystemFontTest_NotoColorEmojiVPlus1.ttf"], tools: ["update_font_metadata"], cmd: "$(location update_font_metadata) " + "--input=$(in) " + @@ -70,9 +70,9 @@ genrule { } genrule { - name: "UpdatableSystemFontTestNotoColorEmojiVPlus2Ttf", - srcs: [":NotoColorEmojiTtf"], - out: ["UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf"], + name: "UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf", + srcs: [":UpdatableSystemFontTest_NotoColorEmoji.ttf"], + out: ["UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf"], tools: ["update_font_metadata"], cmd: "$(location update_font_metadata) " + "--input=$(in) " + @@ -94,29 +94,43 @@ genrule_defaults { } genrule { - name: "UpdatableSystemFontTestNotoColorEmojiTtfFsvSig", + name: "UpdatableSystemFontTest_NotoColorEmoji.sig", defaults: ["updatable_system_font_sig_gen_default"], - srcs: [":NotoColorEmojiTtf"], - out: ["UpdatableSystemFontTestNotoColorEmoji.ttf.fsv_sig"], + srcs: [":UpdatableSystemFontTest_NotoColorEmoji.ttf"], + out: ["UpdatableSystemFontTest_NotoColorEmoji.sig"], } genrule { - name: "UpdatableSystemFontTestNotoColorEmojiV0TtfFsvSig", + name: "UpdatableSystemFontTest_NotoColorEmojiV0.sig", defaults: ["updatable_system_font_sig_gen_default"], - srcs: [":UpdatableSystemFontTestNotoColorEmojiV0Ttf"], - out: ["UpdatableSystemFontTestNotoColorEmojiV0.ttf.fsv_sig"], + srcs: [":UpdatableSystemFontTest_NotoColorEmojiV0.ttf"], + out: ["UpdatableSystemFontTest_NotoColorEmojiV0.sig"], } genrule { - name: "UpdatableSystemFontTestNotoColorEmojiVPlus1TtfFsvSig", + name: "UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig", defaults: ["updatable_system_font_sig_gen_default"], - srcs: [":UpdatableSystemFontTestNotoColorEmojiVPlus1Ttf"], - out: ["UpdatableSystemFontTestNotoColorEmojiVPlus1.ttf.fsv_sig"], + srcs: [":UpdatableSystemFontTest_NotoColorEmojiVPlus1.ttf"], + out: ["UpdatableSystemFontTest_NotoColorEmojiVPlus1.sig"], } genrule { - name: "UpdatableSystemFontTestNotoColorEmojiVPlus2TtfFsvSig", + name: "UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig", defaults: ["updatable_system_font_sig_gen_default"], - srcs: [":UpdatableSystemFontTestNotoColorEmojiVPlus2Ttf"], - out: ["UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf.fsv_sig"], + srcs: [":UpdatableSystemFontTest_NotoColorEmojiVPlus2.ttf"], + out: ["UpdatableSystemFontTest_NotoColorEmojiVPlus2.sig"], +} + +genrule { + name: "UpdatableSystemFontTest_NotoSerif-Regular.sig", + defaults: ["updatable_system_font_sig_gen_default"], + srcs: [":NotoSerif-Regular.ttf"], + out: ["UpdatableSystemFontTest_NotoSerif-Regular.sig"], +} + +genrule { + name: "UpdatableSystemFontTest_NotoSerif-Bold.sig", + defaults: ["updatable_system_font_sig_gen_default"], + srcs: [":NotoSerif-Bold.ttf"], + out: ["UpdatableSystemFontTest_NotoSerif-Bold.sig"], } diff --git a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java index 7e8a13470c35..f695cbd5daf9 100644 --- a/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java +++ b/tests/UsageStatsPerfTests/src/com/android/frameworks/perftests/usage/tests/UsageStatsDatabasePerfTest.java @@ -62,12 +62,13 @@ public class UsageStatsDatabasePerfTest { private static final StatCombiner<UsageEvents.Event> sUsageStatsCombiner = new StatCombiner<UsageEvents.Event>() { @Override - public void combine(IntervalStats stats, boolean mutable, + public boolean combine(IntervalStats stats, boolean mutable, List<UsageEvents.Event> accResult) { final int size = stats.events.size(); for (int i = 0; i < size; i++) { accResult.add(stats.events.get(i)); } + return true; } }; diff --git a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java index e4880fd10d67..1341c85feec3 100644 --- a/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java +++ b/tests/VoiceEnrollment/src/com/android/test/voiceenrollment/TestEnrollmentActivity.java @@ -62,7 +62,7 @@ public class TestEnrollmentActivity extends Activity { public void onEnrollButtonClicked(View v) { Keyphrase kp = new Keyphrase(KEYPHRASE_ID, RECOGNITION_MODES, Locale.forLanguageTag(BCP47_LOCALE), TEXT, - new int[] { UserManager.get(this).getUserHandle() /* current user */}); + new int[] { UserManager.get(this).getProcessUserId() /* current user */}); UUID modelUuid = UUID.randomUUID(); // Generate a fake model to push. byte[] data = new byte[1024]; diff --git a/tests/componentalias/Android.bp b/tests/componentalias/Android.bp new file mode 100644 index 000000000000..e5eb3c7b6394 --- /dev/null +++ b/tests/componentalias/Android.bp @@ -0,0 +1,87 @@ +// 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_defaults { + name: "ComponentAliasTests_defaults", + static_libs: [ + "androidx.test.rules", + "compatibility-device-util-axt", + "mockito-target-extended-minus-junit4", + "truth-prebuilt", + "ub-uiautomator", + ], + libs: ["android.test.base"], + srcs: [ + "src/**/*.java", + ], + test_suites: [ + "general-tests", + ], + 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.xml b/tests/componentalias/AndroidManifest.xml new file mode 100755 index 000000000000..7bb83a336833 --- /dev/null +++ b/tests/componentalias/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?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> + <uses-library android:name="android.test.runner" /> + <property android:name="com.android.EXPERIMENTAL_COMPONENT_ALIAS_OPT_IN" android:value="true" /> + </application> +</manifest> diff --git a/tests/componentalias/AndroidManifest_main.xml b/tests/componentalias/AndroidManifest_main.xml new file mode 100755 index 000000000000..70e817ebf3e7 --- /dev/null +++ b/tests/componentalias/AndroidManifest_main.xml @@ -0,0 +1,28 @@ +<?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 new file mode 100644 index 000000000000..e73bb6102fbf --- /dev/null +++ b/tests/componentalias/AndroidManifest_service_aliases.xml @@ -0,0 +1,81 @@ +<?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="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="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="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="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="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="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="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="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="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="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 new file mode 100644 index 000000000000..24c0432bcf4c --- /dev/null +++ b/tests/componentalias/AndroidManifest_service_targets.xml @@ -0,0 +1,57 @@ +<?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 new file mode 100755 index 000000000000..21616f5edf00 --- /dev/null +++ b/tests/componentalias/AndroidManifest_sub1.xml @@ -0,0 +1,28 @@ +<?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 new file mode 100755 index 000000000000..c11b0cd55ef4 --- /dev/null +++ b/tests/componentalias/AndroidManifest_sub2.xml @@ -0,0 +1,28 @@ +<?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 new file mode 100644 index 000000000000..2d4621702329 --- /dev/null +++ b/tests/componentalias/AndroidTest-template.xml @@ -0,0 +1,40 @@ +<?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"> + <option name="run-command" value="am compat enable --no-kill USE_EXPERIMENTAL_COMPONENT_ALIAS android" /> + + <!-- 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/OWNERS b/tests/componentalias/OWNERS new file mode 100644 index 000000000000..1073c817d793 --- /dev/null +++ b/tests/componentalias/OWNERS @@ -0,0 +1,2 @@ +omakoto@google.com +yamasani@google.com
\ No newline at end of file diff --git a/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java b/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java new file mode 100644 index 000000000000..89db2f724302 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/BaseComponentAliasTest.java @@ -0,0 +1,91 @@ +/* + * 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.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.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); + @Before + public void enableComponentAlias() throws Exception { + sDeviceConfig.set("component_alias_overrides", ""); + + // 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; + }); + } + + @AfterClass + public static void restoreDeviceConfig() throws Exception { + 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 new file mode 100644 index 000000000000..7d5e0b9c6d8a --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasBroadcastTest.java @@ -0,0 +1,110 @@ +/* + * 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/ComponentAliasMessage.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java new file mode 100644 index 000000000000..d41696f27880 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasMessage.java @@ -0,0 +1,215 @@ +/* + * 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/ComponentAliasServiceTest.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java new file mode 100644 index 000000000000..f0ff088815af --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasServiceTest.java @@ -0,0 +1,315 @@ +/* + * 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/ComponentAliasTestCommon.java b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java new file mode 100644 index 000000000000..165d728c92a6 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/ComponentAliasTestCommon.java @@ -0,0 +1,28 @@ +/* + * 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; + +public final class ComponentAliasTestCommon { + private ComponentAliasTestCommon() { + } + + public static final String TAG = "ComponentAliasTest"; + + 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"; +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java b/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java new file mode 100644 index 000000000000..1d05e72a2f3f --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/b/BaseReceiver.java @@ -0,0 +1,44 @@ +/* + * 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/Target00.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target00.java new file mode 100644 index 000000000000..8fb4e91f790c --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/b/Target00.java @@ -0,0 +1,21 @@ +/* + * 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 android.content.componentalias.tests.s.BaseService; + +public class Target00 extends BaseReceiver { +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java new file mode 100644 index 000000000000..06f7a13f73d7 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/b/Target01.java @@ -0,0 +1,19 @@ +/* + * 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 Target01 extends BaseReceiver { +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java new file mode 100644 index 000000000000..df7579d8304d --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/b/Target02.java @@ -0,0 +1,19 @@ +/* + * 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 Target02 extends BaseReceiver { +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java new file mode 100644 index 000000000000..5ae55215f696 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/b/Target03.java @@ -0,0 +1,19 @@ +/* + * 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 Target03 extends BaseReceiver { +} diff --git a/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java b/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java new file mode 100644 index 000000000000..f9b9886b0bb2 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/b/Target04.java @@ -0,0 +1,19 @@ +/* + * 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 new file mode 100644 index 000000000000..535d9b80f100 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/s/BaseService.java @@ -0,0 +1,70 @@ +/* + * 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 new file mode 100644 index 000000000000..64b91f5695f5 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/s/Target00.java @@ -0,0 +1,19 @@ +/* + * 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 new file mode 100644 index 000000000000..bd589991d7dc --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/s/Target01.java @@ -0,0 +1,19 @@ +/* + * 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 new file mode 100644 index 000000000000..0ddf8188768b --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/s/Target02.java @@ -0,0 +1,19 @@ +/* + * 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 new file mode 100644 index 000000000000..0dbc0501b6f9 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/s/Target03.java @@ -0,0 +1,19 @@ +/* + * 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 new file mode 100644 index 000000000000..099425867f02 --- /dev/null +++ b/tests/componentalias/src/android/content/componentalias/tests/s/Target04.java @@ -0,0 +1,19 @@ +/* + * 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/utils/testutils/Android.bp b/tests/utils/testutils/Android.bp index af9786b92f40..deff42a27f47 100644 --- a/tests/utils/testutils/Android.bp +++ b/tests/utils/testutils/Android.bp @@ -38,6 +38,6 @@ java_library { "android.test.runner", "android.test.base", "android.test.mock", - "mockito-target-minus-junit4", + "mockito-target-extended-minus-junit4", ], } |