diff options
Diffstat (limited to 'tests')
178 files changed, 7533 insertions, 2537 deletions
diff --git a/tests/AppJankTest/Android.bp b/tests/AppJankTest/Android.bp new file mode 100644 index 000000000000..c3cda6a41cbb --- /dev/null +++ b/tests/AppJankTest/Android.bp @@ -0,0 +1,38 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "CoreAppJankTestCases", + team: "trendy_team_system_performance", + srcs: ["src/**/*.java"], + static_libs: [ + "androidx.test.rules", + "androidx.test.core", + "platform-test-annotations", + "flag-junit", + "androidx.test.uiautomator_uiautomator", + ], + platform_apis: true, + test_suites: ["device-tests"], + certificate: "platform", +} diff --git a/tests/AppJankTest/AndroidManifest.xml b/tests/AppJankTest/AndroidManifest.xml new file mode 100644 index 000000000000..861a79c6f0ed --- /dev/null +++ b/tests/AppJankTest/AndroidManifest.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT 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.app.jank.tests"> + + <application android:appCategory="news"> + <activity android:name=".JankTrackerActivity" + android:exported="true" + android:label="JankTrackerActivity" + android:launchMode="singleTop"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <action android:name="android.intent.action.VIEW_PERMISSION_USAGE"/> + </intent-filter> + </activity> + <activity android:name=".EmptyActivity" + android:exported="true" + android:label="EmptyActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <action android:name="android.intent.action.VIEW_PERMISSION_USAGE"/> + </intent-filter> + </activity> + <uses-library android:name="android.test.runner"/> + </application> + + <!-- self-instrumenting test package. --> + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:label="Core tests of App Jank Tracking" + android:targetPackage="android.app.jank.tests"> + </instrumentation> + +</manifest>
\ No newline at end of file diff --git a/tests/AppJankTest/AndroidTest.xml b/tests/AppJankTest/AndroidTest.xml new file mode 100644 index 000000000000..c01c75c9695c --- /dev/null +++ b/tests/AppJankTest/AndroidTest.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<configuration description="Config for Core App Jank Tests"> + <option name="test-suite-tag" value="apct"/> + + <option name="config-descriptor:metadata" key="component" value="systems"/> + <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" /> + <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> + <option name="config-descriptor:metadata" key="parameter" value="secondary_user" /> + <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" /> + + <option name="not-shardable" value="true" /> + <option name="install-arg" value="-t" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true"/> + <option name="test-file-name" value="CoreAppJankTestCases.apk"/> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="android.app.jank.tests"/> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/> + </test> +</configuration>
\ No newline at end of file diff --git a/tests/AppJankTest/OWNERS b/tests/AppJankTest/OWNERS new file mode 100644 index 000000000000..806de574b071 --- /dev/null +++ b/tests/AppJankTest/OWNERS @@ -0,0 +1,4 @@ +steventerrell@google.com +carmenjackson@google.com +jjaggi@google.com +pmuetschard@google.com
\ No newline at end of file diff --git a/tests/AppJankTest/res/layout/jank_tracker_activity_layout.xml b/tests/AppJankTest/res/layout/jank_tracker_activity_layout.xml new file mode 100644 index 000000000000..65def7f2d5b6 --- /dev/null +++ b/tests/AppJankTest/res/layout/jank_tracker_activity_layout.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + + +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fitsSystemWindows="true"> + + <LinearLayout + android:id="@+id/linear_layout" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + <EditText + android:id="@+id/edit_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textEnableTextConversionSuggestions" + android:text="Edit Text"/> + <TextView android:id="@+id/text_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Text View" + /> + <android.app.jank.tests.TestWidget + android:id="@+id/jank_tracker_widget" + android:layout_width="match_parent" + android:layout_height="wrap_content" + /> + </LinearLayout> +</ScrollView>
\ No newline at end of file diff --git a/tests/AppJankTest/src/android/app/jank/tests/EmptyActivity.java b/tests/AppJankTest/src/android/app/jank/tests/EmptyActivity.java new file mode 100644 index 000000000000..b326765ab097 --- /dev/null +++ b/tests/AppJankTest/src/android/app/jank/tests/EmptyActivity.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.app.jank.tests; + +import android.app.Activity; + +public class EmptyActivity extends Activity { +} diff --git a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java new file mode 100644 index 000000000000..34f0c191ecf5 --- /dev/null +++ b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.app.jank.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import android.app.Activity; +import android.app.Instrumentation; +import android.app.jank.AppJankStats; +import android.app.jank.Flags; +import android.app.jank.JankDataProcessor; +import android.app.jank.JankTracker; +import android.app.jank.StateTracker; +import android.content.Intent; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.widget.EditText; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.Until; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * This file contains tests that verify the proper functionality of the Jank Tracking feature. + * All tests should obtain references to necessary objects through View type interfaces, rather + * than direct instantiation. When operating outside of a testing environment, the expected + * behavior is to retrieve the necessary objects using View type interfaces. This approach ensures + * that calls are correctly routed down to the activity level. Any modifications to the call + * routing should result in test failures, which might happen with direct instantiations. + */ +@RunWith(AndroidJUnit4.class) +public class IntegrationTests { + public static final int WAIT_FOR_TIMEOUT_MS = 5000; + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private Activity mEmptyActivity; + + public UiDevice mDevice; + private Instrumentation mInstrumentation; + private ActivityTestRule<JankTrackerActivity> mJankTrackerActivityRule = + new ActivityTestRule<>( + JankTrackerActivity.class, + false, + false); + + private ActivityTestRule<EmptyActivity> mEmptyActivityRule = + new ActivityTestRule<>(EmptyActivity.class, false , true); + + @Before + public void setUp() { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + mDevice = UiDevice.getInstance(mInstrumentation); + } + + + /** + * Get a JankTracker object from a view and confirm it's not null. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void getJankTacker_confirmNotNull() { + Activity jankTrackerActivity = mJankTrackerActivityRule.launchActivity(null); + EditText editText = jankTrackerActivity.findViewById(R.id.edit_text); + + mDevice.wait(Until.findObject(By.text("Edit Text")), WAIT_FOR_TIMEOUT_MS); + + JankTracker jankTracker = editText.getJankTracker(); + assertTrue(jankTracker != null); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void reportJankStats_confirmPendingStatsIncreases() { + Activity jankTrackerActivity = mJankTrackerActivityRule.launchActivity(null); + EditText editText = jankTrackerActivity.findViewById(R.id.edit_text); + JankTracker jankTracker = editText.getJankTracker(); + + HashMap<String, JankDataProcessor.PendingJankStat> pendingStats = + jankTracker.getPendingJankStats(); + assertEquals(0, pendingStats.size()); + + editText.reportAppJankStats(JankUtils.getAppJankStats()); + + // reportAppJankStats performs the work on a background thread, check periodically to see + // if the work is complete. + for (int i = 0; i < 10; i++) { + try { + Thread.sleep(100); + if (jankTracker.getPendingJankStats().size() > 0) { + break; + } + } catch (InterruptedException exception) { + //do nothing and continue + } + } + + pendingStats = jankTracker.getPendingJankStats(); + + assertEquals(1, pendingStats.size()); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void simulateWidgetStateChanges_confirmStateChangesAreTracked() { + JankTrackerActivity jankTrackerActivity = + mJankTrackerActivityRule.launchActivity(null); + TestWidget testWidget = jankTrackerActivity.findViewById(R.id.jank_tracker_widget); + JankTracker jankTracker = testWidget.getJankTracker(); + jankTracker.forceListenerRegistration(); + + ArrayList<StateTracker.StateData> uiStates = new ArrayList<>(); + // Get the current UI states, at this point only the activity name should be in the UI + // states list. + jankTracker.getAllUiStates(uiStates); + + assertEquals(1, uiStates.size()); + + // This should add a UI state to be tracked. + testWidget.simulateAnimationStarting(); + uiStates.clear(); + jankTracker.getAllUiStates(uiStates); + + assertEquals(2, uiStates.size()); + + // Stop the animation + testWidget.simulateAnimationEnding(); + uiStates.clear(); + jankTracker.getAllUiStates(uiStates); + + assertEquals(2, uiStates.size()); + + // Confirm the Animation state has a VsyncIdEnd that is not default, indicating the end + // of that state. + for (int i = 0; i < uiStates.size(); i++) { + StateTracker.StateData stateData = uiStates.get(i); + if (stateData.mWidgetCategory.equals(AppJankStats.ANIMATION)) { + assertNotEquals(Long.MAX_VALUE, stateData.mVsyncIdEnd); + } + } + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void jankTrackingPaused_whenActivityNoLongerVisible() { + JankTrackerActivity jankTrackerActivity = + mJankTrackerActivityRule.launchActivity(null); + TestWidget testWidget = jankTrackerActivity.findViewById(R.id.jank_tracker_widget); + JankTracker jankTracker = testWidget.getJankTracker(); + jankTracker.forceListenerRegistration(); + + assertTrue(jankTracker.shouldTrack()); + + // Send jankTrackerActivity to the background + mDevice.pressHome(); + mDevice.waitForIdle(WAIT_FOR_TIMEOUT_MS); + + assertFalse(jankTracker.shouldTrack()); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void jankTrackingResumed_whenActivityBecomesVisibleAgain() { + mEmptyActivityRule.launchActivity(null); + mEmptyActivity = mEmptyActivityRule.getActivity(); + JankTrackerActivity jankTrackerActivity = + mJankTrackerActivityRule.launchActivity(null); + TestWidget testWidget = jankTrackerActivity.findViewById(R.id.jank_tracker_widget); + JankTracker jankTracker = testWidget.getJankTracker(); + jankTracker.forceListenerRegistration(); + + // Send jankTrackerActivity to the background + mDevice.pressHome(); + mDevice.waitForIdle(WAIT_FOR_TIMEOUT_MS); + + assertFalse(jankTracker.shouldTrack()); + + Intent resumeJankTracker = new Intent(mInstrumentation.getContext(), + JankTrackerActivity.class); + resumeJankTracker.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + mEmptyActivity.startActivity(resumeJankTracker); + mDevice.wait(Until.findObject(By.text("Edit Text")), WAIT_FOR_TIMEOUT_MS); + + assertTrue(jankTracker.shouldTrack()); + } +} diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java new file mode 100644 index 000000000000..30c568be7716 --- /dev/null +++ b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.app.jank.tests; + +import static org.junit.Assert.assertEquals; + +import android.app.jank.AppJankStats; +import android.app.jank.Flags; +import android.app.jank.JankDataProcessor; +import android.app.jank.StateTracker; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.view.Choreographer; +import android.view.SurfaceControl; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.core.app.ActivityScenario; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class JankDataProcessorTest { + + private Choreographer mChoreographer; + private StateTracker mStateTracker; + private JankDataProcessor mJankDataProcessor; + private static final int NANOS_PER_MS = 1_000_000; + private static String sActivityName; + private static ActivityScenario<EmptyActivity> sEmptyActivityActivityScenario; + private static final int APP_ID = 25; + + @BeforeClass + public static void classSetup() { + sEmptyActivityActivityScenario = ActivityScenario.launch(EmptyActivity.class); + sActivityName = sEmptyActivityActivityScenario.toString(); + } + + @AfterClass + public static void classTearDown() { + sEmptyActivityActivityScenario.close(); + } + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Before + @UiThreadTest + public void setup() { + mChoreographer = Choreographer.getInstance(); + mStateTracker = new StateTracker(mChoreographer); + mJankDataProcessor = new JankDataProcessor(mStateTracker); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void processJankData_multipleFramesAndStates_attributesTotalFramesCorrectly() { + List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange(); + mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange()); + + mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID); + + long totalFramesAttributed = getTotalFramesCounted(); + + // Each state is active for each frame that is passed in, there are two states being tested + // which is why jankData.size is multiplied by 2. + assertEquals(jankData.size() * 2, totalFramesAttributed); + } + + /** + * Each JankData frame has an associated vsyncid, only frames that have vsyncids between the + * StatData start and end vsyncids should be counted. This test confirms that if JankData + * does not share any frames with the states then no jank stats are added. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void processJankData_outOfRangeVsyncId_skipOutOfRangeVsyncIds() { + List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange(); + mStateTracker.addPendingStateData(getMockStateData_vsyncId_outOfRange()); + + mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID); + + assertEquals(0, mJankDataProcessor.getPendingJankStats().size()); + } + + /** + * It's expected to see many duplicate widget states, if a user is scrolling then + * pauses and resumes scrolling again, we may get three widget states two of which are the same. + * State 1: {Scroll,WidgetId,Scrolling} State 2: {Scroll,WidgetId,None} + * State 3: {Scroll,WidgetId,Scrolling} + * These duplicate states should coalesce into only one Jank stat. This test confirms that + * behavior. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void processJankData_duplicateStates_confirmDuplicatesCoalesce() { + // getMockStateData will return 10 states 5 of which are set to none and 5 of which are + // scrolling. + mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange()); + + mJankDataProcessor.processJankData(getMockJankData_vsyncId_inRange(), sActivityName, + APP_ID); + + // Confirm the duplicate states are coalesced down to 2 stats 1 for the scrolling state + // another for the none state. + assertEquals(2, mJankDataProcessor.getPendingJankStats().size()); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void processJankData_inRangeVsyncIds_confirmOnlyInRangeFramesCounted() { + List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange(); + int inRangeFrameCount = jankData.size(); + + mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange()); + mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID); + + // Two states are active for each frame which is why inRangeFrameCount is multiplied by 2. + assertEquals(inRangeFrameCount * 2, getTotalFramesCounted()); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void processJankData_inRangeVsyncIds_confirmHistogramCountMatchesFrameCount() { + List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange(); + mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange()); + mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID); + + long totalFrames = getTotalFramesCounted(); + long histogramFrames = getHistogramFrameCount(); + + assertEquals(totalFrames, histogramFrames); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void mergeAppJankStats_confirmStatAddedToPendingStats() { + HashMap<String, JankDataProcessor.PendingJankStat> pendingStats = + mJankDataProcessor.getPendingJankStats(); + + assertEquals(pendingStats.size(), 0); + + AppJankStats jankStats = JankUtils.getAppJankStats(); + mJankDataProcessor.mergeJankStats(jankStats, sActivityName); + + pendingStats = mJankDataProcessor.getPendingJankStats(); + + assertEquals(pendingStats.size(), 1); + } + + /** + * This test confirms matching states are combined into one pending stat. When JankStats are + * merged from outside the platform they will contain widget category, widget id and widget + * state. If an incoming JankStats matches a pending stat on all those fields the incoming + * JankStat will be merged into the existing stat. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void mergeAppJankStats_confirmStatsWithMatchingStatesAreCombinedIntoOnePendingStat() { + AppJankStats jankStats = JankUtils.getAppJankStats(); + mJankDataProcessor.mergeJankStats(jankStats, sActivityName); + + HashMap<String, JankDataProcessor.PendingJankStat> pendingStats = + mJankDataProcessor.getPendingJankStats(); + assertEquals(pendingStats.size(), 1); + + AppJankStats secondJankStat = JankUtils.getAppJankStats(); + mJankDataProcessor.mergeJankStats(secondJankStat, sActivityName); + + pendingStats = mJankDataProcessor.getPendingJankStats(); + + assertEquals(pendingStats.size(), 1); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void mergeAppJankStats_whenStatsWithMatchingStatesMerge_confirmFrameCountsAdded() { + AppJankStats jankStats = JankUtils.getAppJankStats(); + mJankDataProcessor.mergeJankStats(jankStats, sActivityName); + mJankDataProcessor.mergeJankStats(jankStats, sActivityName); + + HashMap<String, JankDataProcessor.PendingJankStat> pendingStats = + mJankDataProcessor.getPendingJankStats(); + + String statKey = pendingStats.keySet().iterator().next(); + JankDataProcessor.PendingJankStat pendingStat = pendingStats.get(statKey); + + assertEquals(pendingStats.size(), 1); + // The same jankStats objects are merged twice, this should result in the frame counts being + // doubled. + assertEquals(jankStats.getJankyFrameCount() * 2, pendingStat.getJankyFrames()); + assertEquals(jankStats.getTotalFrameCount() * 2, pendingStat.getTotalFrames()); + + int[] originalHistogramBuckets = jankStats.getFrameOverrunHistogram().getBucketCounters(); + int[] frameOverrunBuckets = pendingStat.getFrameOverrunBuckets(); + + for (int i = 0; i < frameOverrunBuckets.length; i++) { + assertEquals(originalHistogramBuckets[i] * 2, frameOverrunBuckets[i]); + } + } + + // TODO b/375005277 add tests that cover logging and releasing resources back to pool. + + private long getTotalFramesCounted() { + return mJankDataProcessor.getPendingJankStats().values() + .stream().mapToLong(stat -> stat.getTotalFrames()).sum(); + } + + private long getHistogramFrameCount() { + long totalHistogramFrames = 0; + + for (JankDataProcessor.PendingJankStat stats : + mJankDataProcessor.getPendingJankStats().values()) { + int[] overrunHistogram = stats.getFrameOverrunBuckets(); + + for (int i = 0; i < overrunHistogram.length; i++) { + totalHistogramFrames += overrunHistogram[i]; + } + } + + return totalHistogramFrames; + } + + /** + * Out of range data will have a mVsyncIdStart and mVsyncIdEnd values set to below 25. + */ + private List<StateTracker.StateData> getMockStateData_vsyncId_outOfRange() { + ArrayList<StateTracker.StateData> stateData = new ArrayList<StateTracker.StateData>(); + StateTracker.StateData newStateData = new StateTracker.StateData(); + newStateData.mVsyncIdEnd = 20; + newStateData.mStateDataKey = "Test1_OutBand"; + newStateData.mVsyncIdStart = 1; + newStateData.mWidgetState = "scrolling"; + newStateData.mWidgetId = "widgetId"; + newStateData.mWidgetCategory = "Scroll"; + stateData.add(newStateData); + + newStateData = new StateTracker.StateData(); + newStateData.mVsyncIdEnd = 24; + newStateData.mStateDataKey = "Test1_InBand"; + newStateData.mVsyncIdStart = 20; + newStateData.mWidgetState = "Idle"; + newStateData.mWidgetId = "widgetId"; + newStateData.mWidgetCategory = "Scroll"; + stateData.add(newStateData); + + newStateData = new StateTracker.StateData(); + newStateData.mVsyncIdEnd = 20; + newStateData.mStateDataKey = "Test1_OutBand"; + newStateData.mVsyncIdStart = 12; + newStateData.mWidgetState = "Idle"; + newStateData.mWidgetId = "widgetId"; + newStateData.mWidgetCategory = "Scroll"; + stateData.add(newStateData); + + return stateData; + } + + /** + * This method returns two unique states, one state is set to scrolling the other is set + * to none. Both states will have the same startvsyncid to ensure each state is counted the same + * number of times. This keeps logic in asserts easier to reason about. Both states will have + * a startVsyncId between 25 and 35. + */ + private List<StateTracker.StateData> getMockStateData_vsyncId_inRange() { + ArrayList<StateTracker.StateData> stateData = new ArrayList<StateTracker.StateData>(); + + for (int i = 0; i < 10; i++) { + StateTracker.StateData newStateData = new StateTracker.StateData(); + newStateData.mVsyncIdEnd = Long.MAX_VALUE; + newStateData.mStateDataKey = "Test1_" + (i % 2 == 0 ? "scrolling" : "none"); + // Divide i by two to ensure both the scrolling and none states get the same vsyncid + // This makes asserts in tests easier to reason about as each state should be counted + // the same number of times. + newStateData.mVsyncIdStart = 25 + (i / 2); + newStateData.mWidgetState = i % 2 == 0 ? "scrolling" : "none"; + newStateData.mWidgetId = "widgetId"; + newStateData.mWidgetCategory = "Scroll"; + + stateData.add(newStateData); + } + + return stateData; + } + + /** + * In range data will have a frameVsyncId value between 25 and 35. + */ + private List<SurfaceControl.JankData> getMockJankData_vsyncId_inRange() { + ArrayList<SurfaceControl.JankData> mockData = new ArrayList<>(); + + for (int i = 0; i < 10; i++) { + mockData.add(new SurfaceControl.JankData( + /*frameVsyncId*/25 + i, + SurfaceControl.JankData.JANK_NONE, + NANOS_PER_MS * ((long) i), + NANOS_PER_MS * ((long) i), + NANOS_PER_MS * ((long) i))); + + } + + return mockData; + } + + /** + * Out of range data will have frameVsyncId values below 25. + */ + private List<SurfaceControl.JankData> getMockJankData_vsyncId_outOfRange() { + ArrayList<SurfaceControl.JankData> mockData = new ArrayList<>(); + + for (int i = 0; i < 10; i++) { + mockData.add(new SurfaceControl.JankData( + /*frameVsyncId*/i, + SurfaceControl.JankData.JANK_NONE, + NANOS_PER_MS * ((long) i), + NANOS_PER_MS * ((long) i), + NANOS_PER_MS * ((long) i))); + + } + + return mockData; + } +} diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java new file mode 100644 index 000000000000..80ab6ad3e587 --- /dev/null +++ b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerActivity.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.app.jank.tests; + +import android.app.Activity; +import android.os.Bundle; + + +public class JankTrackerActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.jank_tracker_activity_layout); + } +} + + diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java new file mode 100644 index 000000000000..1bdf019d6c42 --- /dev/null +++ b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.app.jank.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import android.app.jank.Flags; +import android.app.jank.JankTracker; +import android.app.jank.StateTracker; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.view.Choreographer; +import android.view.View; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.core.app.ActivityScenario; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.policy.DecorView; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; + +@RunWith(AndroidJUnit4.class) +public class JankTrackerTest { + private Choreographer mChoreographer; + private JankTracker mJankTracker; + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + /** + * Start an empty activity so decore view is not null when creating the JankTracker instance. + */ + private static ActivityScenario<EmptyActivity> sEmptyActivityRule; + + private static String sActivityName; + + private static View sActivityDecorView; + + @BeforeClass + public static void classSetup() { + sEmptyActivityRule = ActivityScenario.launch(EmptyActivity.class); + sEmptyActivityRule.onActivity(activity -> { + sActivityDecorView = activity.getWindow().getDecorView(); + sActivityName = activity.toString(); + }); + } + + @AfterClass + public static void classTearDown() { + sEmptyActivityRule.close(); + } + + @Before + @UiThreadTest + public void setup() { + mChoreographer = Choreographer.getInstance(); + mJankTracker = new JankTracker(mChoreographer, sActivityDecorView); + mJankTracker.setActivityName(sActivityName); + } + + /** + * When jank tracking is enabled the activity name should be added as a state to associate + * frames to it. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void jankTracking_WhenEnabled_ActivityAdded() { + mJankTracker.enableAppJankTracking(); + + ArrayList<StateTracker.StateData> stateData = new ArrayList<>(); + mJankTracker.getAllUiStates(stateData); + + assertEquals(1, stateData.size()); + + StateTracker.StateData firstState = stateData.getFirst(); + + assertEquals(sActivityName, firstState.mWidgetId); + } + + /** + * No states should be added when tracking is disabled. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void jankTrackingDisabled_StatesShouldNot_BeAddedToTracker() { + mJankTracker.disableAppJankTracking(); + + mJankTracker.addUiState("FAKE_CATEGORY", "FAKE_ID", + "FAKE_STATE"); + + ArrayList<StateTracker.StateData> stateData = new ArrayList<>(); + mJankTracker.getAllUiStates(stateData); + + assertEquals(0, stateData.size()); + } + + /** + * The activity name as well as the test state should be added for frame association. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void jankTrackingEnabled_StatesShould_BeAddedToTracker() { + mJankTracker.forceListenerRegistration(); + + mJankTracker.enableAppJankTracking(); + mJankTracker.addUiState("FAKE_CATEGORY", "FAKE_ID", + "FAKE_STATE"); + + ArrayList<StateTracker.StateData> stateData = new ArrayList<>(); + mJankTracker.getAllUiStates(stateData); + + assertEquals(2, stateData.size()); + } + + /** + * Activity state should only be added once even if jank tracking is enabled multiple times. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void jankTrackingEnabled_EnabledCalledTwice_ActivityStateOnlyAddedOnce() { + mJankTracker.enableAppJankTracking(); + + ArrayList<StateTracker.StateData> stateData = new ArrayList<>(); + mJankTracker.getAllUiStates(stateData); + + assertEquals(1, stateData.size()); + + stateData.clear(); + + mJankTracker.enableAppJankTracking(); + mJankTracker.getAllUiStates(stateData); + + assertEquals(1, stateData.size()); + } + + /** + * Test confirms a JankTracker object is retrieved from the activity. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_LOGGING_ENABLED) + public void jankTracker_NotNull_WhenRetrievedFromDecorView() { + DecorView decorView = (DecorView) sActivityDecorView; + JankTracker jankTracker = decorView.getJankTracker(); + + assertNotNull(jankTracker); + } +} diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java b/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java new file mode 100644 index 000000000000..0b4d97ed20d6 --- /dev/null +++ b/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.app.jank.tests; + +import android.app.jank.AppJankStats; +import android.app.jank.FrameOverrunHistogram; + +public class JankUtils { + private static final int APP_ID = 25; + + /** + * Returns a mock AppJankStats object to be used in tests. + */ + public static AppJankStats getAppJankStats() { + AppJankStats jankStats = new AppJankStats( + /*App Uid*/APP_ID, + /*Widget Id*/"test widget id", + /*Widget Category*/AppJankStats.SCROLL, + /*Widget State*/AppJankStats.SCROLLING, + /*Total Frames*/100, + /*Janky Frames*/25, + getOverrunHistogram() + ); + return jankStats; + } + + /** + * Returns a mock histogram to be used with an AppJankStats object. + */ + public static FrameOverrunHistogram getOverrunHistogram() { + FrameOverrunHistogram overrunHistogram = new FrameOverrunHistogram(); + overrunHistogram.addFrameOverrunMillis(-2); + overrunHistogram.addFrameOverrunMillis(1); + overrunHistogram.addFrameOverrunMillis(5); + overrunHistogram.addFrameOverrunMillis(25); + return overrunHistogram; + } +} diff --git a/tests/AppJankTest/src/android/app/jank/tests/StateTrackerTest.java b/tests/AppJankTest/src/android/app/jank/tests/StateTrackerTest.java new file mode 100644 index 000000000000..541009e05e55 --- /dev/null +++ b/tests/AppJankTest/src/android/app/jank/tests/StateTrackerTest.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.app.jank.tests; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.app.jank.Flags; +import android.app.jank.StateTracker; +import android.app.jank.StateTracker.StateData; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.view.Choreographer; + +import androidx.test.annotation.UiThreadTest; +import androidx.test.core.app.ActivityScenario; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; + + +@RunWith(AndroidJUnit4.class) +public class StateTrackerTest { + + private static final String WIDGET_CATEGORY_NONE = "None"; + private static final String WIDGET_CATEGORY_SCROLL = "Scroll"; + private static final String WIDGET_STATE_IDLE = "Idle"; + private static final String WIDGET_STATE_SCROLLING = "Scrolling"; + private StateTracker mStateTracker; + private Choreographer mChoreographer; + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + /** + * Start an empty activity so choreographer won't return -1 for vsyncid. + */ + private static ActivityScenario<EmptyActivity> sEmptyActivityRule; + + @BeforeClass + public static void classSetup() { + sEmptyActivityRule = ActivityScenario.launch(EmptyActivity.class); + } + + @AfterClass + public static void classTearDown() { + sEmptyActivityRule.close(); + } + + @Before + @UiThreadTest + public void setup() { + mChoreographer = Choreographer.getInstance(); + mStateTracker = new StateTracker(mChoreographer); + } + + /** + * Check that the start vsyncid is added when the state is first added and end vsyncid is + * set to the default value, indicating it has not been updated. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void addWidgetState_VerifyStateHasStartVsyncId() { + mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING, + "addWidgetState_VerifyStateHasStartVsyncId"); + + ArrayList<StateData> stateList = new ArrayList<StateData>(); + mStateTracker.retrieveAllStates(stateList); + StateData stateData = stateList.get(0); + + assertTrue(stateData.mVsyncIdStart > 0); + assertTrue(stateData.mVsyncIdEnd == Long.MAX_VALUE); + } + + /** + * Check that the end vsyncid is added when the state is removed. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void removeWidgetState_VerifyStateHasEndVsyncId() { + + mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING, + "removeWidgetState_VerifyStateHasEndVsyncId"); + mStateTracker.removeState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING, + "removeWidgetState_VerifyStateHasEndVsyncId"); + + ArrayList<StateData> stateList = new ArrayList<StateData>(); + mStateTracker.retrieveAllStates(stateList); + StateData stateData = stateList.get(0); + + assertTrue(stateData.mVsyncIdStart > 0); + assertTrue(stateData.mVsyncIdEnd != Long.MAX_VALUE); + } + + /** + * Check that duplicate states are aggregated into only one active instance. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void addDuplicateStates_ConfirmStateCountOnlyOne() { + mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING, + "addDuplicateStates_ConfirmStateCountOnlyOne"); + + ArrayList<StateData> stateList = new ArrayList<>(); + mStateTracker.retrieveAllStates(stateList); + + assertEquals(stateList.size(), 1); + + mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING, + "addDuplicateStates_ConfirmStateCountOnlyOne"); + + stateList.clear(); + + mStateTracker.retrieveAllStates(stateList); + + assertEquals(stateList.size(), 1); + } + + /** + * Check that correct distinct states are returned when all states are retrieved. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void addThreeStateChanges_ConfirmThreeStatesReturned() { + mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING, + "addThreeStateChanges_ConfirmThreeStatesReturned"); + mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING, + "addThreeStateChanges_ConfirmThreeStatesReturned_01"); + mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING, + "addThreeStateChanges_ConfirmThreeStatesReturned_02"); + + ArrayList<StateData> stateList = new ArrayList<>(); + mStateTracker.retrieveAllStates(stateList); + + assertEquals(stateList.size(), 3); + } + + /** + * Confirm when states are added and removed the removed states are moved to the previousStates + * list and returned when retrieveAllStates is called. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void simulateAddingSeveralStates() { + for (int i = 0; i < 20; i++) { + mStateTracker.removeState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING, + String.format("simulateAddingSeveralStates_%s", i - 1)); + mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING, + String.format("simulateAddingSeveralStates_%s", i)); + } + + ArrayList<StateData> stateList = new ArrayList<>(); + mStateTracker.retrieveAllStates(stateList); + + int countStatesWithEndVsync = 0; + for (int i = 0; i < stateList.size(); i++) { + if (stateList.get(i).mVsyncIdEnd != Long.MAX_VALUE) { + countStatesWithEndVsync++; + } + } + + // The last state that was added would be an active state and should not have an associated + // end vsyncid. + assertEquals(19, countStatesWithEndVsync); + } + + /** + * Confirm once a state has been attributed to a frame it has been removed from the previous + * state list. + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API) + public void confirmProcessedStates_RemovedFromPreviousStateList() { + for (int i = 0; i < 20; i++) { + mStateTracker.removeState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING, + String.format("simulateAddingSeveralStates_%s", i - 1)); + mStateTracker.putState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING, + String.format("simulateAddingSeveralStates_%s", i)); + + if (i == 19) { + mStateTracker.removeState(WIDGET_CATEGORY_SCROLL, WIDGET_STATE_SCROLLING, + String.format("simulateAddingSeveralStates_%s", i)); + } + } + + ArrayList<StateData> stateList = new ArrayList<>(); + mStateTracker.retrieveAllStates(stateList); + + assertEquals(20, stateList.size()); + + // Simulate processing all the states. + for (int i = 0; i < stateList.size(); i++) { + stateList.get(i).mProcessed = true; + } + // Clear out all processed states. + mStateTracker.stateProcessingComplete(); + + stateList.clear(); + + mStateTracker.retrieveAllStates(stateList); + + assertEquals(0, stateList.size()); + } +} diff --git a/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java b/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java new file mode 100644 index 000000000000..5fff46038ead --- /dev/null +++ b/tests/AppJankTest/src/android/app/jank/tests/TestWidget.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.app.jank.tests; + +import android.app.jank.AppJankStats; +import android.app.jank.JankTracker; +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; + +public class TestWidget extends View { + + private JankTracker mJankTracker; + + /** + * Create JankTrackerView + */ + public TestWidget(Context context) { + super(context); + } + + /** + * Create JankTrackerView, needed by system when inflating views defined in a layout file. + */ + public TestWidget(Context context, AttributeSet attributeSet) { + super(context, attributeSet); + } + + /** + * Mock starting an animation. + */ + public void simulateAnimationStarting() { + if (jankTrackerCreated()) { + mJankTracker.addUiState(AppJankStats.ANIMATION, + Integer.toString(this.getId()), AppJankStats.ANIMATING); + } + } + + /** + * Mock ending an animation. + */ + public void simulateAnimationEnding() { + if (jankTrackerCreated()) { + mJankTracker.removeUiState(AppJankStats.ANIMATION, + Integer.toString(this.getId()), AppJankStats.ANIMATING); + } + } + + private boolean jankTrackerCreated() { + if (mJankTracker == null) { + mJankTracker = getJankTracker(); + } + return mJankTracker != null; + } +} diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt index ad95fbc36867..88ebf3edc7ed 100644 --- a/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt +++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt @@ -2,10 +2,11 @@ package android.security.attestationverification import android.app.Activity import android.os.Bundle +import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS +import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE import android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY import android.security.attestationverification.AttestationVerificationManager.PROFILE_PEER_DEVICE -import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY import android.security.attestationverification.AttestationVerificationManager.TYPE_UNKNOWN @@ -54,7 +55,7 @@ class PeerDeviceSystemAttestationVerificationTest { future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } @Test @@ -66,7 +67,7 @@ class PeerDeviceSystemAttestationVerificationTest { future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } @Test @@ -80,7 +81,7 @@ class PeerDeviceSystemAttestationVerificationTest { future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) val future2 = CompletableFuture<Int>() val challengeRequirements = Bundle() @@ -90,7 +91,7 @@ class PeerDeviceSystemAttestationVerificationTest { future2.complete(result) } - assertThat(future2.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future2.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } @Test @@ -104,7 +105,7 @@ class PeerDeviceSystemAttestationVerificationTest { future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } @Test @@ -118,7 +119,7 @@ class PeerDeviceSystemAttestationVerificationTest { future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_CERTS) } @Test @@ -131,7 +132,7 @@ class PeerDeviceSystemAttestationVerificationTest { invalidAttestationByteArray, activity.mainExecutor) { result, _ -> future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_CERTS) } private fun <T> CompletableFuture<T>.getSoon(): T { diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt index 8f06b4a2ea0a..e77364de8747 100644 --- a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt +++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt @@ -2,6 +2,9 @@ package android.security.attestationverification import android.os.Bundle import android.app.Activity +import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS +import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS +import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_UNSUPPORTED_PROFILE import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -14,9 +17,6 @@ import com.google.common.truth.Truth.assertThat import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE import android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED import android.security.attestationverification.AttestationVerificationManager.PROFILE_UNKNOWN -import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE -import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS -import android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE import android.security.keystore.KeyGenParameterSpec @@ -58,19 +58,19 @@ class SystemAttestationVerificationTest { future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_UNKNOWN) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_UNSUPPORTED_PROFILE) } @Test fun verifyAttestation_returnsFailureWithEmptyAttestation() { val future = CompletableFuture<Int>() - val profile = AttestationProfile(PROFILE_SELF_TRUSTED) - avm.verifyAttestation(profile, TYPE_CHALLENGE, Bundle(), ByteArray(0), - activity.mainExecutor) { result, _ -> + val selfTrusted = TestSelfTrustedAttestation("test", "challengeStr") + avm.verifyAttestation(selfTrusted.profile, selfTrusted.localBindingType, + selfTrusted.requirements, ByteArray(0), activity.mainExecutor) { result, _ -> future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_CERTS) } @Test @@ -81,7 +81,7 @@ class SystemAttestationVerificationTest { Bundle(), selfTrusted.attestation, activity.mainExecutor) { result, _ -> future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } @Test @@ -92,7 +92,7 @@ class SystemAttestationVerificationTest { selfTrusted.requirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } @Test @@ -106,7 +106,7 @@ class SystemAttestationVerificationTest { wrongKeyRequirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } @Test @@ -119,7 +119,7 @@ class SystemAttestationVerificationTest { wrongChallengeRequirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_FAILURE) + assertThat(future.getSoon()).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } // TODO(b/216144791): Add more failure tests for PROFILE_SELF_TRUSTED. @@ -131,20 +131,7 @@ class SystemAttestationVerificationTest { selfTrusted.requirements, selfTrusted.attestation, activity.mainExecutor) { result, _ -> future.complete(result) } - assertThat(future.getSoon()).isEqualTo(RESULT_SUCCESS) - } - - @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) + assertThat(future.getSoon()).isEqualTo(0) } @Test diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt index 4712d6b51bd5..4d1a1a55af74 100644 --- a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt +++ b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt @@ -3,11 +3,12 @@ package com.android.server.security import android.app.Activity import android.content.Context import android.os.Bundle +import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_CERTS +import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS +import android.security.attestationverification.AttestationVerificationManager.FLAG_FAILURE_PATCH_LEVEL_DIFF import android.security.attestationverification.AttestationVerificationManager.PARAM_CHALLENGE import android.security.attestationverification.AttestationVerificationManager.PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS import android.security.attestationverification.AttestationVerificationManager.PARAM_PUBLIC_KEY -import android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE -import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY import android.util.IndentingPrintWriter @@ -72,7 +73,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_CHALLENGE, challengeRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_SUCCESS) + assertThat(result).isEqualTo(0) } @Test @@ -88,7 +89,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_CHALLENGE, challengeRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_SUCCESS) + assertThat(result).isEqualTo(0) } @Test @@ -108,7 +109,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_PUBLIC_KEY, pkRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_SUCCESS) + assertThat(result).isEqualTo(0) } @Test @@ -126,7 +127,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_SUCCESS) + assertThat(result).isEqualTo(0) } @Test @@ -143,7 +144,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_CHALLENGE, challengeRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_FAILURE) + assertThat(result).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } @Test @@ -159,7 +160,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_CHALLENGE, challengeRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_FAILURE) + assertThat(result).isEqualTo(FLAG_FAILURE_PATCH_LEVEL_DIFF) } @Test @@ -176,7 +177,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_CHALLENGE, challengeRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_SUCCESS) + assertThat(result).isEqualTo(0) } @Test @@ -191,10 +192,28 @@ class AttestationVerificationPeerDeviceVerifierTest { val result = verifier.verifyAttestation( TYPE_CHALLENGE, challengeRequirements, - // The patch date of this file is early 2022 TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_FAILURE) + assertThat(result).isEqualTo(FLAG_FAILURE_PATCH_LEVEL_DIFF) + } + + @Test + fun verifyAttestation_returnsFailureOwnedBySystemAndPatchDataNotWithinMaxPatchDiff() { + val verifier = AttestationVerificationPeerDeviceVerifier( + context, dumpLogger, trustAnchors, false, LocalDate.of(2024, 10, 1), + LocalDate.of(2024, 9, 1) + ) + val challengeRequirements = Bundle() + challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) + challengeRequirements.putBoolean("android.key_owned_by_system", true) + challengeRequirements.putInt(PARAM_MAX_PATCH_LEVEL_DIFF_MONTHS, 24) + + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) + // Both "owned by system" and "patch level diff" checks should fail. + assertThat(result).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS or FLAG_FAILURE_PATCH_LEVEL_DIFF) } @Test @@ -210,7 +229,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_CHALLENGE, challengeRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_FAILURE) + assertThat(result).isEqualTo(FLAG_FAILURE_CERTS) } @Test @@ -232,7 +251,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_CHALLENGE, challengeRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_FAILURE) + assertThat(result).isEqualTo(FLAG_FAILURE_CERTS) } fun verifyAttestation_returnsFailureChallenge() { @@ -247,7 +266,7 @@ class AttestationVerificationPeerDeviceVerifierTest { TYPE_CHALLENGE, challengeRequirements, TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() ) - assertThat(result).isEqualTo(RESULT_FAILURE) + assertThat(result).isEqualTo(FLAG_FAILURE_LOCAL_BINDING_REQUIREMENTS) } private fun String.fromPEMFileToCerts(): Collection<Certificate> { @@ -281,6 +300,7 @@ class AttestationVerificationPeerDeviceVerifierTest { companion object { private const val TAG = "AVFTest" private const val TEST_ROOT_CERT_FILENAME = "test_root_certs.pem" + // Local patch date is 20220105 private const val TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME = "test_attestation_with_root_certs.pem" private const val TEST_ATTESTATION_CERT_FILENAME = "test_attestation_wrong_root_certs.pem" diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java index 30cc002b4144..6d818d7287b0 100644 --- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java +++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java @@ -159,7 +159,7 @@ public class BatteryUsageStatsPerfTest { private static BatteryUsageStats buildBatteryUsageStats() { final BatteryUsageStats.Builder builder = - new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false, false, false, 0) + new BatteryUsageStats.Builder(new String[]{"FOO"}, false, false, false, 0) .setBatteryCapacity(4000) .setDischargePercentage(20) .setDischargedPowerRange(1000, 2000) @@ -168,32 +168,27 @@ public class BatteryUsageStatsPerfTest { builder.getAggregateBatteryConsumerBuilder( BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS) - .setConsumedPower(123) - .setConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU, 10100) - .setConsumedPower( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200) - .setUsageDurationMillis( - BatteryConsumer.POWER_COMPONENT_CPU, 10300) - .setUsageDurationMillis( - BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400); + .addConsumedPower(123) + .addConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, 10100) + .addConsumedPower(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200) + .addUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CPU, 10300) + .addUsageDurationMillis(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); + .setTimeInProcessStateMs(UidBatteryConsumer.STATE_FOREGROUND, i * 2000) + .setTimeInProcessStateMs(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.addConsumedPower(componentId, componentId * 123.0); + consumerBuilder.addUsageDurationMillis(componentId, componentId * 1000); } consumerBuilder - .setConsumedPower(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 1234) - .setUsageDurationMillis(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 4321); + .addConsumedPower(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 1234) + .addUsageDurationMillis(BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 4321); } return builder.build(); } diff --git a/tests/BinaryTransparencyHostTest/Android.bp b/tests/BinaryTransparencyHostTest/Android.bp index e14e5fea001f..1c8386add1b1 100644 --- a/tests/BinaryTransparencyHostTest/Android.bp +++ b/tests/BinaryTransparencyHostTest/Android.bp @@ -31,6 +31,8 @@ java_test_host { ], static_libs: [ "truth", + "flag-junit-host", + "android.app.flags-aconfig-java-host", ], device_common_data: [ ":BinaryTransparencyTestApp", diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java index 6e5f08a11ed8..6d8dbcb5c963 100644 --- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java +++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java @@ -24,6 +24,9 @@ import static org.junit.Assert.fail; import android.platform.test.annotations.LargeTest; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.host.HostFlagsValueProvider; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.log.LogUtil.CLog; @@ -34,6 +37,7 @@ import com.android.tradefed.util.CommandResult; import com.android.tradefed.util.CommandStatus; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,6 +53,10 @@ public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test { /** Waiting time for the job to be scheduled */ private static final int JOB_CREATION_MAX_SECONDS = 30; + @Rule + public final CheckFlagsRule mCheckFlagsRule = + HostFlagsValueProvider.createCheckFlagsRule(this::getDevice); + @Before public void setUp() throws Exception { cancelPendingJob(); @@ -123,6 +131,7 @@ public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test { } } + @RequiresFlagsDisabled(android.app.Flags.FLAG_BACKGROUND_INSTALL_CONTROL_CALLBACK_API) @Test public void testPreloadUpdateTriggersJobScheduling() throws Exception { try { diff --git a/tests/BootImageProfileTest/Android.bp b/tests/BootImageProfileTest/Android.bp index 9fb5aa21f985..dbdc4b4407b7 100644 --- a/tests/BootImageProfileTest/Android.bp +++ b/tests/BootImageProfileTest/Android.bp @@ -19,6 +19,7 @@ package { // to get the below license kinds: // SPDX-license-identifier-Apache-2.0 default_applicable_licenses: ["frameworks_base_license"], + default_team: "trendy_team_art_mainline", } java_test_host { diff --git a/tests/CtsSurfaceControlTestsStaging/AndroidManifest.xml b/tests/CtsSurfaceControlTestsStaging/AndroidManifest.xml index d8eb9ff37e78..da510fcd4d63 100644 --- a/tests/CtsSurfaceControlTestsStaging/AndroidManifest.xml +++ b/tests/CtsSurfaceControlTestsStaging/AndroidManifest.xml @@ -18,12 +18,20 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android.view.surfacecontroltests"> + <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER"/> + <uses-permission android:name="android.permission.OBSERVE_PICTURE_PROFILES"/> + <application android:debuggable="true" android:testOnly="true"> <uses-library android:name="android.test.runner"/> <activity android:name=".GraphicsActivity" android:exported="false"> </activity> + <activity android:name=".SurfaceControlPictureProfileTestActivity" + android:exported="true" + android:turnScreenOn="true" + android:showWhenLocked="true" + android:theme="@android:style/Theme.NoTitleBar.Fullscreen" /> </application> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" diff --git a/tests/CtsSurfaceControlTestsStaging/AndroidTest.xml b/tests/CtsSurfaceControlTestsStaging/AndroidTest.xml index 5c0163fcfa7e..025bf378d82f 100644 --- a/tests/CtsSurfaceControlTestsStaging/AndroidTest.xml +++ b/tests/CtsSurfaceControlTestsStaging/AndroidTest.xml @@ -21,6 +21,9 @@ <option name="install-arg" value="-t" /> <option name="test-file-name" value="CtsSurfaceControlTestsStaging.apk" /> </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.FeatureFlagTargetPreparer" > + <option name="flag-value" value="media_tv/android.media.tv.flags.apply_picture_profiles=true" /> + </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="android.view.surfacecontroltests" /> <option name="hidden-api-checks" value="false" /> diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java new file mode 100644 index 000000000000..135f7102b8a9 --- /dev/null +++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTest.java @@ -0,0 +1,260 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.surfacecontroltests; + +import static android.Manifest.permission.OBSERVE_PICTURE_PROFILES; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assume.assumeTrue; + +import static java.util.Arrays.stream; + +import android.hardware.HardwareBuffer; +import android.media.quality.PictureProfileHandle; +import android.os.Process; +import android.view.SurfaceControl; +import android.view.SurfaceControlActivePicture; +import android.view.SurfaceControlActivePictureListener; +import android.view.SurfaceView; + +import androidx.test.filters.SmallTest; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.stream.LongStream; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SurfaceControlPictureProfileTest { + private static final String TAG = SurfaceControlPictureProfileTest.class.getSimpleName(); + + private SurfaceControl[] mSurfaceControls; + private SurfaceControl mSurfaceControl; + + @Rule + public ActivityTestRule<SurfaceControlPictureProfileTestActivity> mActivityRule = + new ActivityTestRule<>(SurfaceControlPictureProfileTestActivity.class); + + @Before + public void setup() { + SurfaceView[] surfaceViews = mActivityRule.getActivity().getSurfaceViews(); + mSurfaceControls = new SurfaceControl[surfaceViews.length]; + // Create a child surface control so we can set a buffer, priority and profile handle all + // on one single surface control + for (int i = 0; i < mSurfaceControls.length; ++i) { + mSurfaceControls[i] = new SurfaceControl.Builder().setName("test").setHidden(false) + .setParent(surfaceViews[i].getSurfaceControl()).build(); + } + mSurfaceControl = mSurfaceControls[0]; + } + + @Test + public void whenPictureProfileApplied_noExecptionsThrown() { + assumeTrue("Skipping test because feature flag is disabled", + com.android.graphics.libgui.flags.Flags.applyPictureProfiles()); + // TODO(b/337330263): Call MediaQualityManager.getMaxPictureProfiles instead + assumeTrue("Skipping test because no picture profile support", + SurfaceControl.getMaxPictureProfiles() > 0); + + // TODO(b/337330263): Load the handle from MediaQualityManager instead + PictureProfileHandle handle = new PictureProfileHandle(1); + HardwareBuffer buffer = getSolidBuffer(100, 100); + new SurfaceControl.Transaction() + .setBuffer(mSurfaceControl, buffer) + .setPictureProfileHandle(mSurfaceControl, handle) + .apply(); + } + + @Test + public void whenStartsListening_callsListener() { + assumeTrue("Skipping test because feature flag is disabled", + com.android.graphics.libgui.flags.Flags.applyPictureProfiles()); + // TODO(b/337330263): Call MediaQualityManager.getMaxPictureProfiles instead + assumeTrue("Skipping test because no picture profile support", + SurfaceControl.getMaxPictureProfiles() > 0); + + BlockingQueue<SurfaceControlActivePicture[]> picturesQueue = new LinkedBlockingQueue<>(); + SurfaceControlActivePicture[] pictures; + SurfaceControlActivePictureListener listener = new SurfaceControlActivePictureListener() { + @Override + public void onActivePicturesChanged(SurfaceControlActivePicture[] pictures) { + picturesQueue.add(pictures); + } + }; + // TODO(b/337330263): Call MediaQualityManager.addActivePictureListener instead + adoptShellPermissionIdentity(OBSERVE_PICTURE_PROFILES); + listener.startListening(); + { + HardwareBuffer buffer = getSolidBuffer(100, 100); + new SurfaceControl.Transaction().setBuffer(mSurfaceControl, buffer).apply(); + } + + pictures = pollMs(picturesQueue, 200); + assertThat(pictures).isNotNull(); + assertThat(pictures).isEmpty(); + } + + @Test + public void whenPictureProfileApplied_callsListenerWithUidAndProfileId() { + assumeTrue("Skipping test because feature flag is disabled", + com.android.graphics.libgui.flags.Flags.applyPictureProfiles()); + // TODO(b/337330263): Call MediaQualityManager.getMaxPictureProfiles instead + assumeTrue("Skipping test because no picture profile support", + SurfaceControl.getMaxPictureProfiles() > 0); + + BlockingQueue<SurfaceControlActivePicture[]> picturesQueue = new LinkedBlockingQueue<>(); + SurfaceControlActivePicture[] pictures; + SurfaceControlActivePictureListener listener = new SurfaceControlActivePictureListener() { + @Override + public void onActivePicturesChanged(SurfaceControlActivePicture[] pictures) { + picturesQueue.add(pictures); + } + }; + // TODO(b/337330263): Call MediaQualityManager.addActivePictureListener instead + adoptShellPermissionIdentity(OBSERVE_PICTURE_PROFILES); + listener.startListening(); + { + HardwareBuffer buffer = getSolidBuffer(100, 100); + new SurfaceControl.Transaction().setBuffer(mSurfaceControl, buffer).apply(); + } + + pictures = pollMs(picturesQueue, 200); + assertThat(pictures).isNotNull(); + assertThat(pictures).isEmpty(); + + // TODO(b/337330263): Load the handle from MediaQualityManager instead + PictureProfileHandle handle = new PictureProfileHandle(1); + HardwareBuffer buffer = getSolidBuffer(100, 100); + new SurfaceControl.Transaction() + .setBuffer(mSurfaceControl, buffer) + .setPictureProfileHandle(mSurfaceControl, handle) + .apply(); + + pictures = pollMs(picturesQueue, 200); + assertThat(pictures).isNotNull(); + assertThat(stream(pictures).map(picture -> picture.getPictureProfileHandle().getId())) + .containsExactly(handle.getId()); + assertThat(stream(pictures).map(picture -> picture.getOwnerUid())) + .containsExactly(Process.myUid()); + } + + @Test + public void whenPriorityChanges_callsListenerOnlyForLowerPriorityLayers() { + assumeTrue("Skipping test because feature flag is disabled", + com.android.graphics.libgui.flags.Flags.applyPictureProfiles()); + // TODO(b/337330263): Call MediaQualityManager.getMaxPictureProfiles instead + int maxPictureProfiles = SurfaceControl.getMaxPictureProfiles(); + assumeTrue("Skipping test because no picture profile support", maxPictureProfiles > 0); + + BlockingQueue<SurfaceControlActivePicture[]> picturesQueue = new LinkedBlockingQueue<>(); + SurfaceControlActivePicture[] pictures; + SurfaceControlActivePictureListener listener = new SurfaceControlActivePictureListener() { + @Override + public void onActivePicturesChanged(SurfaceControlActivePicture[] pictures) { + picturesQueue.add(pictures); + } + }; + // TODO(b/337330263): Call MediaQualityManager.addActivePictureListener instead + adoptShellPermissionIdentity(OBSERVE_PICTURE_PROFILES); + listener.startListening(); + { + HardwareBuffer buffer = getSolidBuffer(100, 100); + new SurfaceControl.Transaction().setBuffer(mSurfaceControl, buffer).apply(); + } + + pictures = pollMs(picturesQueue, 200); + assertThat(pictures).isNotNull(); + assertThat(pictures).isEmpty(); + + SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); + // Use one more picture profile than allowed + for (int i = 0; i <= maxPictureProfiles; ++i) { + // Increase the number of surface views as necessary to support device configuration. + assertThat(i).isLessThan(mSurfaceControls.length); + + // TODO(b/337330263): Load the handle from MediaQualityManager instead + PictureProfileHandle handle = new PictureProfileHandle(i + 1); + HardwareBuffer buffer = getSolidBuffer(100, 100); + transaction + .setBuffer(mSurfaceControls[i], buffer) + .setPictureProfileHandle(mSurfaceControls[i], handle) + .setContentPriority(mSurfaceControls[i], 0); + } + // Make the first layer low priority (high value) + transaction.setContentPriority(mSurfaceControls[0], 2); + // Make the last layer higher priority (lower value) + transaction.setContentPriority(mSurfaceControls[maxPictureProfiles], 1); + transaction.apply(); + + pictures = pollMs(picturesQueue, 200); + assertThat(pictures).isNotNull(); + assertThat(stream(pictures).map(picture -> picture.getLayerId())) + .containsNoDuplicates(); + // Expect all but the first layer to be listed as an active picture + assertThat(stream(pictures).map(picture -> picture.getPictureProfileHandle().getId())) + .containsExactlyElementsIn(toIterableRange(2, maxPictureProfiles + 1)); + + // Change priority and ensure that the first layer gets access + new SurfaceControl.Transaction().setContentPriority(mSurfaceControls[0], 0).apply(); + pictures = pollMs(picturesQueue, 200); + assertThat(pictures).isNotNull(); + // Expect all but the last layer to be listed as an active picture + assertThat(stream(pictures).map(picture -> picture.getPictureProfileHandle().getId())) + .containsExactlyElementsIn(toIterableRange(1, maxPictureProfiles)); + } + + private static SurfaceControlActivePicture[] pollMs( + BlockingQueue<SurfaceControlActivePicture[]> picturesQueue, int waitMs) { + SurfaceControlActivePicture[] pictures = null; + long nowMs = System.currentTimeMillis(); + long endTimeMs = nowMs + waitMs; + while (nowMs < endTimeMs && pictures == null) { + try { + pictures = picturesQueue.poll(endTimeMs - nowMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + // continue polling until timeout when interrupted + } + nowMs = System.currentTimeMillis(); + } + return pictures; + } + + Iterable<Long> toIterableRange(int start, int stop) { + return () -> LongStream.rangeClosed(start, stop).iterator(); + } + + private void adoptShellPermissionIdentity(String permission) { + getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(permission); + } + + private HardwareBuffer getSolidBuffer(int width, int height) { + // We can assume that RGBA_8888 format is supported for every platform. + return HardwareBuffer.create( + width, height, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_CPU_WRITE_OFTEN); + } +} diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTestActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTestActivity.java new file mode 100644 index 000000000000..42fcb261fa9d --- /dev/null +++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlPictureProfileTestActivity.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.surfacecontroltests; + +import android.app.Activity; +import android.os.Bundle; +import android.view.SurfaceView; + +public class SurfaceControlPictureProfileTestActivity extends Activity { + private SurfaceView[] mSurfaceViews; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.picture_profile_test_layout); + mSurfaceViews = new SurfaceView[3]; + mSurfaceViews[0] = (SurfaceView) findViewById(R.id.surfaceview1); + mSurfaceViews[1] = (SurfaceView) findViewById(R.id.surfaceview2); + mSurfaceViews[2] = (SurfaceView) findViewById(R.id.surfaceview3); + } + + public SurfaceView getSurfaceView() { + return mSurfaceViews[0]; + } + + public SurfaceView[] getSurfaceViews() { + return mSurfaceViews; + } +} diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/res/layout/picture_profile_test_layout.xml b/tests/CtsSurfaceControlTestsStaging/src/main/res/layout/picture_profile_test_layout.xml new file mode 100644 index 000000000000..9aa25785d9f2 --- /dev/null +++ b/tests/CtsSurfaceControlTestsStaging/src/main/res/layout/picture_profile_test_layout.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + <SurfaceView android:id="@+id/surfaceview1" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_weight="1"/> + <SurfaceView android:id="@+id/surfaceview2" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_weight="1"/> + <SurfaceView android:id="@+id/surfaceview3" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:layout_weight="1"/> +</LinearLayout> diff --git a/tests/FlickerTests/ActivityEmbedding/Android.bp b/tests/FlickerTests/ActivityEmbedding/Android.bp index c681ce96a269..529f84ac4e90 100644 --- a/tests/FlickerTests/ActivityEmbedding/Android.bp +++ b/tests/FlickerTests/ActivityEmbedding/Android.bp @@ -24,71 +24,6 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -//////////////////////////////////////////////////////////////////////////////// -// Begin to cleanup after CL merges - -filegroup { - name: "FlickerTestsOtherCommon-src", - srcs: ["src/**/ActivityEmbeddingTestBase.kt"], -} - -filegroup { - name: "FlickerTestsOtherOpen-src", - srcs: ["src/**/open/*"], -} - -filegroup { - name: "FlickerTestsOtherRotation-src", - srcs: ["src/**/rotation/*"], -} - -java_library { - name: "FlickerTestsOtherCommon", - defaults: ["FlickerTestsDefault"], - srcs: [":FlickerTestsOtherCommon-src"], - static_libs: ["FlickerTestsBase"], -} - -java_defaults { - name: "FlickerTestsOtherDefaults", - defaults: ["FlickerTestsDefault"], - manifest: "AndroidManifest.xml", - package_name: "com.android.server.wm.flicker", - instrumentation_target_package: "com.android.server.wm.flicker", - test_config_template: "AndroidTestTemplate.xml", - static_libs: [ - "FlickerTestsBase", - "FlickerTestsOtherCommon", - ], - data: ["trace_config/*"], -} - -android_test { - name: "FlickerTestsOtherOpen", - defaults: ["FlickerTestsOtherDefaults"], - srcs: [":FlickerTestsOtherOpen-src"], -} - -android_test { - name: "FlickerTestsOtherRotation", - defaults: ["FlickerTestsOtherDefaults"], - srcs: [":FlickerTestsOtherRotation-src"], -} - -android_test { - name: "FlickerTestsOther", - defaults: ["FlickerTestsOtherDefaults"], - srcs: ["src/**/*"], - exclude_srcs: [ - ":FlickerTestsOtherOpen-src", - ":FlickerTestsOtherRotation-src", - ":FlickerTestsOtherCommon-src", - ], -} - -// End to cleanup after CL merges -//////////////////////////////////////////////////////////////////////////////// - android_test { name: "FlickerTestsActivityEmbedding", defaults: ["FlickerTestsDefault"], @@ -97,10 +32,7 @@ android_test { instrumentation_target_package: "com.android.server.wm.flicker", test_config_template: "AndroidTestTemplate.xml", srcs: ["src/**/*"], - static_libs: [ - "FlickerTestsBase", - "FlickerTestsOtherCommon", - ], + static_libs: ["FlickerTestsBase"], data: ["trace_config/*"], } diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml index 82de070921f0..685ae9a5fef2 100644 --- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml +++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml @@ -12,6 +12,10 @@ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- Turns off Wi-fi --> + <option name="wifi" value="off"/> + <!-- Turns off Bluetooth --> + <option name="bluetooth" value="off"/> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> @@ -41,6 +45,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt index 519b4296d93a..f44e282e8116 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/close/CloseSecondaryActivityInSplitTest.kt @@ -38,7 +38,7 @@ import org.junit.runners.Parameterized * Setup: Launch A|B in split with B being the secondary activity. Transitions: Finish B and expect * A to become fullscreen. * - * To run this test: `atest FlickerTestsOther:CloseSecondaryActivityInSplitTest` + * To run this test: `atest FlickerTestsActivityEmbedding:CloseSecondaryActivityInSplitTest` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt index 4cd6d15b2983..7a76dd9d1ebb 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt @@ -39,7 +39,7 @@ import org.junit.runners.Parameterized * windows are equal in size. B is on the top and A is on the bottom. Transitions: Change the split * ratio to A:B=0.7:0.3, expect bounds change for both A and B. * - * To run this test: `atest FlickerTestsOther:HorizontalSplitChangeRatioTest` + * To run this test: `atest FlickerTestsActivityEmbedding:HorizontalSplitChangeRatioTest` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt index 5df8b57294f0..08b5f38a4655 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt @@ -39,7 +39,7 @@ import org.junit.runners.Parameterized * Setup: Launch A|B in split with B being the secondary activity. Transitions: A start C with * alwaysExpand=true, expect C to launch in fullscreen and cover split A|B. * - * To run this test: `atest FlickerTestsOther:MainActivityStartsSecondaryWithAlwaysExpandTest` + * To run this test: `atest FlickerTestsActivityEmbedding:MainActivityStartsSecondaryWithAlwaysExpandTest` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt index 5009c7ce4e70..1f002a089486 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt @@ -34,7 +34,7 @@ import org.junit.runners.Parameterized * Test opening an activity that will launch another activity as ActivityEmbedding placeholder in * split. * - * To run this test: `atest FlickerTestsOther:OpenActivityEmbeddingPlaceholderSplitTest` + * To run this test: `atest FlickerTestsActivityEmbedding:OpenActivityEmbeddingPlaceholderSplitTest` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt index 6327d92ed570..b78c3ec65e32 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt @@ -34,7 +34,7 @@ import org.junit.runners.Parameterized /** * Test opening a secondary activity that will split with the main activity. * - * To run this test: `atest FlickerTestsOther:OpenActivityEmbeddingSecondaryToSplitTest` + * To run this test: `atest FlickerTestsActivityEmbedding:OpenActivityEmbeddingSecondaryToSplitTest` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt index 78004ccc3f97..10167d71c255 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt @@ -39,7 +39,7 @@ import org.junit.runners.Parameterized * * Transitions: Let B start C, expect C to cover B and end up in split A|C. * - * To run this test: `atest FlickerTestsOther:OpenThirdActivityOverSplitTest` + * To run this test: `atest FlickerTestsActivityEmbedding:OpenThirdActivityOverSplitTest` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt index eed9225d3da0..a0b910bb9ac3 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt @@ -41,7 +41,7 @@ import org.junit.runners.Parameterized * Setup: Start from a split A|B. Transition: B enters PIP, observe the window first goes fullscreen * then shrink to the bottom right corner on screen. * - * To run this test: `atest FlickerTestsOther:SecondaryActivityEnterPipTest` + * To run this test: `atest FlickerTestsActivityEmbedding:SecondaryActivityEnterPipTest` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt index f5e6c7854eba..ea13f5f748e1 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt @@ -36,7 +36,7 @@ import org.junit.runners.Parameterized * Setup: Launch A|B in split with B being the secondary activity. Transitions: Rotate display, and * expect A and B to split evenly in new rotation. * - * To run this test: `atest FlickerTestsOther:RotateSplitNoChangeTest` + * To run this test: `atest FlickerTestsActivityEmbedding:RotateSplitNoChangeTest` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt index 65a23e854e0b..2a177d53b037 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rtl/RTLStartSecondaryWithPlaceholderTest.kt @@ -37,7 +37,7 @@ import org.junit.runners.Parameterized * PlaceholderPrimary, which is configured to launch with PlaceholderSecondary in RTL. Expect split * PlaceholderSecondary|PlaceholderPrimary covering split B|A. * - * To run this test: `atest FlickerTestsOther:RTLStartSecondaryWithPlaceholderTest` + * To run this test: `atest FlickerTestsActivityEmbedding:RTLStartSecondaryWithPlaceholderTest` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt index c3e1a1fa2e18..0ca8f37b239b 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt @@ -47,7 +47,7 @@ import org.junit.runners.Parameterized * Setup: Launch A|B in split and secondaryApp, return to home. Transitions: Let AE Split A|B enter * splitscreen with secondaryApp. Resulting in A|B|secondaryApp. * - * To run this test: `atest FlickerTestsOther:EnterSystemSplitTest` + * To run this test: `atest FlickerTestsActivityEmbedding:EnterSystemSplitTest` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp index 27e9ffa4cea5..f44eacbaafbf 100644 --- a/tests/FlickerTests/Android.bp +++ b/tests/FlickerTests/Android.bp @@ -41,13 +41,13 @@ java_defaults { "platform-test-annotations", "wm-flicker-common-app-helpers", "wm-shell-flicker-utils", + "systemui-tapl", ], data: [":FlickerTestApp"], } java_library { name: "wm-flicker-common-assertions", - platform_apis: true, optimize: { enabled: false, }, diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml index 4ffb11ab92ae..5f92d7fe830b 100644 --- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml @@ -12,6 +12,10 @@ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- Turns off Wi-fi --> + <option name="wifi" value="off"/> + <!-- Turns off Bluetooth --> + <option name="bluetooth" value="off"/> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> @@ -41,6 +45,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt index e19e1ce35cd9..56b718a642db 100644 --- a/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt +++ b/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt @@ -31,7 +31,7 @@ import org.junit.runners.Parameterized /** * Test app closes by pressing back button * - * To run this test: `atest FlickerTests:CloseAppBackButtonTest` + * To run this test: `atest FlickerTestsAppClose:CloseAppBackButtonTest` * * Actions: * ``` diff --git a/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt index 47ed642cd5f5..5deacaf31802 100644 --- a/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt +++ b/tests/FlickerTests/AppClose/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt @@ -31,7 +31,7 @@ import org.junit.runners.Parameterized /** * Test app closes by pressing home button * - * To run this test: `atest FlickerTests:CloseAppHomeButtonTest` + * To run this test: `atest FlickerTestsAppClose:CloseAppHomeButtonTest` * * Actions: * ``` diff --git a/tests/FlickerTests/AppLaunch/Android.bp b/tests/FlickerTests/AppLaunch/Android.bp index b61739f100ab..17d0f967b1bd 100644 --- a/tests/FlickerTests/AppLaunch/Android.bp +++ b/tests/FlickerTests/AppLaunch/Android.bp @@ -24,69 +24,13 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -//////////////////////////////////////////////////////////////////////////////// -// Begin to cleanup after CL merges - -filegroup { - name: "FlickerTestsAppLaunchCommon-src", - srcs: ["src/**/common/*"], -} - -filegroup { - name: "FlickerTestsAppLaunch1-src", - srcs: ["src/**/OpenAppFrom*"], -} - -java_library { - name: "FlickerTestsAppLaunchCommon", - defaults: ["FlickerTestsDefault"], - srcs: [":FlickerTestsAppLaunchCommon-src"], - static_libs: ["FlickerTestsBase"], -} - -android_test { - name: "FlickerTestsAppLaunch1", - defaults: ["FlickerTestsDefault"], - manifest: "AndroidManifest.xml", - test_config_template: "AndroidTestTemplate.xml", - srcs: [":FlickerTestsAppLaunch1-src"], - static_libs: [ - "FlickerTestsBase", - "FlickerTestsAppLaunchCommon", - ], - data: ["trace_config/*"], -} - -android_test { - name: "FlickerTestsAppLaunch2", - defaults: ["FlickerTestsDefault"], - manifest: "AndroidManifest.xml", - test_config_template: "AndroidTestTemplate.xml", - srcs: ["src/**/*"], - exclude_srcs: [ - ":FlickerTestsAppLaunchCommon-src", - ":FlickerTestsAppLaunch1-src", - ], - static_libs: [ - "FlickerTestsBase", - "FlickerTestsAppLaunchCommon", - ], - data: ["trace_config/*"], -} - -// End to cleanup after CL merges -//////////////////////////////////////////////////////////////////////////////// - android_test { name: "FlickerTestsAppLaunch", defaults: ["FlickerTestsDefault"], manifest: "AndroidManifest.xml", test_config_template: "AndroidTestTemplate.xml", srcs: ["src/**/*"], - static_libs: [ - "FlickerTestsBase", - "FlickerTestsAppLaunchCommon", - ], + static_libs: ["FlickerTestsBase"], data: ["trace_config/*"], } diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml index 0fa4d07b2eca..1b90e99a8ba2 100644 --- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml @@ -12,6 +12,10 @@ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- Turns off Wi-fi --> + <option name="wifi" value="off"/> + <!-- Turns off Bluetooth --> + <option name="bluetooth" value="off"/> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> @@ -41,6 +45,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt index ffa90a33e7b3..e59b6bd0617c 100644 --- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt +++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/ActivityTransitionTest.kt @@ -35,7 +35,7 @@ import org.junit.runners.Parameterized /** * Test the back and forward transition between 2 activities. * - * To run this test: `atest FlickerTests:ActivitiesTransitionTest` + * To run this test: `atest FlickerTestsAppLaunch:ActivityTransitionTest` * * Actions: * ``` diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt index 8c285bda6616..2bf8cc40d0a0 100644 --- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt +++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt @@ -30,7 +30,7 @@ import org.junit.runners.Parameterized /** * Test cold launching an app from launcher * - * To run this test: `atest FlickerTests:OpenAppColdFromIcon` + * To run this test: `atest FlickerTestsAppLaunch:OpenAppFromIconColdTest` * * Actions: * ``` diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt index 57da05f13bbb..9c6bf9de37ce 100644 --- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt +++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt @@ -30,7 +30,7 @@ import org.junit.runners.Parameterized /** * Test launching an app after cold opening camera * - * To run this test: `atest FlickerTests:OpenAppAfterCameraTest` + * To run this test: `atest FlickerTestsAppLaunch:OpenAppFromIntentColdAfterCameraTest` * * Notes: Some default assertions are inherited [OpenAppTransition] */ diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt index 267f282db41c..1a53a611c8d7 100644 --- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt +++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt @@ -35,7 +35,7 @@ import org.junit.runners.Parameterized /** * Test cold launching an app from launcher * - * To run this test: `atest FlickerTests:OpenAppColdTest` + * To run this test: `atest FlickerTestsAppLaunch:OpenAppFromIntentColdTest` * * Actions: * ``` diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt index 83065de8b592..14b6a18bfe2e 100644 --- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt +++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt @@ -34,7 +34,7 @@ import org.junit.runners.Parameterized /** * Test warm launching an app from launcher * - * To run this test: `atest FlickerTests:OpenAppWarmTest` + * To run this test: `atest FlickerTestsAppLaunch:OpenAppFromIntentWarmTest` * * Actions: * ``` diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt index 6e6a3275191f..f30fe96b9d05 100644 --- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt +++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt @@ -41,7 +41,7 @@ import org.junit.runners.Parameterized * * This test assumes the device doesn't have AOD enabled * - * To run this test: `atest FlickerTests:OpenAppNonResizeableTest` + * To run this test: `atest FlickerTestsAppLaunch:OpenAppFromLockscreenViaIntentTest` * * Actions: * ``` diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt index 6d3eaeb9c1b3..064c76f1b92f 100644 --- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt +++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt @@ -35,7 +35,7 @@ import org.junit.runners.Parameterized /** * Test launching an app from the recents app view (the overview) * - * To run this test: `atest FlickerTests:OpenAppFromOverviewTest` + * To run this test: `atest FlickerTestsAppLaunch:OpenAppFromOverviewTest` * * Actions: * ``` diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt index bec02d0e59c6..9c552eb478d4 100644 --- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt +++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt @@ -41,7 +41,7 @@ import org.junit.runners.Parameterized /** * Test cold launching camera from launcher by double pressing power button * - * To run this test: `atest FlickerTests:OpenCameraOnDoubleClickPowerButton` + * To run this test: `atest FlickerTestsAppLaunch:OpenCameraFromHomeOnDoubleClickPowerButtonTest` * * Actions: * ``` @@ -140,14 +140,8 @@ class OpenCameraFromHomeOnDoubleClickPowerButtonTest(flicker: LegacyFlickerTest) @Postsubmit @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() { - flicker.assertLayers { - this.visibleLayersShownMoreThanOneConsecutiveEntry( - LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + - listOf(CAMERA_BACKGROUND) - ) - } - } + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() @Postsubmit @Test @@ -170,12 +164,5 @@ class OpenCameraFromHomeOnDoubleClickPowerButtonTest(flicker: LegacyFlickerTest) @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams() = LegacyFlickerTestFactory.nonRotationTests() - - private val CAMERA_BACKGROUND = - ComponentNameMatcher( - "Background for SurfaceView" + - "[com.google.android.GoogleCamera/" + - "com.google.android.apps.camera.legacy.app.activity.main.CameraActivity]" - ) } } diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt index e0aef8d1addd..9d7a9c6789f8 100644 --- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt +++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt @@ -34,7 +34,7 @@ import org.junit.runners.Parameterized /** * Test cold launching an app from launcher * - * To run this test: `atest FlickerTests:OpenTransferSplashscreenAppFromLauncherTransition` + * To run this test: `atest FlickerTestsAppLaunch:OpenTransferSplashscreenAppFromLauncherTransition` * * Actions: * ``` diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt index f1144991c438..7e2d472f4c4d 100644 --- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt +++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt @@ -42,7 +42,7 @@ import org.junit.runners.Parameterized /** * Test the [android.app.ActivityOptions.makeCustomTaskAnimation]. * - * To run this test: `atest FlickerTests:OverrideTaskTransitionTest` + * To run this test: `atest FlickerTestsAppLaunch:OverrideTaskTransitionTest` * * Actions: * ``` diff --git a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt index a71599d25632..95e8126964e7 100644 --- a/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt +++ b/tests/FlickerTests/AppLaunch/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt @@ -49,7 +49,7 @@ import org.junit.runners.Parameterized /** * Test the back and forward transition between 2 activities. * - * To run this test: `atest FlickerTests:TaskTransitionTest` + * To run this test: `atest FlickerTestsAppLaunch:TaskTransitionTest` * * Actions: * ``` diff --git a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml index 4d9fefbc7d88..ffdbb02984a7 100644 --- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml +++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml @@ -12,6 +12,10 @@ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- Turns off Wi-fi --> + <option name="wifi" value="off"/> + <!-- Turns off Bluetooth --> + <option name="bluetooth" value="off"/> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> @@ -41,6 +45,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/IME/Android.bp b/tests/FlickerTests/IME/Android.bp index f80e6b4b2f5e..cba3d09ebefd 100644 --- a/tests/FlickerTests/IME/Android.bp +++ b/tests/FlickerTests/IME/Android.bp @@ -24,27 +24,6 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -//////////////////////////////////////////////////////////////////////////////// -// Begin to cleanup after CL merges - -filegroup { - name: "FlickerTestsImeCommon-src", - srcs: ["src/**/common/*"], -} - -filegroup { - name: "FlickerTestsIme1-src", - srcs: ["src/**/Close*"], -} - -filegroup { - name: "FlickerTestsIme2-src", - srcs: ["src/**/ShowImeOnAppStart*"], -} - -// End to cleanup after CL merges -//////////////////////////////////////////////////////////////////////////////// - android_test { name: "FlickerTestsIme", defaults: ["FlickerTestsDefault"], @@ -60,67 +39,6 @@ android_test { } //////////////////////////////////////////////////////////////////////////////// -// Begin to cleanup after CL merges - -java_library { - name: "FlickerTestsImeCommon", - defaults: ["FlickerTestsDefault"], - srcs: [":FlickerTestsImeCommon-src"], - static_libs: ["FlickerTestsBase"], -} - -android_test { - name: "FlickerTestsIme1", - defaults: ["FlickerTestsDefault"], - manifest: "AndroidManifest.xml", - test_config_template: "AndroidTestTemplate.xml", - test_suites: [ - "device-tests", - "device-platinum-tests", - ], - srcs: [":FlickerTestsIme1-src"], - static_libs: [ - "FlickerTestsBase", - "FlickerTestsImeCommon", - ], - data: ["trace_config/*"], -} - -android_test { - name: "FlickerTestsIme2", - defaults: ["FlickerTestsDefault"], - manifest: "AndroidManifest.xml", - test_config_template: "AndroidTestTemplate.xml", - srcs: [":FlickerTestsIme2-src"], - static_libs: [ - "FlickerTestsBase", - "FlickerTestsImeCommon", - ], - data: ["trace_config/*"], -} - -android_test { - name: "FlickerTestsIme3", - defaults: ["FlickerTestsDefault"], - manifest: "AndroidManifest.xml", - test_config_template: "AndroidTestTemplate.xml", - srcs: ["src/**/*"], - exclude_srcs: [ - ":FlickerTestsIme1-src", - ":FlickerTestsIme2-src", - ":FlickerTestsImeCommon-src", - ], - static_libs: [ - "FlickerTestsBase", - "FlickerTestsImeCommon", - ], - data: ["trace_config/*"], -} - -// End to cleanup after CL merges -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// // Begin breakdowns for FlickerTestsIme module test_module_config { diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml index b879c54dcab3..12670cda74b2 100644 --- a/tests/FlickerTests/IME/AndroidTestTemplate.xml +++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml @@ -12,6 +12,10 @@ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- Turns off Wi-fi --> + <option name="wifi" value="off"/> + <!-- Turns off Bluetooth --> + <option name="bluetooth" value="off"/> <!-- enable AOD --> <option name="set-secure-setting" key="doze_always_on" value="1" /> <!-- prevents the phone from restarting --> @@ -43,6 +47,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt index 2b6ddcb43f18..48ca36ff8012 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt @@ -33,7 +33,7 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * To run this test: `atest FlickerTestsIme1:CloseImeOnDismissPopupDialogTest` + * To run this test: `atest FlickerTestsIme:CloseImeOnDismissPopupDialogTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt index 0344197c1425..e3f3aca135d1 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt @@ -34,7 +34,7 @@ import org.junit.runners.Parameterized /** * Test IME window closing to home transitions. - * To run this test: `atest FlickerTestsIme1:CloseImeOnGoHomeTest` + * To run this test: `atest FlickerTestsIme:CloseImeOnGoHomeTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt index fde1373b032b..3509e5bfecaf 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt @@ -42,7 +42,7 @@ import org.junit.runners.Parameterized * * More details on b/190352379 * - * To run this test: `atest FlickerTestsIme1:CloseImeShownOnAppStartOnGoHomeTest` + * To run this test: `atest FlickerTestsIme:CloseImeShownOnAppStartOnGoHomeTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt index ed6e8df3e293..53d7a3ff8f21 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt @@ -43,7 +43,7 @@ import org.junit.runners.Parameterized * * More details on b/190352379 * - * To run this test: `atest FlickerTestsIme1:CloseImeShownOnAppStartToAppOnPressBackTest` + * To run this test: `atest FlickerTestsIme:CloseImeShownOnAppStartToAppOnPressBackTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt index 522c68bba0d1..4bc2705aa17d 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt @@ -35,7 +35,7 @@ import org.junit.runners.Parameterized /** * Test IME window closing back to app window transitions. - * To run this test: `atest FlickerTestsIme1:CloseImeToAppOnPressBackTest` + * To run this test: `atest FlickerTestsIme:CloseImeToAppOnPressBackTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt index 05771e88fc83..6117bb0971d0 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt @@ -40,7 +40,7 @@ import org.junit.runners.Parameterized * Unlike {@link OpenImeWindowTest} testing IME window opening transitions, this test also verify * there is no flickering when back to the simple activity without requesting IME to show. * - * To run this test: `atest FlickerTestsIme1:CloseImeToHomeOnFinishActivityTest` + * To run this test: `atest FlickerTestsIme:CloseImeToHomeOnFinishActivityTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt index 336fe6f991ca..9b8d86d82007 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt @@ -37,7 +37,7 @@ import org.junit.runners.Parameterized /** * Test IME window shown on the app with fixing portrait orientation. - * To run this test: `atest FlickerTestsIme2:OpenImeWindowToFixedPortraitAppTest` + * To run this test: `atest FlickerTestsIme:OpenImeWindowToFixedPortraitAppTest` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt index 34a708578396..f806fae01eb4 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt @@ -34,7 +34,7 @@ import org.junit.runners.Parameterized /** * Test IME window opening transitions. - * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromOverviewTest` + * To run this test: `atest FlickerTestsIme:ShowImeOnAppStartWhenLaunchingAppFromOverviewTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt index 7c72c3187a7f..cc19f62a7cb3 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt @@ -36,7 +36,7 @@ import org.junit.runners.Parameterized /** * Test IME windows switching with 2-Buttons or gestural navigation. - * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest` + * To run this test: `atest FlickerTestsIme:ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt index fe5320cd1a46..4a4d3725d82c 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt @@ -36,7 +36,7 @@ import org.junit.runners.Parameterized /** * Launch an app that automatically displays the IME * - * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppTest` + * To run this test: `atest FlickerTestsIme:ShowImeOnAppStartWhenLaunchingAppTest` * * Actions: * ``` diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt index 82e53c81daaa..d47e7ad8d904 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt @@ -36,7 +36,7 @@ import org.junit.runners.Parameterized /** * Test IME window closing on lock and opening on screen unlock. - * To run this test: `atest FlickerTestsIme2:ShowImeOnUnlockScreenTest` + * To run this test: `atest FlickerTestsIme:ShowImeOnUnlockScreenTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt index 9eaf998ed63f..47bf32483d69 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt @@ -33,7 +33,7 @@ import org.junit.runners.Parameterized /** * Test IME window opening transitions. - * To run this test: `atest FlickerTestsIme2:ShowImeWhenFocusingOnInputFieldTest` + * To run this test: `atest FlickerTestsIme:ShowImeWhenFocusingOnInputFieldTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt index 7186a2c48c4c..e3118b4cae0c 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt @@ -41,7 +41,7 @@ import org.junit.runners.Parameterized /** * Test IME snapshot mechanism won't apply when transitioning from non-IME focused dialog activity. - * To run this test: `atest FlickerTestsIme2:ShowImeWhileDismissingThemedPopupDialogTest` + * To run this test: `atest FlickerTestsIme:ShowImeWhileDismissingThemedPopupDialogTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt index eb63e4985a9f..064c07ea0dc0 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt @@ -39,7 +39,7 @@ import org.junit.runners.Parameterized /** * Test IME window layer will be associated with the app task when going to the overview screen. - * To run this test: `atest FlickerTestsIme2:ShowImeWhileEnteringOverviewTest` + * To run this test: `atest FlickerTestsIme:ShowImeWhileEnteringOverviewTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml index 04b312a896b9..e2ac5a9579ae 100644 --- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml +++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml @@ -12,6 +12,10 @@ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- Turns off Wi-fi --> + <option name="wifi" value="off"/> + <!-- Turns off Bluetooth --> + <option name="bluetooth" value="off"/> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> @@ -41,6 +45,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt index ad70757a9a4d..da90c4f624d2 100644 --- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt +++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt @@ -16,6 +16,8 @@ package com.android.server.wm.flicker.notification +import android.platform.systemui_tapl.controller.NotificationIdentity +import android.platform.systemui_tapl.ui.Root import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.platform.test.rule.DisableNotificationCooldownSettingRule @@ -28,8 +30,6 @@ import android.tools.helpers.wakeUpAndGoToHomeScreen import android.tools.traces.component.ComponentNameMatcher import android.view.WindowInsets import android.view.WindowManager -import androidx.test.uiautomator.By -import androidx.test.uiautomator.Until import com.android.server.wm.flicker.helpers.NotificationAppHelper import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd @@ -87,8 +87,9 @@ open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) : .withWindowSurfaceDisappeared(ComponentNameMatcher.NOTIFICATION_SHADE) .waitForAndVerify() } + protected fun FlickerTestData.openAppFromNotification() { - doOpenAppAndWait(startY = 10, endY = 3 * device.displayHeight / 4, steps = 25) + doOpenAppAndWait() } protected fun FlickerTestData.openAppFromLockNotification() { @@ -101,25 +102,27 @@ open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) : WindowInsets.Type.statusBars() or WindowInsets.Type.displayCutout() ) - doOpenAppAndWait(startY = insets.top + 100, endY = device.displayHeight / 2, steps = 4) + doOpenAppAndWait() } - protected fun FlickerTestData.doOpenAppAndWait(startY: Int, endY: Int, steps: Int) { - // Swipe down to show the notification shade - val x = device.displayWidth / 2 - device.swipe(x, startY, x, endY, steps) - device.waitForIdle(2000) - instrumentation.uiAutomation.syncInputTransactions() + protected fun FlickerTestData.doOpenAppAndWait() { + val shade = Root.get().openNotificationShade() // Launch the activity by clicking the notification + // Post notification and ensure that it's collapsed val notification = - device.wait(Until.findObject(By.text("Flicker Test Notification")), 2000L) - notification?.click() ?: error("Notification not found") - instrumentation.uiAutomation.syncInputTransactions() + shade.notificationStack.findNotification( + NotificationIdentity( + type = NotificationIdentity.Type.BY_TEXT, + text = "Flicker Test Notification", + ) + ) + notification.clickToApp() // Wait for the app to launch wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify() } + @Presubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_warmStart() @Presubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_warmStart() diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml index 8acdabc2337d..1a4feb6e9eca 100644 --- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml +++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml @@ -12,6 +12,10 @@ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- Turns off Wi-fi --> + <option name="wifi" value="off"/> + <!-- Turns off Bluetooth --> + <option name="bluetooth" value="off"/> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> @@ -41,6 +45,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt index 9bb62e1e1794..1a32f2045546 100644 --- a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt +++ b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt @@ -38,7 +38,7 @@ import org.junit.runners.Parameterized /** * Test quick switching back to previous app from last opened app * - * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsBackTest` + * To run this test: `atest FlickerTestsQuickswitch:QuickSwitchBetweenTwoAppsBackTest` * * Actions: * ``` diff --git a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt index 491b9945d12d..d82dddd9eeeb 100644 --- a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt +++ b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt @@ -37,7 +37,7 @@ import org.junit.runners.Parameterized /** * Test quick switching back to previous app from last opened app * - * To run this test: `atest FlickerTests:QuickSwitchBetweenTwoAppsForwardTest` + * To run this test: `atest FlickerTestsQuickswitch:QuickSwitchBetweenTwoAppsForwardTest` * * Actions: * ``` diff --git a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt index de54c95da361..ab366286b6d8 100644 --- a/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt +++ b/tests/FlickerTests/QuickSwitch/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt @@ -38,7 +38,7 @@ import org.junit.runners.Parameterized /** * Test quick switching to last opened app from launcher * - * To run this test: `atest FlickerTests:QuickSwitchFromLauncherTest` + * To run this test: `atest FlickerTestsQuickswitch:QuickSwitchFromLauncherTest` * * Actions: * ``` diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml index 91ece214aad5..481a8bb66fee 100644 --- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml +++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml @@ -12,6 +12,10 @@ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on"/> + <!-- Turns off Wi-fi --> + <option name="wifi" value="off"/> + <!-- Turns off Bluetooth --> + <option name="bluetooth" value="off"/> <!-- prevents the phone from restarting --> <option name="force-skip-system-props" value="true"/> <!-- set WM tracing verbose level to all --> @@ -41,6 +45,8 @@ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> <option name="run-command" value="rm -rf /data/user/%TEST_USER%/files/*"/> + <!-- Disable AOD --> + <option name="run-command" value="settings put secure doze_always_on 0"/> <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1"/> <option name="run-command" value="settings put system show_touches 1"/> <option name="run-command" value="settings put system pointer_location 1"/> diff --git a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index 05ab364ed72c..49e2553ab4a1 100644 --- a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -48,7 +48,7 @@ import org.junit.runners.Parameterized * Stop tracing * ``` * - * To run this test: `atest FlickerTests:ChangeAppRotationTest` + * To run this test: `atest FlickerTestsRotation:ChangeAppRotationTest` * * To run only the presubmit assertions add: `-- * diff --git a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index a41362857420..d7f91e009c92 100644 --- a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -55,7 +55,7 @@ import org.junit.runners.Parameterized * Stop tracing * ``` * - * To run this test: `atest FlickerTests:SeamlessAppRotationTest` + * To run this test: `atest FlickerTestsRotation:SeamlessAppRotationTest` * * To run only the presubmit assertions add: `-- * diff --git a/tests/FlickerTests/test-apps/app-helpers/Android.bp b/tests/FlickerTests/test-apps/app-helpers/Android.bp index fc4d71c652d5..e8bb64aa6c55 100644 --- a/tests/FlickerTests/test-apps/app-helpers/Android.bp +++ b/tests/FlickerTests/test-apps/app-helpers/Android.bp @@ -25,7 +25,6 @@ package { java_library { name: "wm-flicker-common-app-helpers", - platform_apis: true, optimize: { enabled: false, }, diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt new file mode 100644 index 000000000000..6573c2c83f20 --- /dev/null +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.content.Intent +import android.tools.traces.parsers.toFlickerComponent +import com.android.server.wm.flicker.testapp.ActivityOptions + +class BottomHalfPipAppHelper( + instrumentation: Instrumentation, + private val useLaunchingActivity: Boolean = false, +) : PipAppHelper( + instrumentation, + appName = ActivityOptions.BottomHalfPip.LABEL, + componentNameMatcher = ActivityOptions.BottomHalfPip.COMPONENT + .toFlickerComponent() +) { + override val openAppIntent: Intent + get() = super.openAppIntent.apply { + component = if (useLaunchingActivity) { + ActivityOptions.BottomHalfPip.LAUNCHING_APP_COMPONENT + } else { + ActivityOptions.BottomHalfPip.COMPONENT + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt index c77413b6a55a..c1c5dc66bac1 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt @@ -16,18 +16,21 @@ package com.android.server.wm.flicker.helpers +import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.content.Context import android.graphics.Insets import android.graphics.Rect import android.graphics.Region import android.os.SystemClock -import android.platform.uiautomator_helpers.DeviceHelpers +import android.platform.uiautomatorhelpers.DeviceHelpers +import android.tools.PlatformConsts import android.tools.device.apphelpers.IStandardAppHelper import android.tools.helpers.SYSTEMUI_PACKAGE import android.tools.traces.parsers.WindowManagerStateHelper import android.tools.traces.wm.WindowingMode import android.view.WindowInsets import android.view.WindowManager +import android.window.DesktopModeFlags import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.uiautomator.By import androidx.test.uiautomator.BySelector @@ -35,8 +38,8 @@ import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until import com.android.server.wm.flicker.helpers.MotionEventHelper.InputMethod.TOUCH -import com.android.window.flags.Flags import java.time.Duration +import kotlin.math.abs /** * Wrapper class around App helper classes. This class adds functionality to the apps that the @@ -59,6 +62,11 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : BOTTOM } + enum class AppProperty { + STANDARD, + NON_RESIZABLE + } + /** Wait for an app moved to desktop to finish its transition. */ private fun waitForAppToMoveToDesktop(wmHelper: WindowManagerStateHelper) { wmHelper @@ -69,13 +77,28 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : .waitForAndVerify() } + /** Launch an app and ensure it's moved to Desktop if it has not. */ + fun enterDesktopMode( + wmHelper: WindowManagerStateHelper, + device: UiDevice, + motionEventHelper: MotionEventHelper = MotionEventHelper(getInstrumentation(), TOUCH), + ) { + innerHelper.launchViaIntent(wmHelper) + if (!isInDesktopWindowingMode(wmHelper)) { + enterDesktopModeWithDrag( + wmHelper = wmHelper, + device = device, + motionEventHelper = motionEventHelper + ) + } + } + /** Move an app to Desktop by dragging the app handle at the top. */ - fun enterDesktopWithDrag( + private fun enterDesktopModeWithDrag( wmHelper: WindowManagerStateHelper, device: UiDevice, motionEventHelper: MotionEventHelper = MotionEventHelper(getInstrumentation(), TOUCH) ) { - innerHelper.launchViaIntent(wmHelper) dragToDesktop( wmHelper = wmHelper, device = device, @@ -102,13 +125,9 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : // drag the window to move to desktop if (motionEventHelper.inputMethod == TOUCH - && Flags.enableHoldToDragAppHandle()) { + && DesktopModeFlags.ENABLE_HOLD_TO_DRAG_APP_HANDLE.isTrue) { // Touch requires hold-to-drag. - val downTime = SystemClock.uptimeMillis() - motionEventHelper.actionDown(startX, startY, time = downTime) - SystemClock.sleep(100L) // hold for 100ns before starting the move. - motionEventHelper.actionMove(startX, startY, startX, endY, 100, downTime = downTime) - motionEventHelper.actionUp(startX, endY, downTime = downTime) + motionEventHelper.holdToDrag(startX, startY, startX, endY, steps = 100) } else { device.drag(startX, startY, startX, endY, 100) } @@ -131,6 +150,48 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() } + private fun getMinimizeButtonForTheApp(caption: UiObject2?): UiObject2 { + return caption + ?.children + ?.find { it.resourceName.endsWith(MINIMIZE_BUTTON_VIEW) } + ?: error("Unable to find resource $MINIMIZE_BUTTON_VIEW\n") + } + + fun minimizeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice, isPip: Boolean = false) { + val caption = getCaptionForTheApp(wmHelper, device) + val minimizeButton = getMinimizeButtonForTheApp(caption) + minimizeButton.click() + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .apply { + if (isPip) withPipShown() + else + withWindowSurfaceDisappeared(innerHelper) + .withActivityState(innerHelper, PlatformConsts.STATE_STOPPED) + } + .waitForAndVerify() + } + + private fun getHeaderEmptyView(caption: UiObject2?): UiObject2 { + return caption + ?.children + ?.find { it.resourceName.endsWith(HEADER_EMPTY_VIEW) } + ?: error("Unable to find resource $HEADER_EMPTY_VIEW\n") + } + + /** Click on an existing window's header to bring it to the front. */ + fun bringToFront(wmHelper: WindowManagerStateHelper, device: UiDevice) { + val caption = getCaptionForTheApp(wmHelper, device) + val openHeaderView = getHeaderEmptyView(caption) + openHeaderView.click() + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withTopVisibleApp(innerHelper) + .waitForAndVerify() + } + /** Open maximize menu and click snap resize button on the app header for the given app. */ fun snapResizeDesktopApp( wmHelper: WindowManagerStateHelper, @@ -140,7 +201,7 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : ) { val caption = getCaptionForTheApp(wmHelper, device) val maximizeButton = getMaximizeButtonForTheApp(caption) - maximizeButton?.longClick() + maximizeButton.longClick() wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() val buttonResId = if (toLeft) SNAP_LEFT_BUTTON else SNAP_RIGHT_BUTTON @@ -162,11 +223,16 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : val expectedRect = Rect(displayRect).apply { if (toLeft) right -= expectedWidth else left += expectedWidth } - - wmHelper - .StateSyncBuilder() + wmHelper.StateSyncBuilder() .withAppTransitionIdle() - .withSurfaceVisibleRegion(this, Region(expectedRect)) + .withSurfaceMatchingVisibleRegion( + this, + Region(expectedRect), + { surfaceRegion, expectedRegion -> + areSnapWindowRegionsMatchingWithinThreshold( + surfaceRegion, expectedRegion, toLeft + ) + }) .waitForAndVerify() } @@ -280,7 +346,11 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : val displayRect = getDisplayRect(wmHelper) - val endX = if (isLeft) displayRect.left else displayRect.right + val endX = if (isLeft) { + displayRect.left + SNAP_RESIZE_DRAG_INSET + } else { + displayRect.right - SNAP_RESIZE_DRAG_INSET + } val endY = displayRect.centerY() / 2 // drag the window to snap resize @@ -324,6 +394,14 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : waitForTransitionToFullscreen(wmHelper) } + /** Maximize an app by dragging the app handle to the top drag zone. */ + fun maximizeAppWithDragToTopDragZone( + wmHelper: WindowManagerStateHelper, + device: UiDevice, + ) { + dragAppWindowToTopDragZone(wmHelper, device) + } + private fun dragAppWindowToTopDragZone(wmHelper: WindowManagerStateHelper, device: UiDevice) { val windowRect = wmHelper.getWindowRegion(innerHelper).bounds val displayRect = getDisplayRect(wmHelper) @@ -384,8 +462,40 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : return metricInsets.getInsetsIgnoringVisibility(typeMask) } + // Requirement of DesktopWindowingMode is having a minimum of 1 app in WINDOWING_MODE_FREEFORM. + private fun isInDesktopWindowingMode(wmHelper: WindowManagerStateHelper) = + wmHelper.getWindow(innerHelper)?.windowingMode == WINDOWING_MODE_FREEFORM + + private fun areSnapWindowRegionsMatchingWithinThreshold( + surfaceRegion: Region, expectedRegion: Region, toLeft: Boolean + ): Boolean { + val surfaceBounds = surfaceRegion.bounds + val expectedBounds = expectedRegion.bounds + // If snapped to left, right bounds will be cut off by the center divider. + // Else if snapped to right, the left bounds will be cut off. + val leftSideMatching: Boolean + val rightSideMatching: Boolean + if (toLeft) { + leftSideMatching = surfaceBounds.left == expectedBounds.left + rightSideMatching = + abs(surfaceBounds.right - expectedBounds.right) <= + surfaceBounds.right * SNAP_WINDOW_MAX_THRESHOLD_DIFF + } else { + leftSideMatching = + abs(surfaceBounds.left - expectedBounds.left) <= + surfaceBounds.left * SNAP_WINDOW_MAX_THRESHOLD_DIFF + rightSideMatching = surfaceBounds.right == expectedBounds.right + } + + return surfaceBounds.top == expectedBounds.top && + surfaceBounds.bottom == expectedBounds.bottom && + leftSideMatching && + rightSideMatching + } + private companion object { val TIMEOUT: Duration = Duration.ofSeconds(3) + const val SNAP_RESIZE_DRAG_INSET: Int = 5 // inset to avoid dragging to display edge const val CAPTION: String = "desktop_mode_caption" const val MAXIMIZE_BUTTON_VIEW: String = "maximize_button_view" const val MAXIMIZE_MENU: String = "maximize_menu" @@ -394,7 +504,16 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) : const val DESKTOP_MODE_BUTTON: String = "desktop_button" const val SNAP_LEFT_BUTTON: String = "maximize_menu_snap_left_button" const val SNAP_RIGHT_BUTTON: String = "maximize_menu_snap_right_button" + const val MINIMIZE_BUTTON_VIEW: String = "minimize_window" + const val HEADER_EMPTY_VIEW: String = "caption_handle" val caption: BySelector get() = By.res(SYSTEMUI_PACKAGE, CAPTION) + // In DesktopMode, window snap can be done with just a single window. In this case, the + // divider tiling between left and right window won't be shown, and hence its states are not + // obtainable in test. + // As the test should just focus on ensuring window goes to one side of the screen, an + // acceptable approach is to ensure snapped window still fills > 95% of either side of the + // screen. + const val SNAP_WINDOW_MAX_THRESHOLD_DIFF = 0.05 } } diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java deleted file mode 100644 index eeee7b4dfc6b..000000000000 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/GestureHelper.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm.flicker.helpers; - -import android.annotation.NonNull; -import android.app.Instrumentation; -import android.app.UiAutomation; -import android.os.SystemClock; -import android.view.InputDevice; -import android.view.InputEvent; -import android.view.MotionEvent; -import android.view.MotionEvent.PointerCoords; -import android.view.MotionEvent.PointerProperties; - -import androidx.annotation.Nullable; - -/** - * Injects gestures given an {@link Instrumentation} object. - */ -public class GestureHelper { - // Inserted after each motion event injection. - private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5; - - private final UiAutomation mUiAutomation; - - /** - * Primary pointer should be cached here for separate release - */ - @Nullable private PointerProperties mPrimaryPtrProp; - @Nullable private PointerCoords mPrimaryPtrCoord; - private long mPrimaryPtrDownTime; - - /** - * A pair of floating point values. - */ - public static class Tuple { - public float x; - public float y; - - public Tuple(float x, float y) { - this.x = x; - this.y = y; - } - } - - public GestureHelper(Instrumentation instrumentation) { - mUiAutomation = instrumentation.getUiAutomation(); - } - - /** - * Injects a series of {@link MotionEvent}s to simulate tapping. - * - * @param point coordinates of pointer to tap - * @param times the number of times to tap - */ - public boolean tap(@NonNull Tuple point, int times) throws InterruptedException { - PointerProperties ptrProp = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER); - PointerCoords ptrCoord = getPointerCoord(point.x, point.y, 1, 1); - - for (int i = 0; i <= times; i++) { - // If already tapped, inject delay in between movements - if (times > 0) { - SystemClock.sleep(50L); - } - if (!primaryPointerDown(ptrProp, ptrCoord, SystemClock.uptimeMillis())) { - return false; - } - // Delay before releasing tap - SystemClock.sleep(100L); - if (!primaryPointerUp(ptrProp, ptrCoord, SystemClock.uptimeMillis())) { - return false; - } - } - return true; - } - - /** - * Injects a series of {@link MotionEvent}s to simulate a drag gesture without pointer release. - * - * Simulates a drag gesture without releasing the primary pointer. The primary pointer info - * will be cached for potential release later on by {@code releasePrimaryPointer()} - * - * @param startPoint initial coordinates of the primary pointer - * @param endPoint final coordinates of the primary pointer - * @param steps number of steps to take to animate dragging - * @return true if gesture is injected successfully - */ - public boolean dragWithoutRelease(@NonNull Tuple startPoint, - @NonNull Tuple endPoint, int steps) { - PointerProperties ptrProp = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER); - PointerCoords ptrCoord = getPointerCoord(startPoint.x, startPoint.y, 1, 1); - - PointerProperties[] ptrProps = new PointerProperties[] { ptrProp }; - PointerCoords[] ptrCoords = new PointerCoords[] { ptrCoord }; - - long downTime = SystemClock.uptimeMillis(); - - if (!primaryPointerDown(ptrProp, ptrCoord, downTime)) { - return false; - } - - // cache the primary pointer info for later potential release - mPrimaryPtrProp = ptrProp; - mPrimaryPtrCoord = ptrCoord; - mPrimaryPtrDownTime = downTime; - - return movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint }, downTime, steps); - } - - /** - * Release primary pointer if previous gesture has cached the primary pointer info. - * - * @return true if the release was injected successfully - */ - public boolean releasePrimaryPointer() { - if (mPrimaryPtrProp != null && mPrimaryPtrCoord != null) { - return primaryPointerUp(mPrimaryPtrProp, mPrimaryPtrCoord, mPrimaryPtrDownTime); - } - - return false; - } - - /** - * Injects a series of {@link MotionEvent} objects to simulate a pinch gesture. - * - * @param startPoint1 initial coordinates of the first pointer - * @param startPoint2 initial coordinates of the second pointer - * @param endPoint1 final coordinates of the first pointer - * @param endPoint2 final coordinates of the second pointer - * @param steps number of steps to take to animate pinching - * @return true if gesture is injected successfully - */ - public boolean pinch(@NonNull Tuple startPoint1, @NonNull Tuple startPoint2, - @NonNull Tuple endPoint1, @NonNull Tuple endPoint2, int steps) { - PointerProperties ptrProp1 = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER); - PointerProperties ptrProp2 = getPointerProp(1, MotionEvent.TOOL_TYPE_FINGER); - - PointerCoords ptrCoord1 = getPointerCoord(startPoint1.x, startPoint1.y, 1, 1); - PointerCoords ptrCoord2 = getPointerCoord(startPoint2.x, startPoint2.y, 1, 1); - - PointerProperties[] ptrProps = new PointerProperties[] { - ptrProp1, ptrProp2 - }; - - PointerCoords[] ptrCoords = new PointerCoords[] { - ptrCoord1, ptrCoord2 - }; - - long downTime = SystemClock.uptimeMillis(); - - if (!primaryPointerDown(ptrProp1, ptrCoord1, downTime)) { - return false; - } - - if (!nonPrimaryPointerDown(ptrProps, ptrCoords, downTime, 1)) { - return false; - } - - if (!movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint1, endPoint2 }, - downTime, steps)) { - return false; - } - - if (!nonPrimaryPointerUp(ptrProps, ptrCoords, downTime, 1)) { - return false; - } - - return primaryPointerUp(ptrProp1, ptrCoord1, downTime); - } - - private boolean primaryPointerDown(@NonNull PointerProperties prop, - @NonNull PointerCoords coord, long downTime) { - MotionEvent event = getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, 1, - new PointerProperties[]{ prop }, new PointerCoords[]{ coord }); - - return injectEventSync(event); - } - - private boolean nonPrimaryPointerDown(@NonNull PointerProperties[] props, - @NonNull PointerCoords[] coords, long downTime, int index) { - // at least 2 pointers are needed - if (props.length != coords.length || coords.length < 2) { - return false; - } - - long eventTime = SystemClock.uptimeMillis(); - - MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_DOWN - + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords); - - return injectEventSync(event); - } - - private boolean movePointers(@NonNull PointerProperties[] props, - @NonNull PointerCoords[] coords, @NonNull Tuple[] endPoints, long downTime, int steps) { - // the number of endpoints should be the same as the number of pointers - if (props.length != coords.length || coords.length != endPoints.length) { - return false; - } - - // prevent division by 0 and negative number of steps - if (steps < 1) { - steps = 1; - } - - // save the starting points before updating any pointers - Tuple[] startPoints = new Tuple[coords.length]; - - for (int i = 0; i < coords.length; i++) { - startPoints[i] = new Tuple(coords[i].x, coords[i].y); - } - - MotionEvent event; - long eventTime; - - for (int i = 0; i < steps; i++) { - // inject a delay between movements - SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS); - - // update the coordinates - for (int j = 0; j < coords.length; j++) { - coords[j].x += (endPoints[j].x - startPoints[j].x) / steps; - coords[j].y += (endPoints[j].y - startPoints[j].y) / steps; - } - - eventTime = SystemClock.uptimeMillis(); - - event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_MOVE, - coords.length, props, coords); - - boolean didInject = injectEventSync(event); - - if (!didInject) { - return false; - } - } - - return true; - } - - private boolean primaryPointerUp(@NonNull PointerProperties prop, - @NonNull PointerCoords coord, long downTime) { - long eventTime = SystemClock.uptimeMillis(); - - MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_UP, 1, - new PointerProperties[]{ prop }, new PointerCoords[]{ coord }); - - return injectEventSync(event); - } - - private boolean nonPrimaryPointerUp(@NonNull PointerProperties[] props, - @NonNull PointerCoords[] coords, long downTime, int index) { - // at least 2 pointers are needed - if (props.length != coords.length || coords.length < 2) { - return false; - } - - long eventTime = SystemClock.uptimeMillis(); - - MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_UP - + (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords); - - return injectEventSync(event); - } - - private PointerCoords getPointerCoord(float x, float y, float pressure, float size) { - PointerCoords ptrCoord = new PointerCoords(); - ptrCoord.x = x; - ptrCoord.y = y; - ptrCoord.pressure = pressure; - ptrCoord.size = size; - return ptrCoord; - } - - private PointerProperties getPointerProp(int id, int toolType) { - PointerProperties ptrProp = new PointerProperties(); - ptrProp.id = id; - ptrProp.toolType = toolType; - return ptrProp; - } - - private static MotionEvent getMotionEvent(long downTime, long eventTime, int action, - int pointerCount, PointerProperties[] ptrProps, PointerCoords[] ptrCoords) { - return MotionEvent.obtain(downTime, eventTime, action, pointerCount, - ptrProps, ptrCoords, 0, 0, 1.0f, 1.0f, - 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); - } - - private boolean injectEventSync(InputEvent event) { - return mUiAutomation.injectInputEvent(event, true); - } -} diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt index 634b6eedd7e6..d5334cbd541c 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt @@ -21,6 +21,7 @@ import android.graphics.Rect import android.graphics.Region import android.tools.device.apphelpers.StandardAppHelper import android.tools.helpers.FIND_TIMEOUT +import android.tools.helpers.GestureHelper import android.tools.helpers.SYSTEMUI_PACKAGE import android.tools.traces.component.ComponentNameMatcher import android.tools.traces.parsers.WindowManagerStateHelper @@ -33,12 +34,13 @@ class LetterboxAppHelper @JvmOverloads constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.NonResizeablePortraitActivity.LABEL, + launcherName: String = ActivityOptions.NonResizeableFixedAspectRatioPortraitActivity.LABEL, component: ComponentNameMatcher = - ActivityOptions.NonResizeablePortraitActivity.COMPONENT.toFlickerComponent() + ActivityOptions.NonResizeableFixedAspectRatioPortraitActivity.COMPONENT.toFlickerComponent() ) : StandardAppHelper(instr, launcherName, component) { - private val gestureHelper: GestureHelper = GestureHelper(instrumentation) + private val gestureHelper: GestureHelper = + GestureHelper(instrumentation) fun clickRestart(wmHelper: WindowManagerStateHelper) { val restartButton = @@ -128,6 +130,6 @@ constructor( } companion object { - private const val BOUNDS_OFFSET: Int = 100 + private const val BOUNDS_OFFSET: Int = 50 } } diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt index 86a0b0f8c66e..1fe60888fa52 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt @@ -54,7 +54,15 @@ class MotionEventHelper( injectMotionEvent(ACTION_UP, x, y, downTime = downTime) } - fun actionMove(startX: Int, startY: Int, endX: Int, endY: Int, steps: Int, downTime: Long) { + fun actionMove( + startX: Int, + startY: Int, + endX: Int, + endY: Int, + steps: Int, + downTime: Long, + withMotionEventInjectDelay: Boolean = false + ) { val incrementX = (endX - startX).toFloat() / (steps - 1) val incrementY = (endY - startY).toFloat() / (steps - 1) @@ -65,9 +73,33 @@ class MotionEventHelper( val moveEvent = getMotionEvent(downTime, time, ACTION_MOVE, x, y) injectMotionEvent(moveEvent) + if (withMotionEventInjectDelay) { + SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS) + } } } + /** + * Drag from [startX], [startY] to [endX], [endY] with a "hold" period after touching down + * and before moving. + */ + fun holdToDrag(startX: Int, startY: Int, endX: Int, endY: Int, steps: Int) { + val downTime = SystemClock.uptimeMillis() + actionDown(startX, startY, time = downTime) + SystemClock.sleep(100L) // Hold before dragging. + actionMove( + startX, + startY, + endX, + endY, + steps, + downTime, + withMotionEventInjectDelay = true + ) + SystemClock.sleep(REGULAR_CLICK_LENGTH) + actionUp(startX, endX, downTime) + } + private fun injectMotionEvent( action: Int, x: Int, @@ -120,4 +152,9 @@ class MotionEventHelper( event.displayId = 0 return event } + + companion object { + private const val MOTION_EVENT_INJECTION_DELAY_MILLIS = 5L + private const val REGULAR_CLICK_LENGTH = 100L + } }
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt index 931e4f88aa8d..de17bf422c0c 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt @@ -18,29 +18,26 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation import android.content.Intent -import android.graphics.Rect import android.graphics.Region import android.media.session.MediaController import android.media.session.MediaSessionManager -import android.tools.datatypes.coversMoreThan -import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.apphelpers.BasePipAppHelper import android.tools.helpers.FIND_TIMEOUT import android.tools.helpers.SYSTEMUI_PACKAGE import android.tools.traces.ConditionsFactory +import android.tools.traces.component.ComponentNameMatcher import android.tools.traces.component.IComponentMatcher import android.tools.traces.parsers.WindowManagerStateHelper import android.tools.traces.parsers.toFlickerComponent -import android.util.Log import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions -open class PipAppHelper(instrumentation: Instrumentation) : - StandardAppHelper( - instrumentation, - ActivityOptions.Pip.LABEL, - ActivityOptions.Pip.COMPONENT.toFlickerComponent() - ) { +open class PipAppHelper( + instrumentation: Instrumentation, + appName: String = ActivityOptions.Pip.LABEL, + componentNameMatcher: ComponentNameMatcher = ActivityOptions.Pip.COMPONENT.toFlickerComponent(), +) : BasePipAppHelper(instrumentation, appName, componentNameMatcher) { private val mediaSessionManager: MediaSessionManager get() = context.getSystemService(MediaSessionManager::class.java) @@ -52,189 +49,6 @@ open class PipAppHelper(instrumentation: Instrumentation) : it.packageName == packageName } - private val gestureHelper: GestureHelper = GestureHelper(instrumentation) - - open fun clickObject(resId: String) { - val selector = By.res(packageName, resId) - val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object") - - obj.click() - } - - /** Drags the PIP window to the provided final coordinates without releasing the pointer. */ - fun dragPipWindowAwayFromEdgeWithoutRelease(wmHelper: WindowManagerStateHelper, steps: Int) { - val initWindowRect = Rect(getWindowRect(wmHelper)) - - // initial pointer at the center of the window - val initialCoord = - GestureHelper.Tuple( - initWindowRect.centerX().toFloat(), - initWindowRect.centerY().toFloat() - ) - - // the offset to the right (or left) of the window center to drag the window to - val offset = 50 - - // the actual final x coordinate with the offset included; - // if the pip window is closer to the right edge of the display the offset is negative - // otherwise the offset is positive - val endX = - initWindowRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) -1 else 1) - val finalCoord = GestureHelper.Tuple(endX.toFloat(), initWindowRect.centerY().toFloat()) - - // drag to the final coordinate - gestureHelper.dragWithoutRelease(initialCoord, finalCoord, steps) - } - - /** - * Releases the primary pointer. - * - * Injects the release of the primary pointer if the primary pointer info was cached after - * another gesture was injected without pointer release. - */ - fun releasePipAfterDragging() { - gestureHelper.releasePrimaryPointer() - } - - /** - * Drags the PIP window away from the screen edge while not crossing the display center. - * - * @throws IllegalStateException if default display bounds are not available - */ - fun dragPipWindowAwayFromEdge(wmHelper: WindowManagerStateHelper, steps: Int) { - val initWindowRect = Rect(getWindowRect(wmHelper)) - - // initial pointer at the center of the window - val startX = initWindowRect.centerX() - val y = initWindowRect.centerY() - - val displayRect = - wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect - ?: throw IllegalStateException("Default display is null") - - // the offset to the right (or left) of the display center to drag the window to - val offset = 20 - - // the actual final x coordinate with the offset included; - // if the pip window is closer to the right edge of the display the offset is positive - // otherwise the offset is negative - val endX = displayRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) 1 else -1) - - // drag the window to the left but not beyond the center of the display - uiDevice.drag(startX, y, endX, y, steps) - } - - /** - * Returns true if PIP window is closer to the right edge of the display than left. - * - * @throws IllegalStateException if default display bounds are not available - */ - fun isCloserToRightEdge(wmHelper: WindowManagerStateHelper): Boolean { - val windowRect = getWindowRect(wmHelper) - - val displayRect = - wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect - ?: throw IllegalStateException("Default display is null") - - return windowRect.centerX() > displayRect.centerX() - } - - /** - * Expands the PIP window by using the pinch out gesture. - * - * @param percent The percentage by which to increase the pip window size. - * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f - */ - fun pinchOpenPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) { - // the percentage must be between 0.0f and 1.0f - if (percent <= 0.0f || percent > 1.0f) { - throw IllegalArgumentException("Percent must be between 0.0f and 1.0f") - } - - val windowRect = getWindowRect(wmHelper) - - // first pointer's initial x coordinate is halfway between the left edge and the center - val initLeftX = (windowRect.centerX() - windowRect.width() / 4).toFloat() - // second pointer's initial x coordinate is halfway between the right edge and the center - val initRightX = (windowRect.centerX() + windowRect.width() / 4).toFloat() - - // horizontal distance the window should increase by - val distIncrease = windowRect.width() * percent - - // final x-coordinates - val finalLeftX = initLeftX - (distIncrease / 2) - val finalRightX = initRightX + (distIncrease / 2) - - // y-coordinate is the same throughout this animation - val yCoord = windowRect.centerY().toFloat() - - var adjustedSteps = MIN_STEPS_TO_ANIMATE - - // if distance per step is at least 1, then we can use the number of steps requested - if (distIncrease.toInt() / (steps * 2) >= 1) { - adjustedSteps = steps - } - - // if the distance per step is less than 1, carry out the animation in two steps - gestureHelper.pinch( - GestureHelper.Tuple(initLeftX, yCoord), - GestureHelper.Tuple(initRightX, yCoord), - GestureHelper.Tuple(finalLeftX, yCoord), - GestureHelper.Tuple(finalRightX, yCoord), - adjustedSteps - ) - - waitForPipWindowToExpandFrom(wmHelper, Region(windowRect)) - } - - /** - * Minimizes the PIP window by using the pinch in gesture. - * - * @param percent The percentage by which to decrease the pip window size. - * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f - */ - fun pinchInPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) { - // the percentage must be between 0.0f and 1.0f - if (percent <= 0.0f || percent > 1.0f) { - throw IllegalArgumentException("Percent must be between 0.0f and 1.0f") - } - - val windowRect = getWindowRect(wmHelper) - - // first pointer's initial x coordinate is halfway between the left edge and the center - val initLeftX = (windowRect.centerX() - windowRect.width() / 4).toFloat() - // second pointer's initial x coordinate is halfway between the right edge and the center - val initRightX = (windowRect.centerX() + windowRect.width() / 4).toFloat() - - // decrease by the distance specified through the percentage - val distDecrease = windowRect.width() * percent - - // get the final x-coordinates and make sure they are not passing the center of the window - val finalLeftX = Math.min(initLeftX + (distDecrease / 2), windowRect.centerX().toFloat()) - val finalRightX = Math.max(initRightX - (distDecrease / 2), windowRect.centerX().toFloat()) - - // y-coordinate is the same throughout this animation - val yCoord = windowRect.centerY().toFloat() - - var adjustedSteps = MIN_STEPS_TO_ANIMATE - - // if distance per step is at least 1, then we can use the number of steps requested - if (distDecrease.toInt() / (steps * 2) >= 1) { - adjustedSteps = steps - } - - // if the distance per step is less than 1, carry out the animation in two steps - gestureHelper.pinch( - GestureHelper.Tuple(initLeftX, yCoord), - GestureHelper.Tuple(initRightX, yCoord), - GestureHelper.Tuple(finalLeftX, yCoord), - GestureHelper.Tuple(finalRightX, yCoord), - adjustedSteps - ) - - waitForPipWindowToMinimizeFrom(wmHelper, Region(windowRect)) - } - /** * Launches the app through an intent instead of interacting with the launcher and waits until * the app window is in PIP mode @@ -250,18 +64,13 @@ open class PipAppHelper(instrumentation: Instrumentation) : wmHelper, launchedAppComponentMatcherOverride, action, - stringExtras, - waitConditionsBuilder = - wmHelper - .StateSyncBuilder() - .add(ConditionsFactory.isWMStateComplete()) - .withAppTransitionIdle() - .add(ConditionsFactory.hasPipWindow()) + stringExtras ) wmHelper .StateSyncBuilder() .withWindowSurfaceAppeared(this) + .add(ConditionsFactory.isWMStateComplete()) .withPipShown() .waitForAndVerify() } @@ -336,126 +145,6 @@ open class PipAppHelper(instrumentation: Instrumentation) : closePipWindow(WindowManagerStateHelper(instrumentation)) } - /** Returns the pip window bounds. */ - fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect { - val windowRegion = wmHelper.getWindowRegion(this) - require(!windowRegion.isEmpty) { "Unable to find a PIP window in the current state" } - return windowRegion.bounds - } - - /** Taps the pip window and dismisses it by clicking on the X button. */ - open fun closePipWindow(wmHelper: WindowManagerStateHelper) { - val windowRect = getWindowRect(wmHelper) - uiDevice.click(windowRect.centerX(), windowRect.centerY()) - // search and interact with the dismiss button - val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss") - uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT) - val dismissPipObject = - uiDevice.findObject(dismissSelector) ?: error("PIP window dismiss button not found") - val dismissButtonBounds = dismissPipObject.visibleBounds - uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY()) - - // Wait for animation to complete. - wmHelper.StateSyncBuilder().withPipGone().withHomeActivityVisible().waitForAndVerify() - } - - open fun tapPipToShowMenu(wmHelper: WindowManagerStateHelper) { - val windowRect = getWindowRect(wmHelper) - uiDevice.click(windowRect.centerX(), windowRect.centerY()) - // search and interact with the dismiss button - val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss") - uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT) - } - - /** Close the pip window by pressing the expand button */ - fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) { - val windowRect = getWindowRect(wmHelper) - uiDevice.click(windowRect.centerX(), windowRect.centerY()) - // search and interact with the expand button - val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button") - uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT) - val expandPipObject = - uiDevice.findObject(expandSelector) ?: error("PIP window expand button not found") - val expandButtonBounds = expandPipObject.visibleBounds - uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY()) - wmHelper.StateSyncBuilder().withPipGone().withFullScreenApp(this).waitForAndVerify() - } - - /** Double click on the PIP window to expand it */ - fun doubleClickPipWindow(wmHelper: WindowManagerStateHelper) { - val windowRect = getWindowRect(wmHelper) - Log.d(TAG, "First click") - uiDevice.click(windowRect.centerX(), windowRect.centerY()) - Log.d(TAG, "Second click") - uiDevice.click(windowRect.centerX(), windowRect.centerY()) - Log.d(TAG, "Wait for app transition to end") - wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() - waitForPipWindowToExpandFrom(wmHelper, Region(windowRect)) - } - - private fun waitForPipWindowToExpandFrom( - wmHelper: WindowManagerStateHelper, - windowRect: Region - ) { - wmHelper - .StateSyncBuilder() - .add("pipWindowExpanded") { - val pipAppWindow = - it.wmState.visibleWindows.firstOrNull { window -> - this.windowMatchesAnyOf(window) - } - ?: return@add false - val pipRegion = pipAppWindow.frameRegion - return@add pipRegion.coversMoreThan(windowRect) - } - .waitForAndVerify() - } - - private fun waitForPipWindowToMinimizeFrom( - wmHelper: WindowManagerStateHelper, - windowRect: Region - ) { - wmHelper - .StateSyncBuilder() - .add("pipWindowMinimized") { - val pipAppWindow = - it.wmState.visibleWindows.firstOrNull { window -> - this.windowMatchesAnyOf(window) - } - Log.d(TAG, "window " + pipAppWindow) - if (pipAppWindow == null) return@add false - val pipRegion = pipAppWindow.frameRegion - Log.d( - TAG, - "region " + pipRegion + " covers " + windowRect.coversMoreThan(pipRegion) - ) - return@add windowRect.coversMoreThan(pipRegion) - } - .waitForAndVerify() - } - - /** - * Waits until the PIP window snaps horizontally to the provided bounds. - * - * @param finalBounds the bounds to wait for PIP window to snap to - */ - fun waitForPipToSnapTo(wmHelper: WindowManagerStateHelper, finalBounds: android.graphics.Rect) { - wmHelper - .StateSyncBuilder() - .add("pipWindowSnapped") { - val pipAppWindow = - it.wmState.visibleWindows.firstOrNull { window -> - this.windowMatchesAnyOf(window) - } - ?: return@add false - val pipRegionBounds = pipAppWindow.frameRegion.bounds - return@add pipRegionBounds.left == finalBounds.left && - pipRegionBounds.right == finalBounds.right - } - .add(ConditionsFactory.isWMStateComplete()) - .waitForAndVerify() - } - companion object { private const val TAG = "PipAppHelper" private const val ENTER_PIP_BUTTON_ID = "enter_pip" @@ -464,8 +153,5 @@ open class PipAppHelper(instrumentation: Instrumentation) : private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual" private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter" private const val SOURCE_RECT_HINT = "set_source_rect_hint" - // minimum number of steps to take, when animating gestures, needs to be 2 - // so that there is at least a single intermediate layer that flicker tests can check - private const val MIN_STEPS_TO_ANIMATE = 2 } -} +}
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt index 69fde0168b14..9e488486e16a 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt @@ -65,10 +65,45 @@ constructor( .waitForAndVerify() } + fun startSingleAppMediaProjectionWithExtraIntent( + wmHelper: WindowManagerStateHelper, + targetApp: StandardAppHelper + ) { + clickStartMediaProjectionWithExtraIntentButton() + chooseSingleAppOption() + startScreenSharing() + selectTargetApp(targetApp.appName) + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withHomeActivityVisible() + .waitForAndVerify() + } + + fun startSingleAppMediaProjectionFromRecents( + wmHelper: WindowManagerStateHelper, + targetApp: StandardAppHelper, + recentTasksIndex: Int = 0, + ) { + clickStartMediaProjectionButton() + chooseSingleAppOption() + startScreenSharing() + selectTargetAppRecent(recentTasksIndex) + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withWindowSurfaceAppeared(targetApp) + .waitForAndVerify() + } + private fun clickStartMediaProjectionButton() { findObject(By.res(packageName, START_MEDIA_PROJECTION_BUTTON_ID)).also { it.click() } } + private fun clickStartMediaProjectionWithExtraIntentButton() { + findObject(By.res(packageName, START_MEDIA_PROJECTION_NEW_INTENT_BUTTON_ID)).also { it.click() } + } + private fun chooseEntireScreenOption() { findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() } @@ -92,6 +127,13 @@ constructor( findObject(By.text(targetAppName)).also { it.click() } } + private fun selectTargetAppRecent(recentTasksIndex: Int) { + // Scroll to to find target app to launch then click app icon it to start capture + val recentsTasksRecycler = + findObject(By.res(SYSTEMUI_PACKAGE, MEDIA_PROJECTION_RECENT_TASKS)) + recentsTasksRecycler.children[recentTasksIndex].also{ it.click() } + } + private fun chooseSingleAppOption() { findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() } @@ -116,8 +158,10 @@ constructor( const val TIMEOUT: Long = 5000L const val ACCEPT_RESOURCE_ID: String = "android:id/button1" const val START_MEDIA_PROJECTION_BUTTON_ID: String = "button_start_mp" + const val START_MEDIA_PROJECTION_NEW_INTENT_BUTTON_ID: String = "button_start_mp_new_intent" val SCREEN_SHARE_OPTIONS_PATTERN: Pattern = Pattern.compile("$SYSTEMUI_PACKAGE:id/screen_share_mode_(options|spinner)") + const val MEDIA_PROJECTION_RECENT_TASKS: String = "media_projection_recent_tasks_recycler" const val ENTIRE_SCREEN_STRING_RES_NAME: String = "screen_share_permission_dialog_option_entire_screen" const val SINGLE_APP_STRING_RES_NAME: String = diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index f891606f0066..7c24a4adca3d 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -115,6 +115,19 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity android:name=".NonResizeableFixedAspectRatioPortraitActivity" + android:theme="@style/CutoutNever" + android:resizeableActivity="false" + android:screenOrientation="portrait" + android:minAspectRatio="1.77" + android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableFixedAspectRatioPortraitActivity" + android:label="NonResizeableFixedAspectRatioPortraitActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> <activity android:name=".StartMediaProjectionActivity" android:theme="@style/CutoutNever" android:resizeableActivity="false" @@ -143,6 +156,7 @@ <activity android:name=".LaunchTransparentActivity" android:resizeableActivity="false" android:screenOrientation="portrait" + android:minAspectRatio="1.77" android:theme="@style/OptOutEdgeToEdge" android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity" android:label="LaunchTransparentActivity" @@ -333,12 +347,34 @@ <category android:name="android.intent.category.LEANBACK_LAUNCHER"/> </intent-filter> </activity> + <activity android:name=".BottomHalfPipLaunchingActivity" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" + android:taskAffinity="com.android.server.wm.flicker.testapp.BottomHalfPipLaunchingActivity" + android:theme="@style/CutoutShortEdges" + android:label="BottomHalfPipLaunchingActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity + android:name=".BottomHalfPipActivity" + android:resizeableActivity="true" + android:supportsPictureInPicture="true" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" + android:taskAffinity="com.android.server.wm.flicker.testapp.BottomHalfPipLaunchingActivity" + android:theme="@style/TranslucentTheme" + android:label="BottomHalfPipActivity" + android:exported="true"> + </activity> <activity android:name=".SplitScreenActivity" android:resizeableActivity="true" android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenActivity" android:theme="@style/CutoutShortEdges" android:label="SplitScreenPrimaryActivity" - android:exported="true"> + android:exported="true" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> @@ -349,7 +385,8 @@ android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenSecondaryActivity" android:theme="@style/CutoutShortEdges" android:label="SplitScreenSecondaryActivity" - android:exported="true"> + android:exported="true" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml index 46f01e6c9752..c34d2003ef42 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml @@ -16,17 +16,27 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" android:orientation="vertical" android:background="@android:color/holo_orange_light"> <Button android:id="@+id/button_start_mp" - android:layout_width="500dp" - android:layout_height="500dp" + android:layout_margin="16dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" android:gravity="center_vertical|center_horizontal" android:text="Start Media Projection" android:textAppearance="?android:attr/textAppearanceLarge"/> + <Button + android:id="@+id/button_start_mp_new_intent" + android:layout_margin="16dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center_vertical|center_horizontal" + android:text="Start Media Projection with extra intent" + android:textAppearance="?android:attr/textAppearanceLarge"/> </LinearLayout>
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml index 47d113717ae0..837d050b73ff 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml @@ -62,6 +62,12 @@ <item name="android:backgroundDimEnabled">false</item> </style> + <style name="TranslucentTheme" parent="@style/OptOutEdgeToEdge"> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowContentOverlay">@null</item> + <item name="android:backgroundDimEnabled">false</item> + </style> + <style name="no_starting_window" parent="@style/OptOutEdgeToEdge"> <item name="android:windowDisablePreview">true</item> </style> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index e4de2c574553..0c1ac9951d32 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -85,6 +85,12 @@ public class ActivityOptions { FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity"); } + public static class NonResizeableFixedAspectRatioPortraitActivity { + public static final String LABEL = "NonResizeableFixedAspectRatioPortraitActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".NonResizeableFixedAspectRatioPortraitActivity"); + } + public static class StartMediaProjectionActivity { public static final String LABEL = "StartMediaProjectionActivity"; public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, @@ -235,6 +241,21 @@ public class ActivityOptions { FLICKER_APP_PACKAGE + ".PipActivity"); } + public static class BottomHalfPip { + public static final String LAUNCHING_APP_LABEL = "BottomHalfPipLaunchingActivity"; + // Test App > Bottom Half PIP Activity + public static final String LABEL = "BottomHalfPipActivity"; + + // Use the bottom half layout for PIP Activity + public static final String EXTRA_BOTTOM_HALF_LAYOUT = "bottom_half"; + + public static final ComponentName LAUNCHING_APP_COMPONENT = new ComponentName( + FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".BottomHalfPipLaunchingActivity"); + + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".BottomHalfPipActivity"); + } + public static class SplitScreen { public static class Primary { public static final String LABEL = "SplitScreenPrimaryActivity"; diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java new file mode 100644 index 000000000000..3d4865572486 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import android.app.Activity; +import android.content.res.Configuration; +import android.os.Bundle; +import android.view.ViewGroup.LayoutParams; +import android.view.WindowManager; + +import androidx.annotation.NonNull; + +public class BottomHalfPipActivity extends PipActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setTheme(R.style.TranslucentTheme); + updateLayout(); + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + updateLayout(); + } + + /** + * Sets to match parent layout if the activity is + * {@link Activity#isInPictureInPictureMode()}. Otherwise, set to bottom half + * layout. + * + * @see #setToBottomHalfMode(boolean) + */ + private void updateLayout() { + setToBottomHalfMode(!isInPictureInPictureMode()); + } + + /** + * Sets `useBottomHalfLayout` to `true` to use the bottom half layout. Use the + * [LayoutParams.MATCH_PARENT] layout. + */ + private void setToBottomHalfMode(boolean useBottomHalfLayout) { + final WindowManager.LayoutParams attrs = getWindow().getAttributes(); + if (useBottomHalfLayout) { + final int taskHeight = getWindowManager().getCurrentWindowMetrics().getBounds() + .height(); + attrs.y = taskHeight / 2; + attrs.height = taskHeight / 2; + } else { + attrs.y = 0; + attrs.height = LayoutParams.MATCH_PARENT; + } + getWindow().setAttributes(attrs); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java new file mode 100644 index 000000000000..d9d4361411bb --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import android.content.Intent; +import android.os.Bundle; + +public class BottomHalfPipLaunchingActivity extends SimpleActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final Intent intent = new Intent(this, BottomHalfPipActivity.class); + startActivity(intent); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java new file mode 100644 index 000000000000..be38c259d00d --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import android.app.Activity; +import android.os.Bundle; + +public class NonResizeableFixedAspectRatioPortraitActivity extends Activity { + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.activity_non_resizeable); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java index a24a48269d7c..b29b87450197 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java @@ -19,7 +19,8 @@ package com.android.server.wm.flicker.testapp; import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.EXTRA_MESSENGER; import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.MSG_SERVICE_DESTROYED; import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.MSG_START_FOREGROUND_DONE; -import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.REQUEST_CODE; +import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.REQUEST_CODE_NORMAL; +import static com.android.wm.shell.flicker.utils.MediaProjectionUtils.REQUEST_CODE_EXTRA_INTENT; import android.app.Activity; import android.content.ComponentName; @@ -71,13 +72,17 @@ public class StartMediaProjectionActivity extends Activity { setContentView(R.layout.activity_start_media_projection); Button startMediaProjectionButton = findViewById(R.id.button_start_mp); + Button startMediaProjectionButton2 = findViewById(R.id.button_start_mp_new_intent); startMediaProjectionButton.setOnClickListener(v -> - startActivityForResult(mService.createScreenCaptureIntent(), REQUEST_CODE)); + startActivityForResult(mService.createScreenCaptureIntent(), REQUEST_CODE_NORMAL)); + startMediaProjectionButton2.setOnClickListener(v -> + startActivityForResult(mService.createScreenCaptureIntent(), + REQUEST_CODE_EXTRA_INTENT)); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode != REQUEST_CODE) { + if (requestCode != REQUEST_CODE_NORMAL && requestCode != REQUEST_CODE_EXTRA_INTENT) { throw new IllegalStateException("Unknown request code: " + requestCode); } if (resultCode != RESULT_OK) { @@ -85,6 +90,11 @@ public class StartMediaProjectionActivity extends Activity { } Log.d(TAG, "onActivityResult"); startMediaProjectionService(resultCode, data); + if (requestCode == REQUEST_CODE_EXTRA_INTENT) { + Intent startMain = new Intent(Intent.ACTION_MAIN); + startMain.addCategory(Intent.CATEGORY_HOME); + startActivity(startMain); + } } private void startMediaProjectionService(int resultCode, Intent resultData) { @@ -122,7 +132,7 @@ public class StartMediaProjectionActivity extends Activity { displayBounds.width(), displayBounds.height(), PixelFormat.RGBA_8888, 1); mVirtualDisplay = mMediaProjection.createVirtualDisplay( - "DanielDisplay", + "TestDisplay", displayBounds.width(), displayBounds.height(), DisplayMetrics.DENSITY_HIGH, diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp index bacb5eb1cfdf..1f0bd61b5c3f 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -38,6 +38,7 @@ android_test { "hamcrest-library", "junit-params", "kotlin-test", + "mockito-kotlin-nodeps", "mockito-target-extended-minus-junit4", "platform-test-annotations", "platform-screenshot-diff-core", diff --git a/tests/Input/res/xml/bookmarks.xml b/tests/Input/res/xml/bookmarks.xml new file mode 100644 index 000000000000..68ec1233cdd7 --- /dev/null +++ b/tests/Input/res/xml/bookmarks.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<bookmarks xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <!-- the key combinations for the following shortcuts must be in sync + with the key combinations sent by the test in KeyGestureControllerTests.java --> + <bookmark + role="android.app.role.BROWSER" + androidprv:keycode="KEYCODE_B" + androidprv:modifierState="META" /> + <bookmark + category="android.intent.category.APP_CONTACTS" + androidprv:keycode="KEYCODE_P" + androidprv:modifierState="META" /> + <bookmark + category="android.intent.category.APP_EMAIL" + androidprv:keycode="KEYCODE_E" + androidprv:modifierState="META" /> + <bookmark + category="android.intent.category.APP_CALENDAR" + androidprv:keycode="KEYCODE_C" + androidprv:modifierState="META" /> + <bookmark + category="android.intent.category.APP_MAPS" + androidprv:keycode="KEYCODE_M" + androidprv:modifierState="META" /> + <bookmark + category="android.intent.category.APP_CALCULATOR" + androidprv:keycode="KEYCODE_U" + androidprv:modifierState="META" /> + + <bookmark + role="android.app.role.BROWSER" + androidprv:keycode="KEYCODE_B" + androidprv:modifierState="META|SHIFT" /> + + <bookmark + category="android.intent.category.APP_CONTACTS" + androidprv:keycode="KEYCODE_P" + shift="true" /> + + <bookmark + package="com.test" + class="com.test.BookmarkTest" + androidprv:keycode="KEYCODE_J" + shift="true" /> +</bookmarks> diff --git a/tests/Input/res/xml/bookmarks_legacy.xml b/tests/Input/res/xml/bookmarks_legacy.xml new file mode 100644 index 000000000000..78cc48b19416 --- /dev/null +++ b/tests/Input/res/xml/bookmarks_legacy.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<bookmarks> + <!-- The key combinations for the following shortcuts are legacy way defining bookmarks and we + should prefer new way of defining bookmarks as shown in {@link bookmarks.xml} --> + <bookmark + role="android.app.role.BROWSER" + shortcut="b" /> + <bookmark + category="android.intent.category.APP_CONTACTS" + shortcut="p" /> + <bookmark + category="android.intent.category.APP_EMAIL" + shortcut="e" /> + <bookmark + category="android.intent.category.APP_CALENDAR" + shortcut="c" /> + <bookmark + category="android.intent.category.APP_MAPS" + shortcut="m" /> + <bookmark + category="android.intent.category.APP_CALCULATOR" + shortcut="u" /> + + <bookmark + role="android.app.role.BROWSER" + shortcut="b" + shift="true" /> + + <bookmark + category="android.intent.category.APP_CONTACTS" + shortcut="p" + shift="true" /> + + <bookmark + package="com.test" + class="com.test.BookmarkTest" + shortcut="j" + shift="true" /> +</bookmarks> diff --git a/tests/Input/res/xml/keyboard_glyph_maps.xml b/tests/Input/res/xml/keyboard_glyph_maps.xml index d0616ff5ccaa..42561c1a9923 100644 --- a/tests/Input/res/xml/keyboard_glyph_maps.xml +++ b/tests/Input/res/xml/keyboard_glyph_maps.xml @@ -19,4 +19,8 @@ androidprv:glyphMap="@xml/test_glyph_map" androidprv:vendorId="0x1234" androidprv:productId="0x3456" /> + <keyboard-glyph-map + androidprv:glyphMap="@xml/test_glyph_map2" + androidprv:vendorId="0x1235" + androidprv:productId="0x3457" /> </keyboard-glyph-maps>
\ No newline at end of file diff --git a/tests/Input/res/xml/test_glyph_map2.xml b/tests/Input/res/xml/test_glyph_map2.xml new file mode 100644 index 000000000000..7a7c1accb7fd --- /dev/null +++ b/tests/Input/res/xml/test_glyph_map2.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<keyboard-glyph-map xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <key-glyph + androidprv:keycode="KEYCODE_BACK" + androidprv:glyphDrawable="@drawable/test_key_drawable" /> + <modifier-glyph + androidprv:modifier="META" + androidprv:glyphDrawable="@drawable/test_modifier_drawable" /> + <function-row-key androidprv:keycode="KEYCODE_EMOJI_PICKER" /> + <hardware-defined-shortcut + androidprv:keycode="KEYCODE_1" + androidprv:modifierState="FUNCTION" + androidprv:outKeycode="KEYCODE_BACK" /> + <hardware-defined-shortcut + androidprv:keycode="KEYCODE_2" + androidprv:modifierState="FUNCTION|META" + androidprv:outKeycode="KEYCODE_HOME" /> +</keyboard-glyph-map>
\ No newline at end of file diff --git a/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt b/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt index 90dff47ab706..a1e165551b5b 100644 --- a/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt +++ b/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt @@ -24,17 +24,16 @@ import android.os.test.TestLooper import android.platform.test.annotations.Presubmit import androidx.test.core.app.ApplicationProvider import com.android.server.testutils.any +import com.android.test.input.MockInputManagerRule import java.util.concurrent.Executor import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlin.test.fail -import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.anyInt import org.mockito.Mockito.doAnswer @@ -61,9 +60,8 @@ class InputDeviceBatteryListenerTest { private lateinit var context: Context private lateinit var inputManager: InputManager - @Mock - private lateinit var iInputManagerMock: IInputManager - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession + @get:Rule + val inputManagerRule = MockInputManagerRule() @Before fun setUp() { @@ -72,7 +70,6 @@ class InputDeviceBatteryListenerTest { executor = HandlerExecutor(Handler(testLooper.looper)) registeredListener = null monitoredDevices.clear() - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock) inputManager = InputManager(context) `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) .thenReturn(inputManager) @@ -92,7 +89,7 @@ class InputDeviceBatteryListenerTest { monitoredDevices.add(deviceId) registeredListener = listener null - }.`when`(iInputManagerMock).registerBatteryListener(anyInt(), any()) + }.`when`(inputManagerRule.mock).registerBatteryListener(anyInt(), any()) // Handle battery listener being unregistered. doAnswer { @@ -108,14 +105,7 @@ class InputDeviceBatteryListenerTest { if (monitoredDevices.isEmpty()) { registeredListener = null } - }.`when`(iInputManagerMock).unregisterBatteryListener(anyInt(), any()) - } - - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } + }.`when`(inputManagerRule.mock).unregisterBatteryListener(anyInt(), any()) } private fun notifyBatteryStateChanged( diff --git a/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java b/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java index 080186e4a2c1..3fc9ce12e718 100644 --- a/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java +++ b/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java @@ -45,15 +45,14 @@ import android.view.InputDevice; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.test.input.MockInputManagerRule; + import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.Arrays; @@ -73,23 +72,22 @@ public class InputDeviceLightsManagerTest { private static final int DEVICE_ID = 1000; private static final int PLAYER_ID = 3; - @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + @Rule + public final MockInputManagerRule mInputManagerRule = new MockInputManagerRule(); private InputManager mInputManager; - @Mock private IInputManager mIInputManagerMock; private InputManagerGlobal.TestSession mInputManagerGlobalSession; @Before public void setUp() throws Exception { final Context context = spy( new ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext())); - when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID}); + when(mInputManagerRule.getMock().getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID}); - when(mIInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn( + when(mInputManagerRule.getMock().getInputDevice(eq(DEVICE_ID))).thenReturn( createInputDevice(DEVICE_ID)); - mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock); mInputManager = new InputManager(context); when(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(mInputManager); @@ -102,7 +100,7 @@ public class InputDeviceLightsManagerTest { lightStatesById.put(lightIds[i], lightStates[i]); } return null; - }).when(mIInputManagerMock).setLightStates(eq(DEVICE_ID), + }).when(mInputManagerRule.getMock()).setLightStates(eq(DEVICE_ID), any(int[].class), any(LightState[].class), any(IBinder.class)); doAnswer(invocation -> { @@ -111,7 +109,7 @@ public class InputDeviceLightsManagerTest { return lightStatesById.get(lightId); } return new LightState(0); - }).when(mIInputManagerMock).getLightState(eq(DEVICE_ID), anyInt()); + }).when(mInputManagerRule.getMock()).getLightState(eq(DEVICE_ID), anyInt()); } @After @@ -130,7 +128,7 @@ public class InputDeviceLightsManagerTest { private void mockLights(Light[] lights) throws Exception { // Mock the Lights returned form InputManagerService - when(mIInputManagerMock.getLights(eq(DEVICE_ID))).thenReturn( + when(mInputManagerRule.getMock().getLights(eq(DEVICE_ID))).thenReturn( new ArrayList(Arrays.asList(lights))); } @@ -151,7 +149,7 @@ public class InputDeviceLightsManagerTest { LightsManager lightsManager = device.getLightsManager(); List<Light> lights = lightsManager.getLights(); - verify(mIInputManagerMock).getLights(eq(DEVICE_ID)); + verify(mInputManagerRule.getMock()).getLights(eq(DEVICE_ID)); assertEquals(lights, Arrays.asList(mockedLights)); } @@ -185,9 +183,9 @@ public class InputDeviceLightsManagerTest { .build()); IBinder token = session.getToken(); - verify(mIInputManagerMock).openLightSession(eq(DEVICE_ID), + verify(mInputManagerRule.getMock()).openLightSession(eq(DEVICE_ID), any(String.class), eq(token)); - verify(mIInputManagerMock).setLightStates(eq(DEVICE_ID), eq(new int[]{1, 2, 3}), + verify(mInputManagerRule.getMock()).setLightStates(eq(DEVICE_ID), eq(new int[]{1, 2, 3}), eq(states), eq(token)); // Then all 3 should turn on. @@ -204,7 +202,7 @@ public class InputDeviceLightsManagerTest { // close session session.close(); - verify(mIInputManagerMock).closeLightSession(eq(DEVICE_ID), eq(token)); + verify(mInputManagerRule.getMock()).closeLightSession(eq(DEVICE_ID), eq(token)); } @Test @@ -232,9 +230,9 @@ public class InputDeviceLightsManagerTest { .build()); IBinder token = session.getToken(); - verify(mIInputManagerMock).openLightSession(eq(DEVICE_ID), + verify(mInputManagerRule.getMock()).openLightSession(eq(DEVICE_ID), any(String.class), eq(token)); - verify(mIInputManagerMock).setLightStates(eq(DEVICE_ID), eq(new int[]{1}), + verify(mInputManagerRule.getMock()).setLightStates(eq(DEVICE_ID), eq(new int[]{1}), eq(states), eq(token)); // Verify the light state @@ -245,7 +243,7 @@ public class InputDeviceLightsManagerTest { // close session session.close(); - verify(mIInputManagerMock).closeLightSession(eq(DEVICE_ID), eq(token)); + verify(mInputManagerRule.getMock()).closeLightSession(eq(DEVICE_ID), eq(token)); } @Test diff --git a/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java b/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java index 0e3c200699d2..3057f5ddb540 100644 --- a/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java +++ b/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java @@ -41,16 +41,13 @@ import android.view.InputDevice; import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.annotations.GuardedBy; +import com.android.test.input.MockInputManagerRule; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.junit.MockitoRule; import java.util.List; import java.util.concurrent.BlockingQueue; @@ -70,43 +67,34 @@ public class InputDeviceSensorManagerTest { private static final int DEVICE_ID = 1000; - @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + @Rule + public final MockInputManagerRule mInputManagerRule = new MockInputManagerRule(); private InputManager mInputManager; private IInputSensorEventListener mIInputSensorEventListener; private final Object mLock = new Object(); - @Mock private IInputManager mIInputManagerMock; - private InputManagerGlobal.TestSession mInputManagerGlobalSession; - @Before public void setUp() throws Exception { final Context context = spy( new ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext())); - mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock); mInputManager = new InputManager(context); when(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(mInputManager); - when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID}); + when(mInputManagerRule.getMock().getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID}); - when(mIInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn( + when(mInputManagerRule.getMock().getInputDevice(eq(DEVICE_ID))).thenReturn( createInputDeviceWithSensor(DEVICE_ID)); - when(mIInputManagerMock.getSensorList(eq(DEVICE_ID))).thenReturn(new InputSensorInfo[] { - createInputSensorInfo(DEVICE_ID, Sensor.TYPE_ACCELEROMETER), - createInputSensorInfo(DEVICE_ID, Sensor.TYPE_GYROSCOPE)}); + when(mInputManagerRule.getMock().getSensorList(eq(DEVICE_ID))).thenReturn( + new InputSensorInfo[]{ + createInputSensorInfo(DEVICE_ID, Sensor.TYPE_ACCELEROMETER), + createInputSensorInfo(DEVICE_ID, Sensor.TYPE_GYROSCOPE)}); - when(mIInputManagerMock.enableSensor(eq(DEVICE_ID), anyInt(), anyInt(), anyInt())) + when(mInputManagerRule.getMock().enableSensor(eq(DEVICE_ID), anyInt(), anyInt(), anyInt())) .thenReturn(true); - when(mIInputManagerMock.registerSensorListener(any())).thenReturn(true); - } - - @After - public void tearDown() { - if (mInputManagerGlobalSession != null) { - mInputManagerGlobalSession.close(); - } + when(mInputManagerRule.getMock().registerSensorListener(any())).thenReturn(true); } private class InputTestSensorEventListener implements SensorEventListener { @@ -175,13 +163,13 @@ public class InputDeviceSensorManagerTest { SensorManager sensorManager = device.getSensorManager(); List<Sensor> accelList = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER); - verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID)); + verify(mInputManagerRule.getMock()).getSensorList(eq(DEVICE_ID)); assertEquals(1, accelList.size()); assertEquals(DEVICE_ID, accelList.get(0).getId()); assertEquals(Sensor.TYPE_ACCELEROMETER, accelList.get(0).getType()); List<Sensor> gyroList = sensorManager.getSensorList(Sensor.TYPE_GYROSCOPE); - verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID)); + verify(mInputManagerRule.getMock()).getSensorList(eq(DEVICE_ID)); assertEquals(1, gyroList.size()); assertEquals(DEVICE_ID, gyroList.get(0).getId()); assertEquals(Sensor.TYPE_GYROSCOPE, gyroList.get(0).getType()); @@ -197,11 +185,11 @@ public class InputDeviceSensorManagerTest { List<Sensor> gameRotationList = sensorManager.getSensorList( Sensor.TYPE_GAME_ROTATION_VECTOR); - verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID)); + verify(mInputManagerRule.getMock()).getSensorList(eq(DEVICE_ID)); assertEquals(0, gameRotationList.size()); List<Sensor> gravityList = sensorManager.getSensorList(Sensor.TYPE_GRAVITY); - verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID)); + verify(mInputManagerRule.getMock()).getSensorList(eq(DEVICE_ID)); assertEquals(0, gravityList.size()); } @@ -218,13 +206,13 @@ public class InputDeviceSensorManagerTest { mIInputSensorEventListener = invocation.getArgument(0); assertNotNull(mIInputSensorEventListener); return true; - }).when(mIInputManagerMock).registerSensorListener(any()); + }).when(mInputManagerRule.getMock()).registerSensorListener(any()); InputTestSensorEventListener listener = new InputTestSensorEventListener(); assertTrue(sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)); - verify(mIInputManagerMock).registerSensorListener(any()); - verify(mIInputManagerMock).enableSensor(eq(DEVICE_ID), eq(sensor.getType()), + verify(mInputManagerRule.getMock()).registerSensorListener(any()); + verify(mInputManagerRule.getMock()).enableSensor(eq(DEVICE_ID), eq(sensor.getType()), anyInt(), anyInt()); float[] values = new float[] {0.12f, 9.8f, 0.2f}; @@ -240,7 +228,7 @@ public class InputDeviceSensorManagerTest { } sensorManager.unregisterListener(listener); - verify(mIInputManagerMock).disableSensor(eq(DEVICE_ID), eq(sensor.getType())); + verify(mInputManagerRule.getMock()).disableSensor(eq(DEVICE_ID), eq(sensor.getType())); } } diff --git a/tests/Input/src/android/hardware/input/InputManagerTest.kt b/tests/Input/src/android/hardware/input/InputManagerTest.kt index 152dde94f006..4c6bb849155c 100644 --- a/tests/Input/src/android/hardware/input/InputManagerTest.kt +++ b/tests/Input/src/android/hardware/input/InputManagerTest.kt @@ -23,18 +23,16 @@ import android.view.Display import android.view.DisplayInfo import android.view.InputDevice import androidx.test.core.app.ApplicationProvider -import org.junit.After -import org.junit.Assert.assertNotNull +import com.android.test.input.MockInputManagerRule import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.eq import org.mockito.Mockito.`when` -import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoJUnitRunner /** @@ -54,35 +52,23 @@ class InputManagerTest { } @get:Rule - val rule = MockitoJUnit.rule()!! + val inputManagerRule = MockInputManagerRule() private lateinit var devicesChangedListener: IInputDevicesChangedListener private val deviceGenerationMap = mutableMapOf<Int /*deviceId*/, Int /*generation*/>() private lateinit var context: Context private lateinit var inputManager: InputManager - @Mock - private lateinit var iInputManager: IInputManager - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession - @Before fun setUp() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) inputManager = InputManager(context) `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager) - `when`(iInputManager.inputDeviceIds).then { + `when`(inputManagerRule.mock.inputDeviceIds).then { deviceGenerationMap.keys.toIntArray() } } - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } - } - private fun notifyDeviceChanged( deviceId: Int, associatedDisplayId: Int, @@ -92,7 +78,7 @@ class InputManagerTest { ?: throw IllegalArgumentException("Device $deviceId was never added!") deviceGenerationMap[deviceId] = generation - `when`(iInputManager.getInputDevice(deviceId)) + `when`(inputManagerRule.mock.getInputDevice(deviceId)) .thenReturn(createInputDevice(deviceId, associatedDisplayId, usiVersion, generation)) val list = deviceGenerationMap.flatMap { listOf(it.key, it.value) } if (::devicesChangedListener.isInitialized) { @@ -125,7 +111,7 @@ class InputManagerTest { fun testUsiVersionFallBackToDisplayConfig() { addInputDevice(DEVICE_ID, Display.DEFAULT_DISPLAY, null) - `when`(iInputManager.getHostUsiVersionFromDisplayConfig(eq(42))) + `when`(inputManagerRule.mock.getHostUsiVersionFromDisplayConfig(eq(42))) .thenReturn(HostUsiVersion(9, 8)) val usiVersion = inputManager.getHostUsiVersion(createDisplay(42)) assertEquals(HostUsiVersion(9, 8), usiVersion) diff --git a/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt b/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt index 072341dcefae..e99c81493394 100644 --- a/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt +++ b/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt @@ -18,20 +18,17 @@ package android.hardware.input import android.content.Context import android.content.ContextWrapper -import android.os.Handler import android.os.IBinder -import android.os.test.TestLooper import android.platform.test.annotations.Presubmit import android.platform.test.flag.junit.SetFlagsRule import android.view.KeyEvent import androidx.test.core.app.ApplicationProvider import com.android.server.testutils.any -import org.junit.After +import com.android.test.input.MockInputManagerRule import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.doAnswer import org.mockito.Mockito.`when` @@ -69,20 +66,16 @@ class KeyGestureEventHandlerTest { @get:Rule val rule = SetFlagsRule() + @get:Rule + val inputManagerRule = MockInputManagerRule() - private val testLooper = TestLooper() private var registeredListener: IKeyGestureHandler? = null private lateinit var context: Context private lateinit var inputManager: InputManager - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession - - @Mock - private lateinit var iInputManagerMock: IInputManager @Before fun setUp() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock) inputManager = InputManager(context) `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) .thenReturn(inputManager) @@ -97,7 +90,7 @@ class KeyGestureEventHandlerTest { } registeredListener = listener null - }.`when`(iInputManagerMock).registerKeyGestureHandler(any()) + }.`when`(inputManagerRule.mock).registerKeyGestureHandler(any()) // Handle key gesture handler being unregistered. doAnswer { @@ -108,14 +101,7 @@ class KeyGestureEventHandlerTest { } registeredListener = null null - }.`when`(iInputManagerMock).unregisterKeyGestureHandler(any()) - } - - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } + }.`when`(inputManagerRule.mock).unregisterKeyGestureHandler(any()) } private fun handleKeyGestureEvent(event: KeyGestureEvent) { diff --git a/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt b/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt index ca9de6000a5a..cf0bfcc4f6df 100644 --- a/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt +++ b/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt @@ -26,20 +26,19 @@ import android.platform.test.flag.junit.SetFlagsRule import android.view.KeyEvent import androidx.test.core.app.ApplicationProvider import com.android.server.testutils.any -import org.junit.After +import com.android.test.input.MockInputManagerRule +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.fail import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.doAnswer import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnitRunner -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.fail /** * Tests for [InputManager.KeyGestureEventListener]. @@ -63,21 +62,18 @@ class KeyGestureEventListenerTest { @get:Rule val rule = SetFlagsRule() + @get:Rule + val inputManagerRule = MockInputManagerRule() private val testLooper = TestLooper() private val executor = HandlerExecutor(Handler(testLooper.looper)) private var registeredListener: IKeyGestureEventListener? = null private lateinit var context: Context private lateinit var inputManager: InputManager - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession - - @Mock - private lateinit var iInputManagerMock: IInputManager @Before fun setUp() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock) inputManager = InputManager(context) `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) .thenReturn(inputManager) @@ -92,7 +88,7 @@ class KeyGestureEventListenerTest { } registeredListener = listener null - }.`when`(iInputManagerMock).registerKeyGestureEventListener(any()) + }.`when`(inputManagerRule.mock).registerKeyGestureEventListener(any()) // Handle key gesture event listener being unregistered. doAnswer { @@ -103,14 +99,7 @@ class KeyGestureEventListenerTest { } registeredListener = null null - }.`when`(iInputManagerMock).unregisterKeyGestureEventListener(any()) - } - - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } + }.`when`(inputManagerRule.mock).unregisterKeyGestureEventListener(any()) } private fun notifyKeyGestureEvent(event: KeyGestureEvent) { diff --git a/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt b/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt index 23135b2550b0..d25dee1d402c 100644 --- a/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt +++ b/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt @@ -24,22 +24,20 @@ import android.os.test.TestLooper import android.platform.test.annotations.Presubmit import androidx.test.core.app.ApplicationProvider import com.android.server.testutils.any -import org.junit.After +import com.android.test.input.MockInputManagerRule +import java.util.concurrent.Executor +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.fail import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.doAnswer import org.mockito.Mockito.`when` -import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoJUnitRunner -import java.util.concurrent.Executor -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.fail /** * Tests for [InputManager.KeyboardBacklightListener]. @@ -50,23 +48,19 @@ import kotlin.test.fail @Presubmit @RunWith(MockitoJUnitRunner::class) class KeyboardBacklightListenerTest { + @get:Rule - val rule = MockitoJUnit.rule()!! + val inputManagerRule = MockInputManagerRule() private lateinit var testLooper: TestLooper private var registeredListener: IKeyboardBacklightListener? = null private lateinit var executor: Executor private lateinit var context: Context private lateinit var inputManager: InputManager - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession - - @Mock - private lateinit var iInputManagerMock: IInputManager @Before fun setUp() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock) testLooper = TestLooper() executor = HandlerExecutor(Handler(testLooper.looper)) registeredListener = null @@ -84,7 +78,7 @@ class KeyboardBacklightListenerTest { } registeredListener = listener null - }.`when`(iInputManagerMock).registerKeyboardBacklightListener(any()) + }.`when`(inputManagerRule.mock).registerKeyboardBacklightListener(any()) // Handle keyboard backlight listener being unregistered. doAnswer { @@ -95,14 +89,7 @@ class KeyboardBacklightListenerTest { } registeredListener = null null - }.`when`(iInputManagerMock).unregisterKeyboardBacklightListener(any()) - } - - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } + }.`when`(inputManagerRule.mock).unregisterKeyboardBacklightListener(any()) } private fun notifyKeyboardBacklightChanged( diff --git a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt index bcd56ad0c669..1c2a0538e552 100644 --- a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt +++ b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt @@ -27,21 +27,20 @@ import android.platform.test.flag.junit.SetFlagsRule import android.view.KeyEvent import androidx.test.core.app.ApplicationProvider import com.android.server.testutils.any -import org.junit.After +import com.android.test.input.MockInputManagerRule +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.test.fail import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.doAnswer import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnitRunner -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.assertTrue -import kotlin.test.fail /** * Tests for [InputManager.StickyModifierStateListener]. @@ -59,21 +58,18 @@ class StickyModifierStateListenerTest { @get:Rule val rule = SetFlagsRule() + @get:Rule + val inputManagerRule = MockInputManagerRule() private val testLooper = TestLooper() private val executor = HandlerExecutor(Handler(testLooper.looper)) private var registeredListener: IStickyModifierStateListener? = null private lateinit var context: Context private lateinit var inputManager: InputManager - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession - - @Mock - private lateinit var iInputManagerMock: IInputManager @Before fun setUp() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock) inputManager = InputManager(context) `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) .thenReturn(inputManager) @@ -88,7 +84,7 @@ class StickyModifierStateListenerTest { } registeredListener = listener null - }.`when`(iInputManagerMock).registerStickyModifierStateListener(any()) + }.`when`(inputManagerRule.mock).registerStickyModifierStateListener(any()) // Handle sticky modifier state listener being unregistered. doAnswer { @@ -99,14 +95,7 @@ class StickyModifierStateListenerTest { } registeredListener = null null - }.`when`(iInputManagerMock).unregisterStickyModifierStateListener(any()) - } - - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } + }.`when`(inputManagerRule.mock).unregisterStickyModifierStateListener(any()) } private fun notifyStickyModifierStateChanged(modifierState: Int, lockedModifierState: Int) { diff --git a/tests/Input/src/com/android/server/input/BatteryControllerTests.kt b/tests/Input/src/com/android/server/input/BatteryControllerTests.kt index f2724e605553..044f11d6904c 100644 --- a/tests/Input/src/com/android/server/input/BatteryControllerTests.kt +++ b/tests/Input/src/com/android/server/input/BatteryControllerTests.kt @@ -27,7 +27,6 @@ import android.hardware.input.HostUsiVersion import android.hardware.input.IInputDeviceBatteryListener import android.hardware.input.IInputDeviceBatteryState import android.hardware.input.IInputDevicesChangedListener -import android.hardware.input.IInputManager import android.hardware.input.InputManager import android.hardware.input.InputManagerGlobal import android.os.Binder @@ -42,13 +41,13 @@ import com.android.server.input.BatteryController.BluetoothBatteryManager.Blueto import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS import com.android.server.input.BatteryController.UEventBatteryListener import com.android.server.input.BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS +import com.android.test.input.MockInputManagerRule import org.hamcrest.Description import org.hamcrest.Matcher import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers import org.hamcrest.TypeSafeMatcher import org.hamcrest.core.IsEqual.equalTo -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -184,12 +183,12 @@ class BatteryControllerTests { @get:Rule val rule = MockitoJUnit.rule()!! + @get:Rule + val inputManagerRule = MockInputManagerRule() @Mock private lateinit var native: NativeInputManagerService @Mock - private lateinit var iInputManager: IInputManager - @Mock private lateinit var uEventManager: UEventManager @Mock private lateinit var bluetoothBatteryManager: BluetoothBatteryManager @@ -205,10 +204,9 @@ class BatteryControllerTests { fun setup() { context = TestableContext(ApplicationProvider.getApplicationContext()) testLooper = TestLooper() - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) val inputManager = InputManager(context) context.addMockSystemService(InputManager::class.java, inputManager) - `when`(iInputManager.inputDeviceIds).then { + `when`(inputManagerRule.mock.inputDeviceIds).then { deviceGenerationMap.keys.toIntArray() } addInputDevice(DEVICE_ID) @@ -218,18 +216,11 @@ class BatteryControllerTests { bluetoothBatteryManager) batteryController.systemRunning() val listenerCaptor = ArgumentCaptor.forClass(IInputDevicesChangedListener::class.java) - verify(iInputManager).registerInputDevicesChangedListener(listenerCaptor.capture()) + verify(inputManagerRule.mock).registerInputDevicesChangedListener(listenerCaptor.capture()) devicesChangedListener = listenerCaptor.value testLooper.dispatchAll() } - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } - } - private fun notifyDeviceChanged( deviceId: Int, hasBattery: Boolean = true, @@ -239,7 +230,7 @@ class BatteryControllerTests { ?: throw IllegalArgumentException("Device $deviceId was never added!") deviceGenerationMap[deviceId] = generation - `when`(iInputManager.getInputDevice(deviceId)) + `when`(inputManagerRule.mock.getInputDevice(deviceId)) .thenReturn(createInputDevice(deviceId, hasBattery, supportsUsi, generation)) val list = deviceGenerationMap.flatMap { listOf(it.key, it.value) } if (::devicesChangedListener.isInitialized) { @@ -657,9 +648,9 @@ class BatteryControllerTests { @Test fun testRegisterBluetoothListenerForMonitoredBluetoothDevices() { - `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) .thenReturn("AA:BB:CC:DD:EE:FF") - `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) .thenReturn("11:22:33:44:55:66") addInputDevice(BT_DEVICE_ID) testLooper.dispatchNext() @@ -686,7 +677,7 @@ class BatteryControllerTests { batteryController.unregisterBatteryListener(BT_DEVICE_ID, listener, PID) verify(bluetoothBatteryManager, never()).removeBatteryListener(any()) - `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) .thenReturn(null) notifyDeviceChanged(SECOND_BT_DEVICE_ID) testLooper.dispatchNext() @@ -695,7 +686,7 @@ class BatteryControllerTests { @Test fun testNotifiesBluetoothBatteryChanges() { - `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) .thenReturn("AA:BB:CC:DD:EE:FF") `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21) addInputDevice(BT_DEVICE_ID) @@ -716,7 +707,7 @@ class BatteryControllerTests { @Test fun testBluetoothBatteryIsPrioritized() { `when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device") - `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) .thenReturn("AA:BB:CC:DD:EE:FF") `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21) `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98) @@ -745,7 +736,7 @@ class BatteryControllerTests { @Test fun testFallBackToNativeBatteryStateWhenBluetoothStateInvalid() { `when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device") - `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) .thenReturn("AA:BB:CC:DD:EE:FF") `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21) `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98) @@ -776,9 +767,9 @@ class BatteryControllerTests { @Test fun testRegisterBluetoothMetadataListenerForMonitoredBluetoothDevices() { - `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) .thenReturn("AA:BB:CC:DD:EE:FF") - `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) .thenReturn("11:22:33:44:55:66") addInputDevice(BT_DEVICE_ID) testLooper.dispatchNext() @@ -811,7 +802,7 @@ class BatteryControllerTests { verify(bluetoothBatteryManager) .removeMetadataListener("AA:BB:CC:DD:EE:FF", metadataListener1.value) - `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) .thenReturn(null) notifyDeviceChanged(SECOND_BT_DEVICE_ID) testLooper.dispatchNext() @@ -821,7 +812,7 @@ class BatteryControllerTests { @Test fun testNotifiesBluetoothMetadataBatteryChanges() { - `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) .thenReturn("AA:BB:CC:DD:EE:FF") `when`(bluetoothBatteryManager.getMetadata("AA:BB:CC:DD:EE:FF", BluetoothDevice.METADATA_MAIN_BATTERY)) @@ -861,7 +852,7 @@ class BatteryControllerTests { @Test fun testBluetoothMetadataBatteryIsPrioritized() { - `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) .thenReturn("AA:BB:CC:DD:EE:FF") `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21) `when`(bluetoothBatteryManager.getMetadata("AA:BB:CC:DD:EE:FF", diff --git a/tests/Input/src/com/android/server/input/InputDataStoreTests.kt b/tests/Input/src/com/android/server/input/InputDataStoreTests.kt new file mode 100644 index 000000000000..78c828bafd8f --- /dev/null +++ b/tests/Input/src/com/android/server/input/InputDataStoreTests.kt @@ -0,0 +1,504 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.input + +import android.app.role.RoleManager +import android.content.Context +import android.content.ContextWrapper +import android.content.Intent +import android.hardware.input.AppLaunchData +import android.hardware.input.InputGestureData +import android.hardware.input.KeyGestureEvent +import android.platform.test.annotations.Presubmit +import android.util.AtomicFile +import android.view.KeyEvent +import androidx.test.core.app.ApplicationProvider +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.FileOutputStream +import java.io.InputStream +import java.nio.charset.StandardCharsets +import kotlin.test.assertEquals +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito + +/** + * Tests for {@link InputDataStore}. + * + * Build/Install/Run: + * atest InputTests:InputDataStoreTests + */ +@Presubmit +class InputDataStoreTests { + + companion object { + const val USER_ID = 1 + } + + private lateinit var context: Context + private lateinit var inputDataStore: InputDataStore + private lateinit var tempFile: File + + @Before + fun setup() { + context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) + setupInputDataStore() + } + + private fun setupInputDataStore() { + tempFile = File.createTempFile("input_gestures", ".xml") + inputDataStore = InputDataStore(object : InputDataStore.FileInjector("input_gestures") { + private val atomicFile: AtomicFile = AtomicFile(tempFile) + + override fun openRead(userId: Int): InputStream? { + return atomicFile.openRead() + } + + override fun startWrite(userId: Int): FileOutputStream? { + return atomicFile.startWrite() + } + + override fun finishWrite(userId: Int, fos: FileOutputStream?, success: Boolean) { + if (success) { + atomicFile.finishWrite(fos) + } else { + atomicFile.failWrite(fos) + } + } + }) + } + + private fun getPrintableXml(inputGestures: List<InputGestureData>): String { + val outputStream = ByteArrayOutputStream() + inputDataStore.writeInputGestureXml(outputStream, true, inputGestures) + return outputStream.toString(StandardCharsets.UTF_8).trimIndent() + } + + @Test + fun saveToDiskKeyGesturesOnly() { + val inputGestures = listOf( + InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_H, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + .build(), + InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_1, + KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK) + .build(), + InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_2, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS) + .build() + ) + + inputDataStore.saveInputGestures(USER_ID, inputGestures) + assertEquals( + inputGestures, + inputDataStore.loadInputGestures(USER_ID), + getPrintableXml(inputGestures) + ) + } + + @Test + fun saveToDiskTouchpadGestures() { + val inputGestures = listOf( + InputGestureData.Builder() + .setTrigger( + InputGestureData.createTouchpadTrigger( + InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + .build() + ) + + inputDataStore.saveInputGestures(USER_ID, inputGestures) + assertEquals( + inputGestures, + inputDataStore.loadInputGestures(USER_ID), + getPrintableXml(inputGestures) + ) + } + + @Test + fun saveToDiskAppLaunchGestures() { + val inputGestures = listOf( + InputGestureData.Builder() + .setTrigger( + InputGestureData.createTouchpadTrigger( + InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION) + .setAppLaunchData(AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)) + .build(), + InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_2, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION) + .setAppLaunchData(AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)) + .build(), + InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_1, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION) + .setAppLaunchData( + AppLaunchData.createLaunchDataForComponent( + "com.test", + "com.test.BookmarkTest" + ) + ) + .build() + ) + + inputDataStore.saveInputGestures(USER_ID, inputGestures) + assertEquals( + inputGestures, + inputDataStore.loadInputGestures(USER_ID), + getPrintableXml(inputGestures) + ) + } + + @Test + fun saveToDiskCombinedGestures() { + val inputGestures = listOf( + InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_1, + KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK) + .build(), + InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_2, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS) + .build(), + InputGestureData.Builder() + .setTrigger( + InputGestureData.createTouchpadTrigger( + InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + .build(), + InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_9, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION) + .setAppLaunchData(AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)) + .build(), + ) + + inputDataStore.saveInputGestures(USER_ID, inputGestures) + assertEquals( + inputGestures, + inputDataStore.loadInputGestures(USER_ID), + getPrintableXml(inputGestures) + ) + } + + @Test + fun validXmlParse() { + val xmlData = """ + <?xml version='1.0' encoding='utf-8' standalone='yes' ?> + <root> + <input_gesture_list> + <input_gesture key_gesture_type="3"> + <key_trigger keycode="8" modifiers="69632" /> + </input_gesture> + <input_gesture key_gesture_type="21"> + <key_trigger keycode="9" modifiers="65536" /> + </input_gesture> + <input_gesture key_gesture_type="1"> + <touchpad_trigger touchpad_gesture_type="1" /> + </input_gesture> + </input_gesture_list> + </root>""".trimIndent() + val validInputGestures = listOf( + InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_1, + KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK) + .build(), + InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_2, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS) + .build(), + InputGestureData.Builder() + .setTrigger( + InputGestureData.createTouchpadTrigger( + InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + .build() + ) + val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8)) + assertEquals( + validInputGestures, + inputDataStore.readInputGesturesXml(inputStream, true) + ) + } + + @Test + fun missingTriggerData() { + val xmlData = """ + <?xml version='1.0' encoding='utf-8' standalone='yes' ?> + <root> + <input_gesture_list> + <input_gesture key_gesture_type="3"> + </input_gesture> + <input_gesture key_gesture_type="21"> + <key_trigger keycode="9" modifiers="65536" /> + </input_gesture> + <input_gesture key_gesture_type="1"> + <touchpad_trigger touchpad_gesture_type="1" /> + </input_gesture> + </input_gesture_list> + </root>""".trimIndent() + val validInputGestures = listOf( + InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_2, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS) + .build(), + InputGestureData.Builder() + .setTrigger( + InputGestureData.createTouchpadTrigger( + InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + .build() + ) + val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8)) + assertEquals( + validInputGestures, + inputDataStore.readInputGesturesXml(inputStream, true) + ) + } + + @Test + fun invalidKeycode() { + val xmlData = """ + <?xml version='1.0' encoding='utf-8' standalone='yes' ?> + <root> + <input_gesture_list> + <input_gesture key_gesture_type="3"> + <key_trigger keycode="8" modifiers="69632" /> + </input_gesture> + <input_gesture key_gesture_type="21"> + <key_trigger keycode="9999999" modifiers="65536" /> + </input_gesture> + <input_gesture key_gesture_type="1"> + <touchpad_trigger touchpad_gesture_type="1" /> + </input_gesture> + </input_gesture_list> + </root>""".trimIndent() + val validInputGestures = listOf( + InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_1, + KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK) + .build(), + InputGestureData.Builder() + .setTrigger( + InputGestureData.createTouchpadTrigger( + InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + .build() + ) + val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8)) + assertEquals( + validInputGestures, + inputDataStore.readInputGesturesXml(inputStream, true) + ) + } + + @Test + fun invalidTriggerName() { + val xmlData = """ + <?xml version='1.0' encoding='utf-8' standalone='yes' ?> + <root> + <input_gesture_list> + <input_gesture key_gesture_type="3"> + <key_trigger keycode="8" modifiers="69632" /> + </input_gesture> + <input_gesture key_gesture_type="21"> + <key_trigger keycode="9" modifiers="65536" /> + </input_gesture> + <input_gesture key_gesture_type="1"> + <invalid_trigger_name touchpad_gesture_type="1" /> + </input_gesture> + </input_gesture_list> + </root>""".trimIndent() + val validInputGestures = listOf( + InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_1, + KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK) + .build(), + InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_2, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS) + .build(), + ) + val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8)) + assertEquals( + validInputGestures, + inputDataStore.readInputGesturesXml(inputStream, true) + ) + } + + @Test + fun invalidTouchpadGestureType() { + val xmlData = """ + <?xml version='1.0' encoding='utf-8' standalone='yes' ?> + <root> + <input_gesture_list> + <input_gesture key_gesture_type="3"> + <key_trigger keycode="8" modifiers="69632" /> + </input_gesture> + <input_gesture key_gesture_type="21"> + <key_trigger keycode="9" modifiers="65536" /> + </input_gesture> + <input_gesture key_gesture_type="1"> + <touchpad_trigger touchpad_gesture_type="9999" /> + </input_gesture> + </input_gesture_list> + </root>""".trimIndent() + val validInputGestures = listOf( + InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_1, + KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK) + .build(), + InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_2, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS) + .build(), + ) + val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8)) + assertEquals( + validInputGestures, + inputDataStore.readInputGesturesXml(inputStream, true) + ) + } + + @Test + fun emptyInputGestureList() { + val xmlData = """ + <?xml version='1.0' encoding='utf-8' standalone='yes' ?> + <root> + <input_gesture_list> + </input_gesture_list> + </root>""".trimIndent() + val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8)) + assertEquals( + listOf(), + inputDataStore.readInputGesturesXml(inputStream, true) + ) + } + + @Test + fun invalidTag() { + val xmlData = """ + <?xml version='1.0' encoding='utf-8' standalone='yes' ?> + <root> + <invalid_tag_name> + </invalid_tag_name> + </root>""".trimIndent() + val inputStream = ByteArrayInputStream(xmlData.toByteArray(Charsets.UTF_8)) + assertEquals( + listOf(), + inputDataStore.readInputGesturesXml(inputStream, true) + ) + } +}
\ No newline at end of file diff --git a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt new file mode 100644 index 000000000000..e281a3fb1287 --- /dev/null +++ b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt @@ -0,0 +1,210 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.input + +import android.hardware.input.InputGestureData +import android.hardware.input.InputManager +import android.hardware.input.KeyGestureEvent +import android.platform.test.annotations.Presubmit +import android.view.KeyEvent +import androidx.test.core.app.ApplicationProvider +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +/** + * Tests for custom keyboard glyph map configuration. + * + * Build/Install/Run: + * atest InputTests:CustomInputGestureManagerTests + */ +@Presubmit +class InputGestureManagerTests { + + companion object { + const val USER_ID = 1 + } + + private lateinit var inputGestureManager: InputGestureManager + + @Before + fun setup() { + inputGestureManager = InputGestureManager(ApplicationProvider.getApplicationContext()) + } + + @Test + fun addRemoveCustomGesture() { + val customGesture = InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_H, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + .build() + val result = inputGestureManager.addCustomInputGesture(USER_ID, customGesture) + assertEquals(InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS, result) + assertEquals( + listOf(customGesture), + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) + ) + + inputGestureManager.removeCustomInputGesture(USER_ID, customGesture) + assertEquals( + listOf<InputGestureData>(), + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) + ) + } + + @Test + fun removeNonExistentGesture() { + val customGesture = InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_H, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + .build() + val result = inputGestureManager.removeCustomInputGesture(USER_ID, customGesture) + assertEquals(InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST, result) + assertEquals( + listOf<InputGestureData>(), + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) + ) + } + + @Test + fun addAlreadyExistentGesture() { + val customGesture = InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_H, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + .build() + inputGestureManager.addCustomInputGesture(USER_ID, customGesture) + val customGesture2 = InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_H, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK) + .build() + val result = inputGestureManager.addCustomInputGesture(USER_ID, customGesture2) + assertEquals(InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS, result) + assertEquals( + listOf(customGesture), + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) + ) + } + + @Test + fun addRemoveAllExistentGestures() { + val customGesture = InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_H, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + .build() + inputGestureManager.addCustomInputGesture(USER_ID, customGesture) + val customGesture2 = InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_DEL, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK) + .build() + inputGestureManager.addCustomInputGesture(USER_ID, customGesture2) + + assertEquals( + listOf(customGesture, customGesture2), + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) + ) + + inputGestureManager.removeAllCustomInputGestures(USER_ID, /* filter = */null) + assertEquals( + listOf<InputGestureData>(), + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) + ) + } + + @Test + fun filteringBasedOnTouchpadOrKeyGestures() { + val customKeyGesture = InputGestureData.Builder() + .setTrigger( + InputGestureData.createKeyTrigger( + KeyEvent.KEYCODE_H, + KeyEvent.META_META_ON + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + .build() + inputGestureManager.addCustomInputGesture(USER_ID, customKeyGesture) + val customTouchpadGesture = InputGestureData.Builder() + .setTrigger( + InputGestureData.createTouchpadTrigger( + InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP + ) + ) + .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK) + .build() + inputGestureManager.addCustomInputGesture(USER_ID, customTouchpadGesture) + + assertEquals( + listOf(customTouchpadGesture, customKeyGesture), + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) + ) + assertEquals( + listOf(customKeyGesture), + inputGestureManager.getCustomInputGestures(USER_ID, InputGestureData.Filter.KEY) + ) + assertEquals( + listOf(customTouchpadGesture), + inputGestureManager.getCustomInputGestures( + USER_ID, + InputGestureData.Filter.TOUCHPAD + ) + ) + + inputGestureManager.removeAllCustomInputGestures(USER_ID, InputGestureData.Filter.KEY) + assertEquals( + listOf(customTouchpadGesture), + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) + ) + + inputGestureManager.removeAllCustomInputGestures( + USER_ID, + InputGestureData.Filter.TOUCHPAD + ) + assertEquals( + listOf<InputGestureData>(), + inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null) + ) + } +}
\ No newline at end of file diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt index 351ec4635977..43844f6514e8 100644 --- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt @@ -93,14 +93,15 @@ class InputManagerServiceTests { ) } - @JvmField - @Rule + @get:Rule val extendedMockitoRule = - ExtendedMockitoRule.Builder(this).mockStatic(LocalServices::class.java) - .mockStatic(PermissionChecker::class.java).build()!! + ExtendedMockitoRule.Builder(this) + .mockStatic(LocalServices::class.java) + .mockStatic(PermissionChecker::class.java) + .mockStatic(KeyCharacterMap::class.java) + .build()!! - @JvmField - @Rule + @get:Rule val setFlagsRule = SetFlagsRule() @get:Rule @@ -124,6 +125,9 @@ class InputManagerServiceTests { @Mock private lateinit var kbdController: InputManagerService.KeyboardBacklightControllerInterface + @Mock + private lateinit var kcm: KeyCharacterMap + private lateinit var service: InputManagerService private lateinit var localService: InputManagerInternal private lateinit var context: Context @@ -152,8 +156,7 @@ class InputManagerServiceTests { } override fun getKeyboardBacklightController( - nativeService: NativeInputManagerService?, - dataStore: PersistentDataStore? + nativeService: NativeInputManagerService? ): InputManagerService.KeyboardBacklightControllerInterface { return kbdController } @@ -173,6 +176,9 @@ class InputManagerServiceTests { ExtendedMockito.doReturn(packageManagerInternal).`when` { LocalServices.getService(eq(PackageManagerInternal::class.java)) } + ExtendedMockito.doReturn(kcm).`when` { + KeyCharacterMap.load(anyInt()) + } assertTrue("Local service must be registered", this::localService.isInitialized) service.setWindowManagerCallbacks(wmCallbacks) @@ -208,6 +214,8 @@ class InputManagerServiceTests { verify(native).setTouchpadTapDraggingEnabled(anyBoolean()) verify(native).setShouldNotifyTouchpadHardwareState(anyBoolean()) verify(native).setTouchpadRightClickZoneEnabled(anyBoolean()) + verify(native).setTouchpadThreeFingerTapShortcutEnabled(anyBoolean()) + verify(native).setTouchpadSystemGesturesEnabled(anyBoolean()) verify(native).setShowTouches(anyBoolean()) verify(native).setMotionClassifierEnabled(anyBoolean()) verify(native).setMaximumObscuringOpacityForTouch(anyFloat()) diff --git a/tests/Input/src/com/android/server/input/InputShellCommandTest.java b/tests/Input/src/com/android/server/input/InputShellCommandTest.java index 11f46335f017..a236244546cb 100644 --- a/tests/Input/src/com/android/server/input/InputShellCommandTest.java +++ b/tests/Input/src/com/android/server/input/InputShellCommandTest.java @@ -133,6 +133,21 @@ public class InputShellCommandTest { assertThat(mInputEventInjector.mInjectedEvents).isEmpty(); } + @Test + public void testSwipeCommandEventFrequency() { + int[] durations = {100, 300, 500}; + for (int durationMillis: durations) { + mInputEventInjector.mInjectedEvents.clear(); + runCommand(String.format("swipe 200 800 200 200 %d", durationMillis)); + + // Add 2 events for ACTION_DOWN and ACTION_UP. + final int maxEventNum = + (int) Math.ceil(InputShellCommand.SWIPE_EVENT_HZ_DEFAULT + * (float) durationMillis / 1000) + 2; + assertThat(mInputEventInjector.mInjectedEvents.size()).isAtMost(maxEventNum); + } + } + private InputEvent getSingleInjectedInputEvent() { assertThat(mInputEventInjector.mInjectedEvents).hasSize(1); return mInputEventInjector.mInjectedEvents.get(0); diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index ba360070abc3..fafb0e0f75c8 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -16,29 +16,47 @@ package com.android.server.input +import android.app.role.RoleManager import android.content.Context import android.content.ContextWrapper -import android.hardware.input.IInputManager +import android.content.Intent +import android.content.pm.PackageManager +import android.content.res.Resources +import android.content.res.XmlResourceParser import android.hardware.input.AidlKeyGestureEvent +import android.hardware.input.AppLaunchData +import android.hardware.input.IInputManager import android.hardware.input.IKeyGestureEventListener import android.hardware.input.IKeyGestureHandler +import android.hardware.input.InputGestureData import android.hardware.input.InputManager import android.hardware.input.InputManagerGlobal import android.hardware.input.KeyGestureEvent -import android.hardware.input.KeyGestureEvent.KeyGestureType import android.os.IBinder import android.os.Process +import android.os.SystemClock +import android.os.SystemProperties import android.os.test.TestLooper +import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.Presubmit +import android.platform.test.flag.junit.SetFlagsRule +import android.util.AtomicFile import android.view.InputDevice import android.view.KeyCharacterMap import android.view.KeyEvent +import android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE import androidx.test.core.app.ApplicationProvider +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.internal.R import com.android.internal.annotations.Keep import com.android.internal.util.FrameworkStatsLog import com.android.modules.utils.testing.ExtendedMockitoRule +import java.io.File +import java.io.FileOutputStream +import java.io.InputStream import junitparams.JUnitParamsRunner import junitparams.Parameters +import org.junit.After import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -78,35 +96,104 @@ class KeyGestureControllerTests { KeyEvent.KEYCODE_META_LEFT to (KeyEvent.META_META_LEFT_ON or KeyEvent.META_META_ON), KeyEvent.KEYCODE_META_RIGHT to (KeyEvent.META_META_RIGHT_ON or KeyEvent.META_META_ON), ) + const val SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH = 0 + const val SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY = 1 + const val SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY = 0 + const val SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL = 1 + const val SETTINGS_KEY_BEHAVIOR_NOTHING = 2 } @JvmField @Rule val extendedMockitoRule = ExtendedMockitoRule.Builder(this) - .mockStatic(FrameworkStatsLog::class.java).build()!! + .mockStatic(FrameworkStatsLog::class.java) + .mockStatic(SystemProperties::class.java) + .mockStatic(KeyCharacterMap::class.java) + .build()!! + + @JvmField + @Rule + val rule = SetFlagsRule() @Mock private lateinit var iInputManager: IInputManager + @Mock + private lateinit var packageManager: PackageManager + private var currentPid = 0 - private lateinit var keyGestureController: KeyGestureController private lateinit var context: Context + private lateinit var resources: Resources + private lateinit var keyGestureController: KeyGestureController private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession private lateinit var testLooper: TestLooper + private lateinit var tempFile: File + private lateinit var inputDataStore: InputDataStore private var events = mutableListOf<KeyGestureEvent>() - private var handleEvents = mutableListOf<KeyGestureEvent>() @Before fun setup() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) + resources = Mockito.spy(context.resources) setupInputDevices() + setupBehaviors() testLooper = TestLooper() currentPid = Process.myPid() - keyGestureController = KeyGestureController(context, testLooper.looper) + tempFile = File.createTempFile("input_gestures", ".xml") + inputDataStore = + InputDataStore(object : InputDataStore.FileInjector("input_gestures.xml") { + private val atomicFile: AtomicFile = AtomicFile(tempFile) + + override fun openRead(userId: Int): InputStream? { + return atomicFile.openRead() + } + + override fun startWrite(userId: Int): FileOutputStream? { + return atomicFile.startWrite() + } + + override fun finishWrite(userId: Int, fos: FileOutputStream?, success: Boolean) { + if (success) { + atomicFile.finishWrite(fos) + } else { + atomicFile.failWrite(fos) + } + } + + override fun getAtomicFileForUserId(userId: Int): AtomicFile { + return atomicFile + } + }) + } + + @After + fun teardown() { + if (this::inputManagerGlobalSession.isInitialized) { + inputManagerGlobalSession.close() + } + } + + private fun setupBehaviors() { + Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1") + Mockito.`when`(resources.getBoolean(R.bool.config_enableScreenshotChord)).thenReturn(true) + Mockito.`when`(context.resources).thenReturn(resources) + Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) + .thenReturn(true) + Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) + .thenReturn(true) + Mockito.`when`(context.packageManager).thenReturn(packageManager) + } + + private fun setupBookmarks(bookmarkRes: Int) { + val testBookmarks: XmlResourceParser = context.resources.getXml(bookmarkRes) + Mockito.`when`(resources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks) } private fun setupInputDevices() { + val correctIm = context.getSystemService(InputManager::class.java)!! + val virtualDevice = correctIm.getInputDevice(KeyCharacterMap.VIRTUAL_KEYBOARD)!! + val kcm = virtualDevice.keyCharacterMap!! + inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) val inputManager = InputManager(context) Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) .thenReturn(inputManager) @@ -114,16 +201,29 @@ class KeyGestureControllerTests { val keyboardDevice = InputDevice.Builder().setId(DEVICE_ID).build() Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID)) Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice) + ExtendedMockito.`when`(KeyCharacterMap.load(Mockito.anyInt())).thenReturn(kcm) + } + + private fun setupKeyGestureController() { + keyGestureController = + KeyGestureController(context, testLooper.looper, inputDataStore) + Mockito.`when`(iInputManager.getAppLaunchBookmarks()) + .thenReturn(keyGestureController.appLaunchBookmarks) + keyGestureController.systemRunning() + testLooper.dispatchAll() } private fun notifyHomeGestureCompleted() { - keyGestureController.notifyKeyGestureCompleted(DEVICE_ID, intArrayOf(KeyEvent.KEYCODE_H), + keyGestureController.notifyKeyGestureCompleted( + DEVICE_ID, intArrayOf(KeyEvent.KEYCODE_H), KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + KeyGestureEvent.KEY_GESTURE_TYPE_HOME + ) } @Test fun testKeyGestureEvent_registerUnregisterListener() { + setupKeyGestureController() val listener = KeyGestureEventListener() // Register key gesture event listener @@ -155,20 +255,22 @@ class KeyGestureControllerTests { @Test fun testKeyGestureEvent_multipleGestureHandlers() { + setupKeyGestureController() + // Set up two callbacks. var callbackCount1 = 0 var callbackCount2 = 0 var selfCallback = 0 val externalHandler1 = KeyGestureHandler { _, _ -> - callbackCount1++; + callbackCount1++ true } val externalHandler2 = KeyGestureHandler { _, _ -> - callbackCount2++; + callbackCount2++ true } val selfHandler = KeyGestureHandler { _, _ -> - selfCallback++; + selfCallback++ false } @@ -184,7 +286,7 @@ class KeyGestureControllerTests { keyGestureController.handleKeyGesture(/* deviceId = */ 0, intArrayOf(KeyEvent.KEYCODE_HOME), /* modifierState = */ 0, KeyGestureEvent.KEY_GESTURE_TYPE_HOME, KeyGestureEvent.ACTION_GESTURE_COMPLETE, /* displayId */ 0, - /* focusedToken = */ null, /* flags = */ 0 + /* focusedToken = */ null, /* flags = */ 0, /* appLaunchData = */null ) assertEquals( @@ -211,12 +313,13 @@ class KeyGestureControllerTests { val expectedKeys: IntArray, val expectedModifierState: Int, val expectedActions: IntArray, + val expectedAppLaunchData: AppLaunchData? = null, ) { override fun toString(): String = name } @Keep - private fun keyGestureEventHandlerTestArguments(): Array<TestData> { + private fun systemGesturesTestArguments(): Array<TestData> { return arrayOf( TestData( "META + A -> Launch Assistant", @@ -227,25 +330,6 @@ class KeyGestureControllerTests { intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( - "RECENT_APPS -> Show Overview", - intArrayOf(KeyEvent.KEYCODE_RECENT_APPS), - KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, - intArrayOf(KeyEvent.KEYCODE_RECENT_APPS), - 0, - intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) - ), - TestData( - "APP_SWITCH -> App Switch", - intArrayOf(KeyEvent.KEYCODE_APP_SWITCH), - KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, - intArrayOf(KeyEvent.KEYCODE_APP_SWITCH), - 0, - intArrayOf( - KeyGestureEvent.ACTION_GESTURE_START, - KeyGestureEvent.ACTION_GESTURE_COMPLETE - ) - ), - TestData( "META + H -> Go Home", intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_H), KeyGestureEvent.KEY_GESTURE_TYPE_HOME, @@ -406,6 +490,495 @@ class KeyGestureControllerTests { intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( + "META + / -> Open Shortcut Helper", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_SLASH), + KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER, + intArrayOf(KeyEvent.KEYCODE_SLASH), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + ALT -> Toggle Caps Lock", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT), + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "ALT + META -> Toggle Caps Lock", + intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_META_LEFT), + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + TAB -> Open Overview", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_TAB), + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, + intArrayOf(KeyEvent.KEYCODE_TAB), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "ALT + TAB -> Toggle Recent Apps Switcher", + intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_TAB), + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER, + intArrayOf(KeyEvent.KEYCODE_TAB), + KeyEvent.META_ALT_ON, + intArrayOf( + KeyGestureEvent.ACTION_GESTURE_START, + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ) + ), + TestData( + "CTRL + SPACE -> Switch Language Forward", + intArrayOf(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_SPACE), + KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, + intArrayOf(KeyEvent.KEYCODE_SPACE), + KeyEvent.META_CTRL_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "CTRL + SHIFT + SPACE -> Switch Language Backward", + intArrayOf( + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_SHIFT_LEFT, + KeyEvent.KEYCODE_SPACE + ), + KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, + intArrayOf(KeyEvent.KEYCODE_SPACE), + KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "CTRL + ALT + Z -> Accessibility Shortcut", + intArrayOf( + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_Z + ), + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT, + intArrayOf(KeyEvent.KEYCODE_Z), + KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + B -> Launch Default Browser", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_B), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_B), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER) + ), + TestData( + "META + C -> Launch Default Contacts", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_P), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_P), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS) + ), + TestData( + "META + E -> Launch Default Email", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_E), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_E), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL) + ), + TestData( + "META + K -> Launch Default Calendar", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_C), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_C), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR) + ), + TestData( + "META + M -> Launch Default Maps", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_M), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_M), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MAPS) + ), + TestData( + "META + U -> Launch Default Calculator", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_U), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_U), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR) + ), + TestData( + "META + CTRL + DEL -> Trigger Bug Report", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_DEL + ), + KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT, + intArrayOf(KeyEvent.KEYCODE_DEL), + KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "Meta + Alt + 3 -> Toggle Bounce Keys", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_3 + ), + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS, + intArrayOf(KeyEvent.KEYCODE_3), + KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "Meta + Alt + 4 -> Toggle Mouse Keys", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_4 + ), + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS, + intArrayOf(KeyEvent.KEYCODE_4), + KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "Meta + Alt + 5 -> Toggle Sticky Keys", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_5 + ), + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS, + intArrayOf(KeyEvent.KEYCODE_5), + KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "Meta + Alt + 6 -> Toggle Slow Keys", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_6 + ), + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS, + intArrayOf(KeyEvent.KEYCODE_6), + KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + CTRL + D -> Move a task to next display", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_D + ), + KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY, + intArrayOf(KeyEvent.KEYCODE_D), + KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + [ -> Resizes a task to fit the left half of the screen", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_LEFT_BRACKET + ), + KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW, + intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + ] -> Resizes a task to fit the right half of the screen", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_RIGHT_BRACKET + ), + KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW, + intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + '=' -> Toggles maximization of a task to maximized and restore its bounds", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_EQUALS + ), + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW, + intArrayOf(KeyEvent.KEYCODE_EQUALS), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + '-' -> Minimizes a freeform task", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_MINUS + ), + KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW, + intArrayOf(KeyEvent.KEYCODE_MINUS), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + ALT + '-' -> Magnifier Zoom Out", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_MINUS + ), + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT, + intArrayOf(KeyEvent.KEYCODE_MINUS), + KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + ALT + '=' -> Magnifier Zoom In", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_EQUALS + ), + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN, + intArrayOf(KeyEvent.KEYCODE_EQUALS), + KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + ALT + M -> Toggle Magnification", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_M + ), + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION, + intArrayOf(KeyEvent.KEYCODE_M), + KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "META + ALT + S -> Activate Select to Speak", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_S + ), + KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK, + intArrayOf(KeyEvent.KEYCODE_S), + KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + ) + } + + @Test + @Parameters(method = "systemGesturesTestArguments") + @EnableFlags( + com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS, + com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES, + com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT, + com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS + ) + fun testKeyGestures(test: TestData) { + setupKeyGestureController() + testKeyGestureInternal(test) + } + + @Test + @Parameters(method = "systemGesturesTestArguments") + @EnableFlags( + com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG, + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS, + com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES, + com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT, + com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS + ) + fun testCustomKeyGesturesNotAllowedForSystemGestures(test: TestData) { + setupKeyGestureController() + // Need to re-init so that bookmarks are correctly blocklisted + Mockito.`when`(iInputManager.getAppLaunchBookmarks()) + .thenReturn(keyGestureController.appLaunchBookmarks) + keyGestureController.systemRunning() + + val builder = InputGestureData.Builder() + .setKeyGestureType(test.expectedKeyGestureType) + .setTrigger( + InputGestureData.createKeyTrigger( + test.expectedKeys[0], + test.expectedModifierState + ) + ) + if (test.expectedAppLaunchData != null) { + builder.setAppLaunchData(test.expectedAppLaunchData) + } + assertEquals( + test.toString(), + InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE, + keyGestureController.addCustomInputGesture(0, builder.build().aidlData) + ) + } + + @Keep + private fun bookmarkArguments(): Array<TestData> { + return arrayOf( + TestData( + "META + B -> Launch Default Browser", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_B), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_B), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER) + ), + TestData( + "META + P -> Launch Default Contacts", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_P), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_P), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS) + ), + TestData( + "META + E -> Launch Default Email", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_E), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_E), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL) + ), + TestData( + "META + C -> Launch Default Calendar", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_C), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_C), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR) + ), + TestData( + "META + M -> Launch Default Maps", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_M), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_M), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MAPS) + ), + TestData( + "META + U -> Launch Default Calculator", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_U), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_U), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR) + ), + TestData( + "META + SHIFT + B -> Launch Default Browser", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_SHIFT_LEFT, + KeyEvent.KEYCODE_B + ), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_B), + KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER) + ), + TestData( + "META + SHIFT + P -> Launch Default Contacts", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_SHIFT_LEFT, + KeyEvent.KEYCODE_P + ), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_P), + KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS) + ), + TestData( + "META + SHIFT + J -> Launch Target Activity", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_SHIFT_LEFT, + KeyEvent.KEYCODE_J + ), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_J), + KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest") + ) + ) + } + + @Test + @Parameters(method = "bookmarkArguments") + fun testBookmarks(test: TestData) { + setupBookmarks(com.android.test.input.R.xml.bookmarks) + setupKeyGestureController() + testKeyGestureInternal(test) + } + + @Test + @Parameters(method = "bookmarkArguments") + fun testBookmarksLegacy(test: TestData) { + setupBookmarks(com.android.test.input.R.xml.bookmarks_legacy) + setupKeyGestureController() + testKeyGestureInternal(test) + } + + @Keep + private fun systemKeysTestArguments(): Array<TestData> { + return arrayOf( + TestData( + "RECENT_APPS -> Show Overview", + intArrayOf(KeyEvent.KEYCODE_RECENT_APPS), + KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, + intArrayOf(KeyEvent.KEYCODE_RECENT_APPS), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "APP_SWITCH -> App Switch", + intArrayOf(KeyEvent.KEYCODE_APP_SWITCH), + KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, + intArrayOf(KeyEvent.KEYCODE_APP_SWITCH), + 0, + intArrayOf( + KeyGestureEvent.ACTION_GESTURE_START, + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ) + ), + TestData( "BRIGHTNESS_UP -> Brightness Up", intArrayOf(KeyEvent.KEYCODE_BRIGHTNESS_UP), KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP, @@ -494,35 +1067,285 @@ class KeyGestureControllerTests { intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( - "META + ALT -> Toggle Caps Lock", - intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT), - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, - intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT), + "SYSRQ -> Take screenshot", + intArrayOf(KeyEvent.KEYCODE_SYSRQ), + KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT, + intArrayOf(KeyEvent.KEYCODE_SYSRQ), 0, intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( - "ALT + META -> Toggle Caps Lock", - intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_META_LEFT), - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, - intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT), + "ESC -> Close All Dialogs", + intArrayOf(KeyEvent.KEYCODE_ESCAPE), + KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS, + intArrayOf(KeyEvent.KEYCODE_ESCAPE), 0, intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( - "META + TAB -> Open Overview", - intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_TAB), - KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, - intArrayOf(KeyEvent.KEYCODE_TAB), - KeyEvent.META_META_ON, + "EXPLORER -> Launch Default Browser", + intArrayOf(KeyEvent.KEYCODE_EXPLORER), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_EXPLORER), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER) + ), + TestData( + "ENVELOPE -> Launch Default Email", + intArrayOf(KeyEvent.KEYCODE_ENVELOPE), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_ENVELOPE), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL) + ), + TestData( + "CONTACTS -> Launch Default Contacts", + intArrayOf(KeyEvent.KEYCODE_CONTACTS), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_CONTACTS), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS) + ), + TestData( + "CALENDAR -> Launch Default Calendar", + intArrayOf(KeyEvent.KEYCODE_CALENDAR), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_CALENDAR), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR) + ), + TestData( + "MUSIC -> Launch Default Music", + intArrayOf(KeyEvent.KEYCODE_MUSIC), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_MUSIC), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MUSIC) + ), + TestData( + "CALCULATOR -> Launch Default Calculator", + intArrayOf(KeyEvent.KEYCODE_CALCULATOR), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_CALCULATOR), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE), + AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR) + ), + TestData( + "LOCK -> Lock Screen", + intArrayOf(KeyEvent.KEYCODE_LOCK), + KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN, + intArrayOf(KeyEvent.KEYCODE_LOCK), + 0, intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), TestData( - "ALT + TAB -> Toggle Recent Apps Switcher", - intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_TAB), - KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER, - intArrayOf(KeyEvent.KEYCODE_TAB), - KeyEvent.META_ALT_ON, + "FULLSCREEN -> Maximizes a task to fit the screen", + intArrayOf(KeyEvent.KEYCODE_FULLSCREEN), + KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW, + intArrayOf(KeyEvent.KEYCODE_FULLSCREEN), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + ) + } + + @Test + @Parameters(method = "systemKeysTestArguments") + @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES) + fun testSystemKeys(test: TestData) { + setupKeyGestureController() + testKeyGestureInternal(test) + } + + @Test + fun testKeycodesFullyConsumed_irrespectiveOfHandlers() { + setupKeyGestureController() + val testKeys = intArrayOf( + KeyEvent.KEYCODE_RECENT_APPS, + KeyEvent.KEYCODE_APP_SWITCH, + KeyEvent.KEYCODE_BRIGHTNESS_UP, + KeyEvent.KEYCODE_BRIGHTNESS_DOWN, + KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, + KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, + KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, + KeyEvent.KEYCODE_ALL_APPS, + KeyEvent.KEYCODE_NOTIFICATION, + KeyEvent.KEYCODE_SETTINGS, + KeyEvent.KEYCODE_LANGUAGE_SWITCH, + KeyEvent.KEYCODE_SCREENSHOT, + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_META_RIGHT, + KeyEvent.KEYCODE_ASSIST, + KeyEvent.KEYCODE_VOICE_ASSIST, + KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY, + KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY, + KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY, + KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL, + KeyEvent.KEYCODE_DO_NOT_DISTURB, + KeyEvent.KEYCODE_LOCK, + KeyEvent.KEYCODE_FULLSCREEN + ) + + val handler = KeyGestureHandler { _, _ -> false } + keyGestureController.registerKeyGestureHandler(handler, 0) + + for (key in testKeys) { + sendKeys(intArrayOf(key), assertNotSentToApps = true) + } + } + + @Test + fun testSearchKeyGestures_defaultSearch() { + Mockito.`when`(resources.getInteger(R.integer.config_searchKeyBehavior)) + .thenReturn(SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH) + setupKeyGestureController() + testKeyGestureNotProduced( + "SEARCH -> Default Search", + intArrayOf(KeyEvent.KEYCODE_SEARCH), + ) + } + + @Test + fun testSearchKeyGestures_searchActivity() { + Mockito.`when`(resources.getInteger(R.integer.config_searchKeyBehavior)) + .thenReturn(SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY) + setupKeyGestureController() + testKeyGestureInternal( + TestData( + "SEARCH -> Launch Search Activity", + intArrayOf(KeyEvent.KEYCODE_SEARCH), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH, + intArrayOf(KeyEvent.KEYCODE_SEARCH), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ) + ) + } + + @Test + fun testSettingKeyGestures_doNothing() { + Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior)) + .thenReturn(SETTINGS_KEY_BEHAVIOR_NOTHING) + setupKeyGestureController() + testKeyGestureNotProduced( + "SETTINGS -> Do Nothing", + intArrayOf(KeyEvent.KEYCODE_SETTINGS), + ) + } + + @Test + fun testSettingKeyGestures_settingsActivity() { + Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior)) + .thenReturn(SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY) + setupKeyGestureController() + testKeyGestureInternal( + TestData( + "SETTINGS -> Launch Settings Activity", + intArrayOf(KeyEvent.KEYCODE_SETTINGS), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS, + intArrayOf(KeyEvent.KEYCODE_SETTINGS), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ) + ) + } + + @Test + fun testSettingKeyGestures_notificationPanel() { + Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior)) + .thenReturn(SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL) + setupKeyGestureController() + testKeyGestureInternal( + TestData( + "SETTINGS -> Toggle Notification Panel", + intArrayOf(KeyEvent.KEYCODE_SETTINGS), + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, + intArrayOf(KeyEvent.KEYCODE_SETTINGS), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ) + ) + } + + @Test + fun testCapsLockPressNotified() { + setupKeyGestureController() + val listener = KeyGestureEventListener() + + keyGestureController.registerKeyGestureEventListener(listener, 0) + sendKeys(intArrayOf(KeyEvent.KEYCODE_CAPS_LOCK)) + testLooper.dispatchAll() + assertEquals( + "Listener should get callbacks on key gesture event completed", + 1, + events.size + ) + assertEquals( + "Listener should get callback for Toggle Caps Lock key gesture complete event", + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, + events[0].keyGestureType + ) + } + + @Keep + private fun systemGesturesTestArguments_forKeyCombinations(): Array<TestData> { + return arrayOf( + TestData( + "VOLUME_DOWN + POWER -> Screenshot Chord", + intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_POWER), + KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD, + intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_POWER), + 0, + intArrayOf( + KeyGestureEvent.ACTION_GESTURE_START, + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ) + ), + TestData( + "POWER + STEM_PRIMARY -> Screenshot Chord", + intArrayOf(KeyEvent.KEYCODE_POWER, KeyEvent.KEYCODE_STEM_PRIMARY), + KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD, + intArrayOf(KeyEvent.KEYCODE_POWER, KeyEvent.KEYCODE_STEM_PRIMARY), + 0, + intArrayOf( + KeyGestureEvent.ACTION_GESTURE_START, + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ) + ), + TestData( + "VOLUME_DOWN + VOLUME_UP -> Accessibility Chord", + intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP), + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD, + intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP), + 0, + intArrayOf( + KeyGestureEvent.ACTION_GESTURE_START, + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ) + ), + TestData( + "BACK + DPAD_DOWN -> TV Accessibility Chord", + intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), + KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD, + intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), + 0, + intArrayOf( + KeyGestureEvent.ACTION_GESTURE_START, + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ) + ), + TestData( + "BACK + DPAD_CENTER -> TV Trigger Bug Report", + intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER), + KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT, + intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER), + 0, intArrayOf( KeyGestureEvent.ACTION_GESTURE_START, KeyGestureEvent.ACTION_GESTURE_COMPLETE @@ -532,24 +1355,240 @@ class KeyGestureControllerTests { } @Test - @Parameters(method = "keyGestureEventHandlerTestArguments") - fun testKeyGestures(test: TestData) { + @Parameters(method = "systemGesturesTestArguments_forKeyCombinations") + @EnableFlags( + com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER, + com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER_MULTI_KEY_GESTURES + ) + fun testKeyCombinationGestures(test: TestData) { + setupKeyGestureController() + testKeyGestureInternal(test) + } + + @Keep + private fun customInputGesturesTestArguments(): Array<TestData> { + return arrayOf( + TestData( + "META + ALT + Q -> Go Home", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_Q + ), + KeyGestureEvent.KEY_GESTURE_TYPE_HOME, + intArrayOf(KeyEvent.KEYCODE_Q), + KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, + intArrayOf( + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ) + ), + TestData( + "META + ALT + Q -> Launch app", + intArrayOf( + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_SHIFT_LEFT, + KeyEvent.KEYCODE_Q + ), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + intArrayOf(KeyEvent.KEYCODE_Q), + KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON, + intArrayOf( + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ), + AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest") + ), + ) + } + + @Test + @Parameters(method = "customInputGesturesTestArguments") + fun testCustomKeyGestures(test: TestData) { + setupKeyGestureController() + val builder = InputGestureData.Builder() + .setKeyGestureType(test.expectedKeyGestureType) + .setTrigger( + InputGestureData.createKeyTrigger( + test.expectedKeys[0], + test.expectedModifierState + ) + ) + if (test.expectedAppLaunchData != null) { + builder.setAppLaunchData(test.expectedAppLaunchData) + } + val inputGestureData = builder.build() + + keyGestureController.addCustomInputGesture(0, inputGestureData.aidlData) + testKeyGestureInternal(test) + } + + @Test + @Parameters(method = "customInputGesturesTestArguments") + fun testCustomKeyGesturesSavedAndLoadedByController(test: TestData) { + val userId = 10 + setupKeyGestureController() + val builder = InputGestureData.Builder() + .setKeyGestureType(test.expectedKeyGestureType) + .setTrigger( + InputGestureData.createKeyTrigger( + test.expectedKeys[0], + test.expectedModifierState + ) + ) + if (test.expectedAppLaunchData != null) { + builder.setAppLaunchData(test.expectedAppLaunchData) + } + val inputGestureData = builder.build() + + keyGestureController.setCurrentUserId(userId) + testLooper.dispatchAll() + keyGestureController.addCustomInputGesture(userId, inputGestureData.aidlData) + testLooper.dispatchAll() + + // Reinitialize the gesture controller simulating a login/logout for the user. + setupKeyGestureController() + keyGestureController.setCurrentUserId(userId) + testLooper.dispatchAll() + val savedInputGestures = keyGestureController.getCustomInputGestures(userId, null) + assertEquals( + "Test: $test doesn't produce correct number of saved input gestures", + 1, + savedInputGestures.size + ) + assertEquals( + "Test: $test doesn't produce correct input gesture data", inputGestureData, + InputGestureData(savedInputGestures[0]) + ) + } + + class TouchpadTestData( + val name: String, + val touchpadGestureType: Int, + val expectedKeyGestureType: Int, + val expectedAction: Int, + val expectedAppLaunchData: AppLaunchData? = null, + ) { + override fun toString(): String = name + } + + @Keep + private fun customTouchpadGesturesTestArguments(): Array<TouchpadTestData> { + return arrayOf( + TouchpadTestData( + "3 Finger Tap -> Go Home", + InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP, + KeyGestureEvent.KEY_GESTURE_TYPE_HOME, + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ), + TouchpadTestData( + "3 Finger Tap -> Launch app", + InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION, + KeyGestureEvent.ACTION_GESTURE_COMPLETE, + AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest") + ), + ) + } + + @Test + @Parameters(method = "customTouchpadGesturesTestArguments") + fun testCustomTouchpadGesture(test: TouchpadTestData) { + setupKeyGestureController() + val builder = InputGestureData.Builder() + .setKeyGestureType(test.expectedKeyGestureType) + .setTrigger(InputGestureData.createTouchpadTrigger(test.touchpadGestureType)) + if (test.expectedAppLaunchData != null) { + builder.setAppLaunchData(test.expectedAppLaunchData) + } + val inputGestureData = builder.build() + + keyGestureController.addCustomInputGesture(0, inputGestureData.aidlData) + + val handledEvents = mutableListOf<KeyGestureEvent>() val handler = KeyGestureHandler { event, _ -> - handleEvents.add(KeyGestureEvent(event)) + handledEvents.add(KeyGestureEvent(event)) true } keyGestureController.registerKeyGestureHandler(handler, 0) - handleEvents.clear() + handledEvents.clear() - sendKeys(test.keys, /* assertAllConsumed = */ false) + keyGestureController.handleTouchpadGesture(test.touchpadGestureType) + + assertEquals( + "Test: $test doesn't produce correct number of key gesture events", + 1, + handledEvents.size + ) + val event = handledEvents[0] + assertEquals( + "Test: $test doesn't produce correct key gesture type", + test.expectedKeyGestureType, + event.keyGestureType + ) + assertEquals( + "Test: $test doesn't produce correct key gesture action", + test.expectedAction, + event.action + ) + assertEquals( + "Test: $test doesn't produce correct app launch data", + test.expectedAppLaunchData, + event.appLaunchData + ) + + keyGestureController.unregisterKeyGestureHandler(handler, 0) + } + + @Test + @Parameters(method = "customTouchpadGesturesTestArguments") + fun testCustomTouchpadGesturesSavedAndLoadedByController(test: TouchpadTestData) { + val userId = 10 + setupKeyGestureController() + val builder = InputGestureData.Builder() + .setKeyGestureType(test.expectedKeyGestureType) + .setTrigger(InputGestureData.createTouchpadTrigger(test.touchpadGestureType)) + if (test.expectedAppLaunchData != null) { + builder.setAppLaunchData(test.expectedAppLaunchData) + } + val inputGestureData = builder.build() + keyGestureController.setCurrentUserId(userId) + testLooper.dispatchAll() + keyGestureController.addCustomInputGesture(userId, inputGestureData.aidlData) + testLooper.dispatchAll() + + // Reinitialize the gesture controller simulating a login/logout for the user. + setupKeyGestureController() + keyGestureController.setCurrentUserId(userId) + testLooper.dispatchAll() + val savedInputGestures = keyGestureController.getCustomInputGestures(userId, null) + assertEquals( + "Test: $test doesn't produce correct number of saved input gestures", + 1, + savedInputGestures.size + ) + assertEquals( + "Test: $test doesn't produce correct input gesture data", inputGestureData, + InputGestureData(savedInputGestures[0]) + ) + } + + private fun testKeyGestureInternal(test: TestData) { + val handledEvents = mutableListOf<KeyGestureEvent>() + val handler = KeyGestureHandler { event, _ -> + handledEvents.add(KeyGestureEvent(event)) + true + } + keyGestureController.registerKeyGestureHandler(handler, 0) + handledEvents.clear() + + sendKeys(test.keys) assertEquals( "Test: $test doesn't produce correct number of key gesture events", test.expectedActions.size, - handleEvents.size + handledEvents.size ) - for (i in handleEvents.indices) { - val event = handleEvents[i] + for (i in handledEvents.indices) { + val event = handledEvents[i] assertArrayEquals( "Test: $test doesn't produce correct key gesture keycodes", test.expectedKeys, @@ -570,60 +1609,39 @@ class KeyGestureControllerTests { test.expectedActions[i], event.action ) + assertEquals( + "Test: $test doesn't produce correct app launch data", + test.expectedAppLaunchData, + event.appLaunchData + ) } keyGestureController.unregisterKeyGestureHandler(handler, 0) } - @Test - fun testKeycodesFullyConsumed_irrespectiveOfHandlers() { - val testKeys = intArrayOf( - KeyEvent.KEYCODE_RECENT_APPS, - KeyEvent.KEYCODE_APP_SWITCH, - KeyEvent.KEYCODE_BRIGHTNESS_UP, - KeyEvent.KEYCODE_BRIGHTNESS_DOWN, - KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, - KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, - KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, - KeyEvent.KEYCODE_ALL_APPS, - KeyEvent.KEYCODE_NOTIFICATION, - KeyEvent.KEYCODE_SETTINGS, - KeyEvent.KEYCODE_LANGUAGE_SWITCH, - KeyEvent.KEYCODE_SCREENSHOT, - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_META_RIGHT, - KeyEvent.KEYCODE_ASSIST, - KeyEvent.KEYCODE_VOICE_ASSIST, - KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY, - KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY, - KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY, - KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL, - ) - - val handler = KeyGestureHandler { _, _ -> false } + private fun testKeyGestureNotProduced(testName: String, testKeys: IntArray) { + var handledEvents = mutableListOf<KeyGestureEvent>() + val handler = KeyGestureHandler { event, _ -> + handledEvents.add(KeyGestureEvent(event)) + true + } keyGestureController.registerKeyGestureHandler(handler, 0) + handledEvents.clear() - for (key in testKeys) { - sendKeys(intArrayOf(key), /* assertAllConsumed = */ true) - } + sendKeys(testKeys) + assertEquals("Test: $testName should not produce Key gesture", 0, handledEvents.size) } - private fun sendKeys(testKeys: IntArray, assertAllConsumed: Boolean) { + private fun sendKeys(testKeys: IntArray, assertNotSentToApps: Boolean = false) { var metaState = 0 + val now = SystemClock.uptimeMillis() for (key in testKeys) { val downEvent = KeyEvent( - /* downTime = */0, /* eventTime = */ 0, KeyEvent.ACTION_DOWN, key, - 0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, - 0 /*flags*/, InputDevice.SOURCE_KEYBOARD + now, now, KeyEvent.ACTION_DOWN, key, 0 /*repeat*/, metaState, + DEVICE_ID, 0 /*scancode*/, 0 /*flags*/, + InputDevice.SOURCE_KEYBOARD ) - val consumed = - keyGestureController.interceptKeyBeforeDispatching(null, downEvent, 0) == -1L - if (assertAllConsumed) { - assertTrue( - "interceptKeyBeforeDispatching should consume all events $downEvent", - consumed - ) - } + interceptKey(downEvent, assertNotSentToApps) metaState = metaState or MODIFIER.getOrDefault(key, 0) downEvent.recycle() @@ -632,24 +1650,35 @@ class KeyGestureControllerTests { for (key in testKeys.reversed()) { val upEvent = KeyEvent( - /* downTime = */0, /* eventTime = */ 0, KeyEvent.ACTION_UP, key, - 0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, - 0 /*flags*/, InputDevice.SOURCE_KEYBOARD + now, now, KeyEvent.ACTION_UP, key, 0 /*repeat*/, metaState, + DEVICE_ID, 0 /*scancode*/, 0 /*flags*/, + InputDevice.SOURCE_KEYBOARD ) - val consumed = - keyGestureController.interceptKeyBeforeDispatching(null, upEvent, 0) == -1L - if (assertAllConsumed) { - assertTrue( - "interceptKeyBeforeDispatching should consume all events $upEvent", - consumed - ) - } + interceptKey(upEvent, assertNotSentToApps) + metaState = metaState and MODIFIER.getOrDefault(key, 0).inv() upEvent.recycle() testLooper.dispatchAll() } } + private fun interceptKey(event: KeyEvent, assertNotSentToApps: Boolean) { + keyGestureController.interceptKeyBeforeQueueing(event, FLAG_INTERACTIVE) + testLooper.dispatchAll() + + val consumed = + keyGestureController.interceptKeyBeforeDispatching(null, event, 0) == -1L + if (assertNotSentToApps) { + assertTrue( + "interceptKeyBeforeDispatching should consume all events $event", + consumed + ) + } + if (!consumed) { + keyGestureController.interceptUnhandledKey(event, null) + } + } + inner class KeyGestureEventListener : IKeyGestureEventListener.Stub() { override fun onKeyGestureEvent(event: AidlKeyGestureEvent) { events.add(KeyGestureEvent(event)) @@ -667,4 +1696,4 @@ class KeyGestureControllerTests { return true } } -}
\ No newline at end of file +} diff --git a/tests/Input/src/com/android/server/input/KeyRemapperTests.kt b/tests/Input/src/com/android/server/input/KeyRemapperTests.kt index f74fd723d540..4f4c97bef4c0 100644 --- a/tests/Input/src/com/android/server/input/KeyRemapperTests.kt +++ b/tests/Input/src/com/android/server/input/KeyRemapperTests.kt @@ -18,16 +18,18 @@ package com.android.server.input import android.content.Context import android.content.ContextWrapper -import android.hardware.input.IInputManager import android.hardware.input.InputManager -import android.hardware.input.InputManagerGlobal import android.os.test.TestLooper import android.platform.test.annotations.Presubmit import android.provider.Settings import android.view.InputDevice import android.view.KeyEvent import androidx.test.core.app.ApplicationProvider -import org.junit.After +import com.android.test.input.MockInputManagerRule +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule @@ -35,10 +37,6 @@ import org.junit.Test import org.mockito.Mock import org.mockito.Mockito import org.mockito.junit.MockitoJUnit -import java.io.FileNotFoundException -import java.io.FileOutputStream -import java.io.IOException -import java.io.InputStream private fun createKeyboard(deviceId: Int): InputDevice = InputDevice.Builder() @@ -73,15 +71,15 @@ class KeyRemapperTests { @get:Rule val rule = MockitoJUnit.rule()!! - @Mock - private lateinit var iInputManager: IInputManager + @get:Rule + val inputManagerRule = MockInputManagerRule() + @Mock private lateinit var native: NativeInputManagerService private lateinit var mKeyRemapper: KeyRemapper private lateinit var context: Context private lateinit var dataStore: PersistentDataStore private lateinit var testLooper: TestLooper - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession @Before fun setup() { @@ -104,25 +102,17 @@ class KeyRemapperTests { dataStore, testLooper.looper ) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) val inputManager = InputManager(context) Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) .thenReturn(inputManager) - Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID)) - } - - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } + Mockito.`when`(inputManagerRule.mock.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID)) } @Test fun testKeyRemapping_whenRemappingEnabled() { ModifierRemappingFlag(true).use { val keyboard = createKeyboard(DEVICE_ID) - Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboard) + Mockito.`when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboard) for (i in REMAPPABLE_KEYS.indices) { val fromKeyCode = REMAPPABLE_KEYS[i] @@ -160,7 +150,7 @@ class KeyRemapperTests { fun testKeyRemapping_whenRemappingDisabled() { ModifierRemappingFlag(false).use { val keyboard = createKeyboard(DEVICE_ID) - Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboard) + Mockito.`when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboard) mKeyRemapper.remapKey(REMAPPABLE_KEYS[0], REMAPPABLE_KEYS[1]) testLooper.dispatchAll() diff --git a/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt index 59aa96c46336..644d5a0679de 100644 --- a/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt @@ -19,25 +19,27 @@ package com.android.server.input import android.animation.ValueAnimator import android.content.Context import android.content.ContextWrapper +import android.content.res.Resources import android.graphics.Color -import android.hardware.input.IInputManager import android.hardware.input.IKeyboardBacklightListener import android.hardware.input.IKeyboardBacklightState import android.hardware.input.InputManager -import android.hardware.input.InputManagerGlobal import android.hardware.lights.Light +import android.os.SystemProperties import android.os.UEventObserver import android.os.test.TestLooper import android.platform.test.annotations.Presubmit import android.view.InputDevice +import android.util.TypedValue import androidx.test.annotation.UiThreadTest import androidx.test.core.app.ApplicationProvider +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.internal.R +import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.server.input.KeyboardBacklightController.DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL import com.android.server.input.KeyboardBacklightController.MAX_BRIGHTNESS_CHANGE_STEPS -import com.android.server.input.KeyboardBacklightController.USER_INACTIVITY_THRESHOLD_MILLIS -import org.junit.After +import com.android.test.input.MockInputManagerRule import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -47,15 +49,11 @@ import org.junit.Rule import org.junit.Test import org.mockito.Mock import org.mockito.Mockito.any +import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyInt import org.mockito.Mockito.eq import org.mockito.Mockito.spy import org.mockito.Mockito.`when` -import org.mockito.junit.MockitoJUnit -import java.io.FileNotFoundException -import java.io.FileOutputStream -import java.io.IOException -import java.io.InputStream private fun createKeyboard(deviceId: Int): InputDevice = InputDevice.Builder() @@ -96,22 +94,24 @@ class KeyboardBacklightControllerTests { const val LIGHT_ID = 2 const val SECOND_LIGHT_ID = 3 const val MAX_BRIGHTNESS = 255 + const val USER_INACTIVITY_THRESHOLD_MILLIS = 30000 } @get:Rule - val rule = MockitoJUnit.rule()!! + val extendedMockitoRule = + ExtendedMockitoRule.Builder(this).mockStatic(SystemProperties::class.java).build()!! + @get:Rule + val inputManagerRule = MockInputManagerRule() @Mock - private lateinit var iInputManager: IInputManager - @Mock private lateinit var native: NativeInputManagerService @Mock private lateinit var uEventManager: UEventManager + @Mock + private lateinit var resources: Resources private lateinit var keyboardBacklightController: KeyboardBacklightController private lateinit var context: Context - private lateinit var dataStore: PersistentDataStore private lateinit var testLooper: TestLooper - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession private var lightColorMap: HashMap<Int, Int> = HashMap() private var lastBacklightState: KeyboardBacklightState? = null private var sysfsNodeChanges = 0 @@ -120,24 +120,12 @@ class KeyboardBacklightControllerTests { @Before fun setup() { context = spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - dataStore = PersistentDataStore(object : PersistentDataStore.Injector() { - override fun openRead(): InputStream? { - throw FileNotFoundException() - } - - override fun startWrite(): FileOutputStream? { - throw IOException() - } - - override fun finishWrite(fos: FileOutputStream?, success: Boolean) {} - }) + `when`(context.resources).thenReturn(resources) testLooper = TestLooper() - keyboardBacklightController = KeyboardBacklightController(context, native, dataStore, - testLooper.looper, FakeAnimatorFactory(), uEventManager) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) + setupConfig() val inputManager = InputManager(context) `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager) - `when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID)) + `when`(inputManagerRule.mock.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID)) `when`(native.setLightColor(anyInt(), anyInt(), anyInt())).then { val args = it.arguments lightColorMap.put(args[1] as Int, args[2] as Int) @@ -152,270 +140,191 @@ class KeyboardBacklightControllerTests { } } - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() + private fun setupConfig() { + val brightnessValues = intArrayOf(100, 200, 0) + val decreaseThresholds = intArrayOf(-1, 900, 1900) + val increaseThresholds = intArrayOf(1000, 2000, -1) + `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightBrightnessValues)) + .thenReturn(brightnessValues) + `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightDecreaseLuxThreshold)) + .thenReturn(decreaseThresholds) + `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightIncreaseLuxThreshold)) + .thenReturn(increaseThresholds) + `when`(resources.getInteger(R.integer.config_keyboardBacklightTimeoutMs)) + .thenReturn(USER_INACTIVITY_THRESHOLD_MILLIS) + `when`( + resources.getValue( + eq(R.dimen.config_autoKeyboardBrightnessSmoothingConstant), + any(TypedValue::class.java), + anyBoolean() + ) + ).then { + val args = it.arguments + val outValue = args[1] as TypedValue + outValue.data = java.lang.Float.floatToRawIntBits(1.0f) + Unit } } + private fun setupController() { + keyboardBacklightController = KeyboardBacklightController(context, native, + testLooper.looper, FakeAnimatorFactory(), uEventManager) + } + @Test fun testKeyboardBacklightIncrementDecrement() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = false, - ambientControlEnabled = false - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - - assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, - DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL) - } + setupController() + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + + assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, + DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL) } @Test fun testKeyboardWithoutBacklight() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = false, - ambientControlEnabled = false - ).use { - val keyboardWithoutBacklight = createKeyboard(DEVICE_ID) - val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight)) - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - - incrementKeyboardBacklight(DEVICE_ID) - assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty()) - } + setupController() + val keyboardWithoutBacklight = createKeyboard(DEVICE_ID) + val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + + incrementKeyboardBacklight(DEVICE_ID) + assertTrue("Non Keyboard backlights should not change", lightColorMap.isEmpty()) } @Test fun testKeyboardWithMultipleLight() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = false, - ambientControlEnabled = false - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn( - listOf( - keyboardBacklight, - keyboardInputLight - ) - ) - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - - incrementKeyboardBacklight(DEVICE_ID) - assertEquals("Only keyboard backlights should change", 1, lightColorMap.size) - assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID]) - assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID]) - } - } - - @Test - fun testRestoreBacklightOnInputDeviceAdded() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = false, - ambientControlEnabled = false - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - - for (level in 1 until DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size) { - dataStore.setKeyboardBacklightBrightness( - keyboardWithBacklight.descriptor, - LIGHT_ID, - DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[level] - 1 - ) - - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - keyboardBacklightController.notifyUserActivity() - testLooper.dispatchNext() - assertEquals( - "Keyboard backlight level should be restored to the level saved in the " + - "data store", - Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[level], 0, 0, 0), - lightColorMap[LIGHT_ID] - ) - keyboardBacklightController.onInputDeviceRemoved(DEVICE_ID) - } - } - } - - @Test - fun testRestoreBacklightOnInputDeviceChanged() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = false, - ambientControlEnabled = false - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - dataStore.setKeyboardBacklightBrightness( - keyboardWithBacklight.descriptor, - LIGHT_ID, - MAX_BRIGHTNESS - ) - - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - keyboardBacklightController.notifyUserActivity() - testLooper.dispatchNext() - assertTrue( - "Keyboard backlight should not be changed until its added", - lightColorMap.isEmpty() + setupController() + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) + val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn( + listOf( + keyboardBacklight, + keyboardInputLight ) + ) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - keyboardBacklightController.onInputDeviceChanged(DEVICE_ID) - keyboardBacklightController.notifyUserActivity() - testLooper.dispatchNext() - assertEquals( - "Keyboard backlight level should be restored to the level saved in the data store", - Color.argb(MAX_BRIGHTNESS, 0, 0, 0), - lightColorMap[LIGHT_ID] - ) - } + incrementKeyboardBacklight(DEVICE_ID) + assertEquals("Only keyboard backlights should change", 1, lightColorMap.size) + assertNotNull("Keyboard backlight should change", lightColorMap[LIGHT_ID]) + assertNull("Input lights should not change", lightColorMap[SECOND_LIGHT_ID]) } @Test fun testKeyboardBacklight_registerUnregisterListener() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = false, - ambientControlEnabled = false - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - val maxLevel = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size - 1 - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - - // Register backlight listener - val listener = KeyboardBacklightListener() - keyboardBacklightController.registerKeyboardBacklightListener(listener, 0) - - lastBacklightState = null - keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID) - testLooper.dispatchNext() + setupController() + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) + val maxLevel = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size - 1 + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + + // Register backlight listener + val listener = KeyboardBacklightListener() + keyboardBacklightController.registerKeyboardBacklightListener(listener, 0) + + lastBacklightState = null + keyboardBacklightController.incrementKeyboardBacklight(DEVICE_ID) + testLooper.dispatchNext() - assertEquals( - "Backlight state device Id should be $DEVICE_ID", - DEVICE_ID, - lastBacklightState!!.deviceId - ) - assertEquals( - "Backlight state brightnessLevel should be 1", - 1, - lastBacklightState!!.brightnessLevel - ) - assertEquals( - "Backlight state maxBrightnessLevel should be $maxLevel", - maxLevel, - lastBacklightState!!.maxBrightnessLevel - ) - assertEquals( - "Backlight state isTriggeredByKeyPress should be true", - true, - lastBacklightState!!.isTriggeredByKeyPress - ) + assertEquals( + "Backlight state device Id should be $DEVICE_ID", + DEVICE_ID, + lastBacklightState!!.deviceId + ) + assertEquals( + "Backlight state brightnessLevel should be 1", + 1, + lastBacklightState!!.brightnessLevel + ) + assertEquals( + "Backlight state maxBrightnessLevel should be $maxLevel", + maxLevel, + lastBacklightState!!.maxBrightnessLevel + ) + assertEquals( + "Backlight state isTriggeredByKeyPress should be true", + true, + lastBacklightState!!.isTriggeredByKeyPress + ) - // Unregister listener - keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0) + // Unregister listener + keyboardBacklightController.unregisterKeyboardBacklightListener(listener, 0) - lastBacklightState = null - incrementKeyboardBacklight(DEVICE_ID) + lastBacklightState = null + incrementKeyboardBacklight(DEVICE_ID) - assertNull("Listener should not receive any updates", lastBacklightState) - } + assertNull("Listener should not receive any updates", lastBacklightState) } @Test fun testKeyboardBacklight_userActivity() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = false, - ambientControlEnabled = false - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - dataStore.setKeyboardBacklightBrightness( - keyboardWithBacklight.descriptor, - LIGHT_ID, - MAX_BRIGHTNESS - ) - - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - keyboardBacklightController.notifyUserActivity() - testLooper.dispatchNext() - assertEquals( - "Keyboard backlight level should be restored to the level saved in the data store", - Color.argb(MAX_BRIGHTNESS, 0, 0, 0), - lightColorMap[LIGHT_ID] - ) + setupController() + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + incrementKeyboardBacklight(DEVICE_ID) + assertNotEquals( + "Keyboard backlight level should be incremented to a non-zero value", + 0, + lightColorMap[LIGHT_ID] + ) - testLooper.moveTimeForward(USER_INACTIVITY_THRESHOLD_MILLIS + 1000) - testLooper.dispatchNext() - assertEquals( - "Keyboard backlight level should be turned off after inactivity", - 0, - lightColorMap[LIGHT_ID] - ) - } + testLooper.moveTimeForward((USER_INACTIVITY_THRESHOLD_MILLIS + 1000).toLong()) + testLooper.dispatchNext() + assertEquals( + "Keyboard backlight level should be turned off after inactivity", + 0, + lightColorMap[LIGHT_ID] + ) } @Test fun testKeyboardBacklight_displayOnOff() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = false, - ambientControlEnabled = false - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - dataStore.setKeyboardBacklightBrightness( - keyboardWithBacklight.descriptor, - LIGHT_ID, - MAX_BRIGHTNESS - ) + setupController() + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + incrementKeyboardBacklight(DEVICE_ID) + + val currentValue = lightColorMap[LIGHT_ID] + assertNotEquals( + "Keyboard backlight level should be incremented to a non-zero value", + 0, + lightColorMap[LIGHT_ID] + ) - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - keyboardBacklightController.handleInteractiveStateChange(true /* isDisplayOn */) - assertEquals( - "Keyboard backlight level should be restored to the level saved in the data " + - "store when display turned on", - Color.argb(MAX_BRIGHTNESS, 0, 0, 0), - lightColorMap[LIGHT_ID] - ) + keyboardBacklightController.handleInteractiveStateChange(false /* isDisplayOn */) + assertEquals( + "Keyboard backlight level should be turned off after display is turned off", + 0, + lightColorMap[LIGHT_ID] + ) - keyboardBacklightController.handleInteractiveStateChange(false /* isDisplayOn */) - assertEquals( - "Keyboard backlight level should be turned off after display is turned off", - 0, - lightColorMap[LIGHT_ID] - ) - } + keyboardBacklightController.handleInteractiveStateChange(true /* isDisplayOn */) + assertEquals( + "Keyboard backlight level should be turned on after display is turned on", + currentValue, + lightColorMap[LIGHT_ID] + ) } @Test fun testKeyboardBacklightSysfsNodeAdded_AfterInputDeviceAdded() { + setupController() var counter = sysfsNodeChanges keyboardBacklightController.onKeyboardBacklightUEvent(UEventObserver.UEvent( "ACTION=add\u0000SUBSYSTEM=leds\u0000DEVPATH=/xyz/leds/abc::no_backlight\u0000" @@ -475,249 +384,160 @@ class KeyboardBacklightControllerTests { @Test @UiThreadTest fun testKeyboardBacklightAnimation_onChangeLevels() { - KeyboardBacklightFlags( - animationEnabled = true, - customLevelsEnabled = false, - ambientControlEnabled = false - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - - incrementKeyboardBacklight(DEVICE_ID) - assertEquals( - "Should start animation from level 0", - DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[0], - lastAnimationValues[0] - ) - assertEquals( - "Should start animation to level 1", - DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], - lastAnimationValues[1] - ) + ExtendedMockito.doReturn("true").`when` { + SystemProperties.get(eq("persist.input.keyboard.backlight_animation.enabled")) } + setupController() + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + + incrementKeyboardBacklight(DEVICE_ID) + assertEquals( + "Should start animation from level 0", + DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[0], + lastAnimationValues[0] + ) + assertEquals( + "Should start animation to level 1", + DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], + lastAnimationValues[1] + ) } @Test fun testKeyboardBacklightPreferredLevels() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = true, - ambientControlEnabled = false - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val suggestedLevels = intArrayOf(0, 22, 63, 135, 196, 255) - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT, - suggestedLevels) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - - assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, - suggestedLevels) - } + setupController() + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val suggestedLevels = intArrayOf(0, 22, 63, 135, 196, 255) + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT, + suggestedLevels) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + + assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, suggestedLevels) } @Test fun testKeyboardBacklightPreferredLevels_moreThanMax_shouldUseDefault() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = true, - ambientControlEnabled = false - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val suggestedLevels = IntArray(MAX_BRIGHTNESS_CHANGE_STEPS + 1) { 10 * (it + 1) } - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT, - suggestedLevels) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - - assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, - DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL) - } + setupController() + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val suggestedLevels = IntArray(MAX_BRIGHTNESS_CHANGE_STEPS + 1) { 10 * (it + 1) } + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT, + suggestedLevels) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + + assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, + DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL) } @Test fun testKeyboardBacklightPreferredLevels_mustHaveZeroAndMaxBrightnessAsBounds() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = true, - ambientControlEnabled = false - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val suggestedLevels = intArrayOf(22, 63, 135, 196) - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT, - suggestedLevels) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - - // Framework will add the lowest and maximum levels if not provided via config - assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, - intArrayOf(0, 22, 63, 135, 196, 255)) - } + setupController() + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val suggestedLevels = intArrayOf(22, 63, 135, 196) + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT, + suggestedLevels) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + + // Framework will add the lowest and maximum levels if not provided via config + assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, + intArrayOf(0, 22, 63, 135, 196, 255)) } @Test fun testKeyboardBacklightPreferredLevels_dropsOutOfBoundsLevels() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = true, - ambientControlEnabled = false - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val suggestedLevels = intArrayOf(22, 63, 135, 400, 196, 1000) - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT, - suggestedLevels) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - - // Framework will drop out of bound levels in the config - assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, - intArrayOf(0, 22, 63, 135, 196, 255)) - } - } - - @Test - fun testAmbientBacklightControl_doesntRestoreBacklightLevel() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = false, - ambientControlEnabled = true - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - - dataStore.setKeyboardBacklightBrightness( - keyboardWithBacklight.descriptor, - LIGHT_ID, - DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1] - ) - - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - keyboardBacklightController.notifyUserActivity() - testLooper.dispatchNext() - assertNull( - "Keyboard backlight level should not be restored to the saved level", - lightColorMap[LIGHT_ID] - ) - } - } - - @Test - fun testAmbientBacklightControl_doesntBackupBacklightLevel() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = false, - ambientControlEnabled = true - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - incrementKeyboardBacklight(DEVICE_ID) - assertFalse( - "Light value should not be backed up if ambient control is enabled", - dataStore.getKeyboardBacklightBrightness( - keyboardWithBacklight.descriptor, LIGHT_ID - ).isPresent - ) - } + setupController() + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val suggestedLevels = intArrayOf(22, 63, 135, 400, 196, 1000) + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT, + suggestedLevels) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + + // Framework will drop out of bound levels in the config + assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, + intArrayOf(0, 22, 63, 135, 196, 255)) } @Test fun testAmbientBacklightControl_incrementLevel_afterAmbientChange() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = false, - ambientControlEnabled = true - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - sendAmbientBacklightValue(1) - assertEquals( - "Light value should be changed to ambient provided value", - Color.argb(1, 0, 0, 0), - lightColorMap[LIGHT_ID] - ) + setupController() + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + sendAmbientBacklightValue(1) + assertEquals( + "Light value should be changed to ambient provided value", + Color.argb(1, 0, 0, 0), + lightColorMap[LIGHT_ID] + ) - incrementKeyboardBacklight(DEVICE_ID) + incrementKeyboardBacklight(DEVICE_ID) - assertEquals( - "Light value for level after increment post Ambient change is mismatched", - Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0), - lightColorMap[LIGHT_ID] - ) - } + assertEquals( + "Light value for level after increment post Ambient change is mismatched", + Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0), + lightColorMap[LIGHT_ID] + ) } @Test fun testAmbientBacklightControl_decrementLevel_afterAmbientChange() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = false, - ambientControlEnabled = true - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - sendAmbientBacklightValue(254) - assertEquals( - "Light value should be changed to ambient provided value", - Color.argb(254, 0, 0, 0), - lightColorMap[LIGHT_ID] - ) + setupController() + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + sendAmbientBacklightValue(254) + assertEquals( + "Light value should be changed to ambient provided value", + Color.argb(254, 0, 0, 0), + lightColorMap[LIGHT_ID] + ) - decrementKeyboardBacklight(DEVICE_ID) + decrementKeyboardBacklight(DEVICE_ID) - val numLevels = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size - assertEquals( - "Light value for level after decrement post Ambient change is mismatched", - Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[numLevels - 2], 0, 0, 0), - lightColorMap[LIGHT_ID] - ) - } + val numLevels = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size + assertEquals( + "Light value for level after decrement post Ambient change is mismatched", + Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[numLevels - 2], 0, 0, 0), + lightColorMap[LIGHT_ID] + ) } @Test fun testAmbientBacklightControl_ambientChanges_afterManualChange() { - KeyboardBacklightFlags( - animationEnabled = false, - customLevelsEnabled = false, - ambientControlEnabled = true - ).use { - val keyboardWithBacklight = createKeyboard(DEVICE_ID) - val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) - keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) - incrementKeyboardBacklight(DEVICE_ID) - assertEquals( - "Light value should be changed to the first level", - Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0), - lightColorMap[LIGHT_ID] - ) + setupController() + val keyboardWithBacklight = createKeyboard(DEVICE_ID) + val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) + incrementKeyboardBacklight(DEVICE_ID) + assertEquals( + "Light value should be changed to the first level", + Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0), + lightColorMap[LIGHT_ID] + ) - sendAmbientBacklightValue(100) - assertNotEquals( - "Light value should not change based on ambient changes after manual changes", - Color.argb(100, 0, 0, 0), - lightColorMap[LIGHT_ID] - ) - } + sendAmbientBacklightValue(100) + assertNotEquals( + "Light value should not change based on ambient changes after manual changes", + Color.argb(100, 0, 0, 0), + lightColorMap[LIGHT_ID] + ) } private fun assertIncrementDecrementForLevels( @@ -734,11 +554,6 @@ class KeyboardBacklightControllerTests { Color.argb(expectedLevels[level], 0, 0, 0), lightColorMap[lightId] ) - assertEquals( - "Light value for level $level must be correctly stored in the datastore", - expectedLevels[level], - dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt - ) } // Increment above max level @@ -748,11 +563,6 @@ class KeyboardBacklightControllerTests { Color.argb(MAX_BRIGHTNESS, 0, 0, 0), lightColorMap[lightId] ) - assertEquals( - "Light value for max level must be correctly stored in the datastore", - MAX_BRIGHTNESS, - dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt - ) for (level in expectedLevels.size - 2 downTo 0) { decrementKeyboardBacklight(deviceId) @@ -761,11 +571,6 @@ class KeyboardBacklightControllerTests { Color.argb(expectedLevels[level], 0, 0, 0), lightColorMap[lightId] ) - assertEquals( - "Light value for level $level must be correctly stored in the datastore", - expectedLevels[level], - dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt - ) } // Decrement below min level @@ -775,11 +580,6 @@ class KeyboardBacklightControllerTests { Color.argb(0, 0, 0, 0), lightColorMap[lightId] ) - assertEquals( - "Light value for min level must be correctly stored in the datastore", - 0, - dataStore.getKeyboardBacklightBrightness(device.descriptor, lightId).asInt - ) } inner class KeyboardBacklightListener : IKeyboardBacklightListener.Stub() { @@ -822,23 +622,6 @@ class KeyboardBacklightControllerTests { val isTriggeredByKeyPress: Boolean ) - private inner class KeyboardBacklightFlags constructor( - animationEnabled: Boolean, - customLevelsEnabled: Boolean, - ambientControlEnabled: Boolean - ) : AutoCloseable { - init { - InputFeatureFlagProvider.setKeyboardBacklightAnimationEnabled(animationEnabled) - InputFeatureFlagProvider.setKeyboardBacklightCustomLevelsEnabled(customLevelsEnabled) - InputFeatureFlagProvider - .setAmbientKeyboardBacklightControlEnabled(ambientControlEnabled) - } - - override fun close() { - InputFeatureFlagProvider.clearOverrides() - } - } - private inner class FakeAnimatorFactory : KeyboardBacklightController.AnimatorFactory { override fun makeIntAnimator(from: Int, to: Int): ValueAnimator { lastAnimationValues[0] = from diff --git a/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt index c073c7aae678..5da0beb9cc8a 100644 --- a/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt @@ -23,9 +23,7 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.ResolveInfo import android.content.pm.ServiceInfo -import android.hardware.input.IInputManager import android.hardware.input.InputManager -import android.hardware.input.InputManagerGlobal import android.hardware.input.KeyGlyphMap.KeyCombination import android.os.Bundle import android.os.test.TestLooper @@ -36,8 +34,8 @@ import android.view.InputDevice import android.view.KeyEvent import androidx.test.core.app.ApplicationProvider import com.android.hardware.input.Flags +import com.android.test.input.MockInputManagerRule import com.android.test.input.R -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -61,34 +59,31 @@ class KeyboardGlyphManagerTests { const val DEVICE_ID = 1 const val VENDOR_ID = 0x1234 const val PRODUCT_ID = 0x3456 + const val DEVICE_ID2 = 2 + const val VENDOR_ID2 = 0x1235 + const val PRODUCT_ID2 = 0x3457 const val PACKAGE_NAME = "KeyboardLayoutManagerTests" const val RECEIVER_NAME = "DummyReceiver" } - @JvmField - @Rule(order = 0) + @get:Rule val setFlagsRule = SetFlagsRule() - - @JvmField - @Rule(order = 1) + @get:Rule val mockitoRule = MockitoJUnit.rule()!! + @get:Rule + val inputManagerRule = MockInputManagerRule() @Mock private lateinit var packageManager: PackageManager - @Mock - private lateinit var iInputManager: IInputManager - private lateinit var keyboardGlyphManager: KeyboardGlyphManager private lateinit var context: Context private lateinit var testLooper: TestLooper - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession private lateinit var keyboardDevice: InputDevice @Before fun setup() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) testLooper = TestLooper() keyboardGlyphManager = KeyboardGlyphManager(context, testLooper.looper) @@ -98,21 +93,17 @@ class KeyboardGlyphManagerTests { testLooper.dispatchAll() } - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } - } - private fun setupInputDevices() { val inputManager = InputManager(context) Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) .thenReturn(inputManager) keyboardDevice = createKeyboard(DEVICE_ID, VENDOR_ID, PRODUCT_ID, 0, "", "") - Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID)) - Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice) + Mockito.`when`(inputManagerRule.mock.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID, DEVICE_ID2)) + Mockito.`when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice) + + val keyboardDevice2 = createKeyboard(DEVICE_ID2, VENDOR_ID2, PRODUCT_ID2, 0, "", "") + Mockito.`when`(inputManagerRule.mock.getInputDevice(DEVICE_ID2)).thenReturn(keyboardDevice2) } private fun setupBroadcastReceiver() { @@ -158,6 +149,10 @@ class KeyboardGlyphManagerTests { "Glyph map for test keyboard(deviceId=$DEVICE_ID) must exist", keyboardGlyphManager.getKeyGlyphMap(DEVICE_ID) ) + assertNotNull( + "Glyph map for test keyboard(deviceId=$DEVICE_ID2) must exist", + keyboardGlyphManager.getKeyGlyphMap(DEVICE_ID2) + ) assertNull( "Glyph map for non-existing keyboard must be null", keyboardGlyphManager.getKeyGlyphMap(-2) @@ -173,6 +168,7 @@ class KeyboardGlyphManagerTests { assertNotNull(glyphMap.getDrawableForModifier(context, KeyEvent.KEYCODE_META_LEFT)) assertNotNull(glyphMap.getDrawableForModifier(context, KeyEvent.KEYCODE_META_RIGHT)) + assertNotNull(glyphMap.getDrawableForModifierState(context, KeyEvent.META_META_ON)) val functionRowKeys = glyphMap.functionRowKeys assertEquals(1, functionRowKeys.size) diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt index 301c0e6a159f..d6654cceb458 100644 --- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt @@ -24,11 +24,10 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.ResolveInfo import android.content.pm.ServiceInfo -import android.hardware.input.KeyboardLayoutSelectionResult -import android.hardware.input.IInputManager import android.hardware.input.InputManager import android.hardware.input.InputManagerGlobal import android.hardware.input.KeyboardLayout +import android.hardware.input.KeyboardLayoutSelectionResult import android.icu.util.ULocale import android.os.Bundle import android.os.test.TestLooper @@ -42,8 +41,12 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.internal.os.KeyboardConfiguredProto import com.android.internal.util.FrameworkStatsLog import com.android.modules.utils.testing.ExtendedMockitoRule +import com.android.test.input.MockInputManagerRule import com.android.test.input.R -import org.junit.After +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue @@ -53,10 +56,6 @@ import org.junit.Test import org.mockito.ArgumentMatchers import org.mockito.Mock import org.mockito.Mockito -import java.io.FileNotFoundException -import java.io.FileOutputStream -import java.io.IOException -import java.io.InputStream fun createKeyboard( deviceId: Int, @@ -120,8 +119,8 @@ class KeyboardLayoutManagerTests { val extendedMockitoRule = ExtendedMockitoRule.Builder(this) .mockStatic(FrameworkStatsLog::class.java).build()!! - @Mock - private lateinit var iInputManager: IInputManager + @get:Rule + val inputManagerRule = MockInputManagerRule() @Mock private lateinit var native: NativeInputManagerService @@ -148,7 +147,6 @@ class KeyboardLayoutManagerTests { @Before fun setup() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) dataStore = PersistentDataStore(object : PersistentDataStore.Injector() { override fun openRead(): InputStream? { throw FileNotFoundException() @@ -171,13 +169,6 @@ class KeyboardLayoutManagerTests { setupIme() } - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } - } - private fun setupInputDevices() { val inputManager = InputManager(context) Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) @@ -191,19 +182,19 @@ class KeyboardLayoutManagerTests { DEFAULT_PRODUCT_ID, DEFAULT_DEVICE_BUS, "en", "dvorak") englishQwertyKeyboardDevice = createKeyboard(ENGLISH_QWERTY_DEVICE_ID, DEFAULT_VENDOR_ID, DEFAULT_PRODUCT_ID, DEFAULT_DEVICE_BUS, "en", "qwerty") - Mockito.`when`(iInputManager.inputDeviceIds) + Mockito.`when`(inputManagerRule.mock.inputDeviceIds) .thenReturn(intArrayOf( DEVICE_ID, VENDOR_SPECIFIC_DEVICE_ID, ENGLISH_DVORAK_DEVICE_ID, ENGLISH_QWERTY_DEVICE_ID )) - Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice) - Mockito.`when`(iInputManager.getInputDevice(VENDOR_SPECIFIC_DEVICE_ID)) + Mockito.`when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice) + Mockito.`when`(inputManagerRule.mock.getInputDevice(VENDOR_SPECIFIC_DEVICE_ID)) .thenReturn(vendorSpecificKeyboardDevice) - Mockito.`when`(iInputManager.getInputDevice(ENGLISH_DVORAK_DEVICE_ID)) + Mockito.`when`(inputManagerRule.mock.getInputDevice(ENGLISH_DVORAK_DEVICE_ID)) .thenReturn(englishDvorakKeyboardDevice) - Mockito.`when`(iInputManager.getInputDevice(ENGLISH_QWERTY_DEVICE_ID)) + Mockito.`when`(inputManagerRule.mock.getInputDevice(ENGLISH_QWERTY_DEVICE_ID)) .thenReturn(englishQwertyKeyboardDevice) } diff --git a/tests/Input/src/com/android/server/input/PointerIconCacheTest.kt b/tests/Input/src/com/android/server/input/PointerIconCacheTest.kt new file mode 100644 index 000000000000..47e7ac720a08 --- /dev/null +++ b/tests/Input/src/com/android/server/input/PointerIconCacheTest.kt @@ -0,0 +1,135 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.input + +import android.content.Context +import android.content.ContextWrapper +import android.os.Handler +import android.os.test.TestLooper +import android.platform.test.annotations.Presubmit +import android.view.Display +import android.view.PointerIcon +import androidx.test.platform.app.InstrumentationRegistry +import junit.framework.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +/** + * Tests for {@link PointerIconCache}. + */ +@Presubmit +class PointerIconCacheTest { + + @get:Rule + val rule = MockitoJUnit.rule()!! + + @Mock + private lateinit var native: NativeInputManagerService + @Mock + private lateinit var defaultDisplay: Display + + private lateinit var context: Context + private lateinit var testLooper: TestLooper + private lateinit var cache: PointerIconCache + + @Before + fun setup() { + whenever(defaultDisplay.displayId).thenReturn(Display.DEFAULT_DISPLAY) + + context = object : ContextWrapper(InstrumentationRegistry.getInstrumentation().context) { + override fun getDisplay() = defaultDisplay + } + + testLooper = TestLooper() + cache = PointerIconCache(context, native, Handler(testLooper.looper)) + } + + @Test + fun testSetPointerScale() { + val defaultBitmap = getDefaultIcon().bitmap + cache.setPointerScale(2f) + + testLooper.dispatchAll() + verify(native).reloadPointerIcons() + + val bitmap = + cache.getLoadedPointerIcon(Display.DEFAULT_DISPLAY, PointerIcon.TYPE_ARROW).bitmap + + assertEquals(defaultBitmap.height * 2, bitmap.height) + assertEquals(defaultBitmap.width * 2, bitmap.width) + } + + @Test + fun testSetAccessibilityScaleFactor() { + val defaultBitmap = getDefaultIcon().bitmap + cache.setAccessibilityScaleFactor(Display.DEFAULT_DISPLAY, 4f) + + testLooper.dispatchAll() + verify(native).reloadPointerIcons() + + val bitmap = + cache.getLoadedPointerIcon(Display.DEFAULT_DISPLAY, PointerIcon.TYPE_ARROW).bitmap + + assertEquals(defaultBitmap.height * 4, bitmap.height) + assertEquals(defaultBitmap.width * 4, bitmap.width) + } + + @Test + fun testSetAccessibilityScaleFactorOnSecondaryDisplay() { + val defaultBitmap = getDefaultIcon().bitmap + val secondaryDisplayId = Display.DEFAULT_DISPLAY + 1 + cache.setAccessibilityScaleFactor(secondaryDisplayId, 4f) + + testLooper.dispatchAll() + verify(native).reloadPointerIcons() + + val bitmap = + cache.getLoadedPointerIcon(Display.DEFAULT_DISPLAY, PointerIcon.TYPE_ARROW).bitmap + assertEquals(defaultBitmap.height, bitmap.height) + assertEquals(defaultBitmap.width, bitmap.width) + + val bitmapSecondary = + cache.getLoadedPointerIcon(secondaryDisplayId, PointerIcon.TYPE_ARROW).bitmap + assertEquals(defaultBitmap.height * 4, bitmapSecondary.height) + assertEquals(defaultBitmap.width * 4, bitmapSecondary.width) + } + + @Test + fun testSetPointerScaleAndAccessibilityScaleFactor() { + val defaultBitmap = getDefaultIcon().bitmap + cache.setPointerScale(2f) + cache.setAccessibilityScaleFactor(Display.DEFAULT_DISPLAY, 3f) + + testLooper.dispatchAll() + verify(native, times(2)).reloadPointerIcons() + + val bitmap = + cache.getLoadedPointerIcon(Display.DEFAULT_DISPLAY, PointerIcon.TYPE_ARROW).bitmap + + assertEquals(defaultBitmap.height * 6, bitmap.height) + assertEquals(defaultBitmap.width * 6, bitmap.width) + } + + private fun getDefaultIcon() = + PointerIcon.getLoadedSystemIcon(context, PointerIcon.TYPE_ARROW, false, 1f) +} diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java index b5258dfc9c3c..60fa52f85e34 100644 --- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java +++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java @@ -402,4 +402,73 @@ public class TouchpadDebugViewTest { // Verify that no updateViewLayout is called (as expected for a two-finger drag gesture). verify(mWindowManager, times(0)).updateViewLayout(any(), any()); } -}
\ No newline at end of file + + @Test + public void testPinchDrag() { + float offsetY = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10; + + MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_MOUSE) + .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(40f) + ) + .classification(MotionEvent.CLASSIFICATION_PINCH) + .build(); + mTouchpadDebugView.dispatchTouchEvent(actionDown); + + MotionEvent pointerDown = new MotionEventBuilder(MotionEvent.ACTION_POINTER_DOWN, + SOURCE_MOUSE) + .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(40f) + ) + .pointer(new PointerBuilder(1, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(45f) + ) + .classification(MotionEvent.CLASSIFICATION_PINCH) + .build(); + mTouchpadDebugView.dispatchTouchEvent(pointerDown); + + // Simulate ACTION_MOVE event (both fingers moving apart). + MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_MOUSE) + .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(40f - offsetY) + ) + .rawXCursorPosition(mWindowLayoutParams.x + 10f) + .rawYCursorPosition(mWindowLayoutParams.y + 10f) + .pointer(new PointerBuilder(1, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(45f + offsetY) + ) + .classification(MotionEvent.CLASSIFICATION_PINCH) + .build(); + mTouchpadDebugView.dispatchTouchEvent(actionMove); + + MotionEvent pointerUp = new MotionEventBuilder(MotionEvent.ACTION_POINTER_UP, SOURCE_MOUSE) + .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(40f - offsetY) + ) + .pointer(new PointerBuilder(1, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(45f + offsetY) + ) + .classification(MotionEvent.CLASSIFICATION_PINCH) + .build(); + mTouchpadDebugView.dispatchTouchEvent(pointerUp); + + MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_MOUSE) + .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(40f - offsetY) + ) + .classification(MotionEvent.CLASSIFICATION_PINCH) + .build(); + mTouchpadDebugView.dispatchTouchEvent(actionUp); + + // Verify that no updateViewLayout is called (as expected for a two-finger drag gesture). + verify(mWindowManager, times(0)).updateViewLayout(any(), any()); + } +} diff --git a/tests/Input/src/com/android/test/input/MockInputManagerRule.kt b/tests/Input/src/com/android/test/input/MockInputManagerRule.kt new file mode 100644 index 000000000000..cef985600c40 --- /dev/null +++ b/tests/Input/src/com/android/test/input/MockInputManagerRule.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.hardware.input.IInputManager +import android.hardware.input.InputManagerGlobal +import org.junit.rules.ExternalResource +import org.mockito.Mockito + +/** + * A test rule that temporarily replaces the [IInputManager] connection to the server with a mock + * to be used for testing. + */ +class MockInputManagerRule : ExternalResource() { + + private lateinit var session: InputManagerGlobal.TestSession + + val mock: IInputManager = Mockito.mock(IInputManager::class.java) + + override fun before() { + session = InputManagerGlobal.createTestSession(mock) + } + + override fun after() { + if (this::session.isInitialized) { + session.close() + } + } +} diff --git a/tests/Internal/Android.bp b/tests/Internal/Android.bp index 3e58517579b8..9f35c7b7fa33 100644 --- a/tests/Internal/Android.bp +++ b/tests/Internal/Android.bp @@ -32,6 +32,27 @@ android_test { test_suites: ["device-tests"], } +// Run just ApplicationSharedMemoryTest with ABI override for 32 bits. +// This is to test that on systems that support multi-ABI, +// ApplicationSharedMemory works in app processes launched with a different ABI +// than that of the system processes. +android_test { + name: "ApplicationSharedMemoryTest32", + team: "trendy_team_system_performance", + srcs: ["src/com/android/internal/os/ApplicationSharedMemoryTest.java"], + libs: ["android.test.runner.stubs.system"], + static_libs: [ + "junit", + "androidx.test.rules", + "platform-test-annotations", + ], + manifest: "ApplicationSharedMemoryTest32/AndroidManifest.xml", + test_config: "ApplicationSharedMemoryTest32/AndroidTest.xml", + certificate: "platform", + platform_apis: true, + test_suites: ["device-tests"], +} + android_ravenwood_test { name: "InternalTestsRavenwood", static_libs: [ @@ -45,3 +66,9 @@ android_ravenwood_test { ], auto_gen_config: true, } + +java_test_helper_library { + name: "ApplicationSharedMemoryTestRule", + srcs: ["src/com/android/internal/os/ApplicationSharedMemoryTestRule.java"], + static_libs: ["junit"], +} diff --git a/tests/Internal/ApplicationSharedMemoryTest32/AndroidManifest.xml b/tests/Internal/ApplicationSharedMemoryTest32/AndroidManifest.xml new file mode 100644 index 000000000000..4e1058ead734 --- /dev/null +++ b/tests/Internal/ApplicationSharedMemoryTest32/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT 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.internal.tests"> + <application + android:use32bitAbi="true" + android:multiArch="true"> + <uses-library android:name="android.test.runner"/> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.internal.tests" + android:label="Internal Tests"/> +</manifest> diff --git a/tests/Internal/ApplicationSharedMemoryTest32/AndroidTest.xml b/tests/Internal/ApplicationSharedMemoryTest32/AndroidTest.xml new file mode 100644 index 000000000000..9bde8b7522e3 --- /dev/null +++ b/tests/Internal/ApplicationSharedMemoryTest32/AndroidTest.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<configuration description="Runs tests for internal classes/utilities."> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="ApplicationSharedMemoryTest32.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="framework-base-presubmit" /> + <option name="test-tag" value="InternalTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.internal.tests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + </test> + + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="pull-pattern-keys" value="perfetto_file_path"/> + <option name="directory-keys" + value="/data/user/0/com.android.internal.tests/files"/> + <option name="collect-on-run-ended-only" value="true"/> + <option name="clean-up" value="true"/> + </metrics_collector> +</configuration>
\ No newline at end of file diff --git a/tests/Internal/ApplicationSharedMemoryTest32/OWNERS b/tests/Internal/ApplicationSharedMemoryTest32/OWNERS new file mode 100644 index 000000000000..1ff3fac8ae6f --- /dev/null +++ b/tests/Internal/ApplicationSharedMemoryTest32/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/base:/PERFORMANCE_OWNERS
\ No newline at end of file diff --git a/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java new file mode 100644 index 000000000000..d03ad5cb2877 --- /dev/null +++ b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import java.io.IOException; + +import static org.junit.Assert.fail; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assume.assumeTrue; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.junit.Before; + +import java.io.FileDescriptor; + +/** Tests for {@link TimeoutRecord}. */ +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class ApplicationSharedMemoryTest { + + @Before + public void setUp() { + // Skip tests if the feature under test is disabled. + assumeTrue(Flags.applicationSharedMemoryEnabled()); + } + + /** + * Every application process, including ours, should have had an instance installed at this + * point. + */ + @Test + public void hasInstance() { + // This shouldn't throw and shouldn't return null. + assertNotNull(ApplicationSharedMemory.getInstance()); + } + + /** Any app process should be able to read shared memory values. */ + @Test + public void canRead() { + ApplicationSharedMemory instance = ApplicationSharedMemory.getInstance(); + try { + instance.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(); + // Don't actually care about the value of the above. + } catch (java.time.DateTimeException e) { + // This exception is okay during testing. It means there was no time source, which + // could be because of network problems or a feature being flagged off. + } + } + + /** Application processes should not have mutable access. */ + @Test + public void appInstanceNotMutable() { + ApplicationSharedMemory instance = ApplicationSharedMemory.getInstance(); + try { + instance.setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(17); + fail("Attempted mutation in an app process should throw"); + } catch (Exception expected) { + } + } + + /** Instances share memory if they share the underlying memory region. */ + @Test + public void instancesShareMemory() throws IOException { + ApplicationSharedMemory instance1 = ApplicationSharedMemory.create(); + ApplicationSharedMemory instance2 = + ApplicationSharedMemory.fromFileDescriptor( + instance1.getFileDescriptor(), /* mutable= */ true); + ApplicationSharedMemory instance3 = + ApplicationSharedMemory.fromFileDescriptor( + instance2.getReadOnlyFileDescriptor(), /* mutable= */ false); + + instance1.setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(17); + assertEquals( + 17, instance1.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis()); + assertEquals( + 17, instance2.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis()); + assertEquals( + 17, instance3.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis()); + + instance2.setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(24); + assertEquals( + 24, instance1.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis()); + assertEquals( + 24, instance2.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis()); + assertEquals( + 24, instance3.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis()); + } + + /** Can't map read-only memory as mutable. */ + @Test + public void readOnlyCantBeMutable() throws IOException { + ApplicationSharedMemory readWriteInstance = ApplicationSharedMemory.create(); + FileDescriptor readOnlyFileDescriptor = readWriteInstance.getReadOnlyFileDescriptor(); + + try { + ApplicationSharedMemory.fromFileDescriptor(readOnlyFileDescriptor, /* mutable= */ true); + fail("Shouldn't be able to map read-only memory as mutable"); + } catch (Exception expected) { + } + } +} diff --git a/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTestRule.java b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTestRule.java new file mode 100644 index 000000000000..ff2a4611cdf0 --- /dev/null +++ b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTestRule.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import com.android.internal.os.ApplicationSharedMemory; + +/** Test rule that sets up and tears down ApplicationSharedMemory for test. */ +public class ApplicationSharedMemoryTestRule implements TestRule { + + private ApplicationSharedMemory mSavedInstance; + + @Override + public Statement apply(final Statement base, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + setup(); + try { + base.evaluate(); // Run the test + } finally { + teardown(); + } + } + }; + } + + private void setup() { + mSavedInstance = ApplicationSharedMemory.sInstance; + ApplicationSharedMemory.sInstance = ApplicationSharedMemory.create(); + } + + private void teardown() { + ApplicationSharedMemory.sInstance.close(); + ApplicationSharedMemory.sInstance = mSavedInstance; + mSavedInstance = null; + } +} diff --git a/tests/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp index 096555eb3056..8be74eaccd20 100644 --- a/tests/PackageWatchdog/Android.bp +++ b/tests/PackageWatchdog/Android.bp @@ -35,7 +35,13 @@ android_test { "services.core", "services.net", "truth", - ], + ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), { + "true": [ + "service-crashrecovery-pre-jarjar", + "framework-crashrecovery.impl", + ], + default: [], + }), libs: ["android.test.runner.stubs.system"], jni_libs: [ // mockito-target-extended dependencies diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java index c0e90f9232d6..49616c30b784 100644 --- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java +++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java @@ -83,6 +83,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Set; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -112,6 +113,7 @@ public class CrashRecoveryTest { private final TestClock mTestClock = new TestClock(); private TestLooper mTestLooper; + private Executor mTestExecutor; private Context mSpyContext; // Keep track of all created watchdogs to apply device config changes private List<PackageWatchdog> mAllocatedWatchdogs; @@ -138,8 +140,10 @@ public class CrashRecoveryTest { new File(InstrumentationRegistry.getContext().getFilesDir(), "package-watchdog.xml").delete(); adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG, - Manifest.permission.WRITE_DEVICE_CONFIG); + Manifest.permission.WRITE_DEVICE_CONFIG, + Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG); mTestLooper = new TestLooper(); + mTestExecutor = mTestLooper.getNewExecutor(); mSpyContext = spy(InstrumentationRegistry.getContext()); when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager); when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> { @@ -224,39 +228,45 @@ public class CrashRecoveryTest { PackageWatchdog watchdog = createWatchdog(); RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(1); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(1); for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { watchdog.noteBoot(); } - verify(rescuePartyObserver).executeBootLoopMitigation(1); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(2); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver).onExecuteBootLoopMitigation(1); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2); watchdog.noteBoot(); - verify(rescuePartyObserver).executeBootLoopMitigation(2); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(3); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver).onExecuteBootLoopMitigation(2); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3); watchdog.noteBoot(); - verify(rescuePartyObserver).executeBootLoopMitigation(3); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(4); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver).onExecuteBootLoopMitigation(3); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(4); watchdog.noteBoot(); - verify(rescuePartyObserver).executeBootLoopMitigation(4); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(5); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver).onExecuteBootLoopMitigation(4); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(5); watchdog.noteBoot(); - verify(rescuePartyObserver).executeBootLoopMitigation(5); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(6); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver).onExecuteBootLoopMitigation(5); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6); watchdog.noteBoot(); - verify(rescuePartyObserver).executeBootLoopMitigation(6); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(7); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver).onExecuteBootLoopMitigation(6); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(7); } @Test @@ -265,14 +275,15 @@ public class CrashRecoveryTest { RollbackPackageHealthObserver rollbackObserver = setUpRollbackPackageHealthObserver(watchdog); - verify(rollbackObserver, never()).executeBootLoopMitigation(1); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1); for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { watchdog.noteBoot(); } - verify(rollbackObserver).executeBootLoopMitigation(1); - verify(rollbackObserver, never()).executeBootLoopMitigation(2); + mTestLooper.dispatchAll(); + verify(rollbackObserver).onExecuteBootLoopMitigation(1); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2); // Update the list of available rollbacks after executing bootloop mitigation once when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH, @@ -280,15 +291,17 @@ public class CrashRecoveryTest { watchdog.noteBoot(); - verify(rollbackObserver).executeBootLoopMitigation(2); - verify(rollbackObserver, never()).executeBootLoopMitigation(3); + mTestLooper.dispatchAll(); + verify(rollbackObserver).onExecuteBootLoopMitigation(2); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3); // Update the list of available rollbacks after executing bootloop mitigation once when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL)); watchdog.noteBoot(); - verify(rollbackObserver, never()).executeBootLoopMitigation(3); + mTestLooper.dispatchAll(); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3); } @Test @@ -299,61 +312,69 @@ public class CrashRecoveryTest { RollbackPackageHealthObserver rollbackObserver = setUpRollbackPackageHealthObserver(watchdog); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(1); - verify(rollbackObserver, never()).executeBootLoopMitigation(1); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(1); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1); for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { watchdog.noteBoot(); } - verify(rescuePartyObserver).executeBootLoopMitigation(1); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(2); - verify(rollbackObserver, never()).executeBootLoopMitigation(1); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver).onExecuteBootLoopMitigation(1); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1); watchdog.noteBoot(); - verify(rescuePartyObserver).executeBootLoopMitigation(2); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(3); - verify(rollbackObserver, never()).executeBootLoopMitigation(2); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver).onExecuteBootLoopMitigation(2); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2); watchdog.noteBoot(); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(3); - verify(rollbackObserver).executeBootLoopMitigation(1); - verify(rollbackObserver, never()).executeBootLoopMitigation(2); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3); + verify(rollbackObserver).onExecuteBootLoopMitigation(1); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2); // Update the list of available rollbacks after executing bootloop mitigation once when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL)); watchdog.noteBoot(); - verify(rescuePartyObserver).executeBootLoopMitigation(3); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(4); - verify(rollbackObserver, never()).executeBootLoopMitigation(2); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver).onExecuteBootLoopMitigation(3); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(4); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2); watchdog.noteBoot(); - verify(rescuePartyObserver).executeBootLoopMitigation(4); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(5); - verify(rollbackObserver, never()).executeBootLoopMitigation(2); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver).onExecuteBootLoopMitigation(4); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(5); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2); watchdog.noteBoot(); - verify(rescuePartyObserver).executeBootLoopMitigation(5); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(6); - verify(rollbackObserver, never()).executeBootLoopMitigation(2); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver).onExecuteBootLoopMitigation(5); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2); watchdog.noteBoot(); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(6); - verify(rollbackObserver).executeBootLoopMitigation(2); - verify(rollbackObserver, never()).executeBootLoopMitigation(3); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6); + verify(rollbackObserver).onExecuteBootLoopMitigation(2); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3); // Update the list of available rollbacks after executing bootloop mitigation when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL)); watchdog.noteBoot(); - verify(rescuePartyObserver).executeBootLoopMitigation(6); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(7); - verify(rollbackObserver, never()).executeBootLoopMitigation(3); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver).onExecuteBootLoopMitigation(6); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(7); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3); moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1); Mockito.reset(rescuePartyObserver); @@ -361,8 +382,9 @@ public class CrashRecoveryTest { for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { watchdog.noteBoot(); } - verify(rescuePartyObserver).executeBootLoopMitigation(1); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(2); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver).onExecuteBootLoopMitigation(1); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2); } @Test @@ -373,37 +395,41 @@ public class CrashRecoveryTest { RollbackPackageHealthObserver rollbackObserver = setUpRollbackPackageHealthObserver(watchdog); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(1); - verify(rollbackObserver, never()).executeBootLoopMitigation(1); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(1); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1); for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { watchdog.noteBoot(); } - verify(rescuePartyObserver).executeBootLoopMitigation(1); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(2); - verify(rollbackObserver, never()).executeBootLoopMitigation(1); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver).onExecuteBootLoopMitigation(1); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1); watchdog.noteBoot(); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(2); - verify(rollbackObserver).executeBootLoopMitigation(1); - verify(rollbackObserver, never()).executeBootLoopMitigation(2); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2); + verify(rollbackObserver).onExecuteBootLoopMitigation(1); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2); // Update the list of available rollbacks after executing bootloop mitigation once when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL)); watchdog.noteBoot(); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(2); - verify(rollbackObserver).executeBootLoopMitigation(2); - verify(rollbackObserver, never()).executeBootLoopMitigation(3); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2); + verify(rollbackObserver).onExecuteBootLoopMitigation(2); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3); // Update the list of available rollbacks after executing bootloop mitigation when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL)); watchdog.noteBoot(); - verify(rescuePartyObserver).executeBootLoopMitigation(2); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(3); - verify(rollbackObserver, never()).executeBootLoopMitigation(3); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver).onExecuteBootLoopMitigation(2); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3); + verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3); moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1); Mockito.reset(rescuePartyObserver); @@ -411,8 +437,9 @@ public class CrashRecoveryTest { for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { watchdog.noteBoot(); } - verify(rescuePartyObserver).executeBootLoopMitigation(1); - verify(rescuePartyObserver, never()).executeBootLoopMitigation(2); + mTestLooper.dispatchAll(); + verify(rescuePartyObserver).onExecuteBootLoopMitigation(1); + verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2); } @Test @@ -435,46 +462,46 @@ public class CrashRecoveryTest { Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: SCOPED_DEVICE_CONFIG_RESET - verify(rescuePartyObserver).execute(versionedPackageA, + verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); - verify(rescuePartyObserver, never()).execute(versionedPackageA, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); - verify(rollbackObserver, never()).execute(versionedPackageA, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: ALL_DEVICE_CONFIG_RESET - verify(rescuePartyObserver).execute(versionedPackageA, + verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); - verify(rescuePartyObserver, never()).execute(versionedPackageA, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 3); - verify(rollbackObserver, never()).execute(versionedPackageA, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: WARM_REBOOT - verify(rescuePartyObserver).execute(versionedPackageA, + verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 3); - verify(rescuePartyObserver, never()).execute(versionedPackageA, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 4); - verify(rollbackObserver, never()).execute(versionedPackageA, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: Low impact rollback - verify(rollbackObserver).execute(versionedPackageA, + verify(rollbackObserver).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); - verify(rescuePartyObserver, never()).execute(versionedPackageA, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 4); // update available rollbacks to mock rollbacks being applied after the call to - // rollbackObserver.execute + // rollbackObserver.onExecuteHealthCheckMitigation when(mRollbackManager.getAvailableRollbacks()).thenReturn( List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL)); @@ -482,9 +509,9 @@ public class CrashRecoveryTest { Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH); // DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD reached. No more mitigations applied - verify(rescuePartyObserver, never()).execute(versionedPackageA, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 4); - verify(rollbackObserver, never()).execute(versionedPackageA, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); } @@ -510,24 +537,24 @@ public class CrashRecoveryTest { Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: WARM_REBOOT - verify(rescuePartyObserver).execute(versionedPackageA, + verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); - verify(rescuePartyObserver, never()).execute(versionedPackageA, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); - verify(rollbackObserver, never()).execute(versionedPackageA, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: Low impact rollback - verify(rollbackObserver).execute(versionedPackageA, + verify(rollbackObserver).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); - verify(rescuePartyObserver, never()).execute(versionedPackageA, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); // update available rollbacks to mock rollbacks being applied after the call to - // rollbackObserver.execute + // rollbackObserver.onExecuteHealthCheckMitigation when(mRollbackManager.getAvailableRollbacks()).thenReturn( List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL)); @@ -535,9 +562,9 @@ public class CrashRecoveryTest { Arrays.asList(versionedPackageA), PackageWatchdog.FAILURE_REASON_APP_CRASH); // DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD reached. No more mitigations applied - verify(rescuePartyObserver, never()).execute(versionedPackageA, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); - verify(rollbackObserver, never()).execute(versionedPackageA, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageA, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); } @@ -567,48 +594,48 @@ public class CrashRecoveryTest { Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: SCOPED_DEVICE_CONFIG_RESET - verify(rescuePartyObserver).execute(versionedPackageUi, + verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); - verify(rescuePartyObserver, never()).execute(versionedPackageUi, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); - verify(rollbackObserver, never()).execute(versionedPackageUi, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: ALL_DEVICE_CONFIG_RESET - verify(rescuePartyObserver).execute(versionedPackageUi, + verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); - verify(rescuePartyObserver, never()).execute(versionedPackageUi, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 3); - verify(rollbackObserver, never()).execute(versionedPackageUi, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: WARM_REBOOT - verify(rescuePartyObserver).execute(versionedPackageUi, + verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 3); - verify(rescuePartyObserver, never()).execute(versionedPackageUi, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 4); - verify(rollbackObserver, never()).execute(versionedPackageUi, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: Low impact rollback - verify(rollbackObserver).execute(versionedPackageUi, + verify(rollbackObserver).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); - verify(rescuePartyObserver, never()).execute(versionedPackageUi, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 4); - verify(rollbackObserver, never()).execute(versionedPackageUi, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); // update available rollbacks to mock rollbacks being applied after the call to - // rollbackObserver.execute + // rollbackObserver.onExecuteHealthCheckMitigation when(mRollbackManager.getAvailableRollbacks()).thenReturn( List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL)); @@ -616,44 +643,44 @@ public class CrashRecoveryTest { Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: RESET_SETTINGS_UNTRUSTED_DEFAULTS - verify(rescuePartyObserver).execute(versionedPackageUi, + verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 4); - verify(rescuePartyObserver, never()).execute(versionedPackageUi, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 5); - verify(rollbackObserver, never()).execute(versionedPackageUi, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: RESET_SETTINGS_UNTRUSTED_CHANGES - verify(rescuePartyObserver).execute(versionedPackageUi, + verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 5); - verify(rescuePartyObserver, never()).execute(versionedPackageUi, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 6); - verify(rollbackObserver, never()).execute(versionedPackageUi, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: RESET_SETTINGS_TRUSTED_DEFAULTS - verify(rescuePartyObserver).execute(versionedPackageUi, + verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 6); - verify(rescuePartyObserver, never()).execute(versionedPackageUi, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 7); - verify(rollbackObserver, never()).execute(versionedPackageUi, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: Factory reset. High impact rollbacks are performed only for boot loops. - verify(rescuePartyObserver).execute(versionedPackageUi, + verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 7); - verify(rescuePartyObserver, never()).execute(versionedPackageUi, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 8); - verify(rollbackObserver, never()).execute(versionedPackageUi, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); } @@ -685,26 +712,26 @@ public class CrashRecoveryTest { Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: WARM_REBOOT - verify(rescuePartyObserver).execute(versionedPackageUi, + verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); - verify(rescuePartyObserver, never()).execute(versionedPackageUi, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); - verify(rollbackObserver, never()).execute(versionedPackageUi, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: Low impact rollback - verify(rollbackObserver).execute(versionedPackageUi, + verify(rollbackObserver).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1); - verify(rescuePartyObserver, never()).execute(versionedPackageUi, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); - verify(rollbackObserver, never()).execute(versionedPackageUi, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); // update available rollbacks to mock rollbacks being applied after the call to - // rollbackObserver.execute + // rollbackObserver.onExecuteHealthCheckMitigation when(mRollbackManager.getAvailableRollbacks()).thenReturn( List.of(ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL)); @@ -712,30 +739,40 @@ public class CrashRecoveryTest { Arrays.asList(versionedPackageUi), PackageWatchdog.FAILURE_REASON_APP_CRASH); // Mitigation: Factory reset. High impact rollbacks are performed only for boot loops. - verify(rescuePartyObserver).execute(versionedPackageUi, + verify(rescuePartyObserver).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); - verify(rescuePartyObserver, never()).execute(versionedPackageUi, + verify(rescuePartyObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 3); - verify(rollbackObserver, never()).execute(versionedPackageUi, + verify(rollbackObserver, never()).onExecuteHealthCheckMitigation(versionedPackageUi, PackageWatchdog.FAILURE_REASON_APP_CRASH, 2); } RollbackPackageHealthObserver setUpRollbackPackageHealthObserver(PackageWatchdog watchdog) { RollbackPackageHealthObserver rollbackObserver = - spy(new RollbackPackageHealthObserver(mSpyContext, mApexManager)); + spy(new RollbackPackageHealthObserver(mSpyContext)); when(mSpyContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_LOW, ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL)); when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager); - - watchdog.registerHealthObserver(rollbackObserver); + try { + when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> { + final PackageInfo res = new PackageInfo(); + res.packageName = inv.getArgument(0); + res.setApexPackageName(res.packageName); + res.setLongVersionCode(VERSION_CODE); + return res; + }); + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); + } + watchdog.registerHealthObserver(rollbackObserver, mTestExecutor); return rollbackObserver; } RescuePartyObserver setUpRescuePartyObserver(PackageWatchdog watchdog) { setCrashRecoveryPropRescueBootCount(0); RescuePartyObserver rescuePartyObserver = spy(RescuePartyObserver.getInstance(mSpyContext)); assertFalse(RescueParty.isRebootPropertySet()); - watchdog.registerHealthObserver(rescuePartyObserver); + watchdog.registerHealthObserver(rescuePartyObserver, mTestExecutor); return rescuePartyObserver; } @@ -775,7 +812,7 @@ public class CrashRecoveryTest { Handler handler = new Handler(mTestLooper.getLooper()); PackageWatchdog watchdog = new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller, - mConnectivityModuleConnector, mTestClock); + mTestClock); mockCrashRecoveryProperties(watchdog); // Verify controller is not automatically started @@ -787,8 +824,10 @@ public class CrashRecoveryTest { // Verify controller by default is started when packages are ready assertThat(controller.mIsEnabled).isTrue(); - verify(mConnectivityModuleConnector).registerHealthListener( - mConnectivityModuleCallbackCaptor.capture()); + if (!Flags.refactorCrashrecovery()) { + verify(mConnectivityModuleConnector).registerHealthListener( + mConnectivityModuleCallbackCaptor.capture()); + } } mAllocatedWatchdogs.add(watchdog); return watchdog; @@ -1001,7 +1040,7 @@ public class CrashRecoveryTest { triggerFailureCount = 1; } for (int i = 0; i < triggerFailureCount; i++) { - watchdog.onPackageFailure(packages, failureReason); + watchdog.notifyPackageFailure(packages, failureReason); } mTestLooper.dispatchAll(); if (Flags.recoverabilityDetection()) { diff --git a/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java b/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java index 2fbfeba47b13..055e159ff0b6 100644 --- a/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java +++ b/tests/PackageWatchdog/src/com/android/server/ExplicitHealthCheckServiceTest.java @@ -16,6 +16,8 @@ package com.android.server; +import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; @@ -50,7 +52,7 @@ public class ExplicitHealthCheckServiceTest { IBinder binder = mExplicitHealthCheckService.onBind(new Intent()); CountDownLatch countDownLatch = new CountDownLatch(1); RemoteCallback callback = new RemoteCallback(result -> { - assertThat(result.get(ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE)) + assertThat(result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE)) .isEqualTo(PACKAGE_NAME); countDownLatch.countDown(); }); diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index 5b178250a4c9..c64dc7296f0a 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -46,10 +46,14 @@ import android.net.ConnectivityModuleConnector.ConnectivityModuleHealthListener; import android.os.Handler; import android.os.SystemProperties; import android.os.test.TestLooper; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.util.AtomicFile; import android.util.LongArrayQueue; +import android.util.Slog; import android.util.Xml; import androidx.test.InstrumentationRegistry; @@ -85,6 +89,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Set; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Supplier; @@ -111,8 +116,12 @@ public class PackageWatchdogTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule + public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private final TestClock mTestClock = new TestClock(); private TestLooper mTestLooper; + private Executor mTestExecutor; private Context mSpyContext; // Keep track of all created watchdogs to apply device config changes private List<PackageWatchdog> mAllocatedWatchdogs; @@ -146,8 +155,10 @@ public class PackageWatchdogTest { new File(InstrumentationRegistry.getContext().getFilesDir(), "package-watchdog.xml").delete(); adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG, - Manifest.permission.WRITE_DEVICE_CONFIG); + Manifest.permission.WRITE_DEVICE_CONFIG, + Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG); mTestLooper = new TestLooper(); + mTestExecutor = mTestLooper.getNewExecutor(); mSpyContext = spy(InstrumentationRegistry.getContext()); when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager); when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> { @@ -219,7 +230,8 @@ public class PackageWatchdogTest { PackageWatchdog watchdog = createWatchdog(); TestObserver observer = new TestObserver(OBSERVER_NAME_1); - watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer, mTestExecutor); + watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); @@ -235,8 +247,10 @@ public class PackageWatchdogTest { TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); - watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION); + watchdog.registerHealthObserver(observer1, mTestExecutor); + watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer2, mTestExecutor); + watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE), new VersionedPackage(APP_B, VERSION_CODE)), @@ -253,7 +267,8 @@ public class PackageWatchdogTest { PackageWatchdog watchdog = createWatchdog(); TestObserver observer = new TestObserver(OBSERVER_NAME_1); - watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer, mTestExecutor); + watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION); watchdog.unregisterHealthObserver(observer); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), @@ -269,8 +284,10 @@ public class PackageWatchdogTest { TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); - watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer1, mTestExecutor); + watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer2, mTestExecutor); + watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION); watchdog.unregisterHealthObserver(observer2); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), @@ -287,7 +304,8 @@ public class PackageWatchdogTest { PackageWatchdog watchdog = createWatchdog(); TestObserver observer = new TestObserver(OBSERVER_NAME_1); - watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer, mTestExecutor); + watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION); moveTimeForwardAndDispatch(SHORT_DURATION); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), @@ -303,8 +321,10 @@ public class PackageWatchdogTest { TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); - watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), LONG_DURATION); + watchdog.registerHealthObserver(observer1, mTestExecutor); + watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer2, mTestExecutor); + watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), LONG_DURATION); moveTimeForwardAndDispatch(SHORT_DURATION); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), @@ -323,13 +343,14 @@ public class PackageWatchdogTest { TestObserver observer = new TestObserver(OBSERVER_NAME_1); // Start observing APP_A - watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer, mTestExecutor); + watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION); // Then advance time half-way moveTimeForwardAndDispatch(SHORT_DURATION / 2); // Start observing APP_A again - watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION); // Then advance time such that it should have expired were it not for the second observation moveTimeForwardAndDispatch((SHORT_DURATION / 2) + 1); @@ -351,15 +372,17 @@ public class PackageWatchdogTest { TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); - watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION); + watchdog1.registerHealthObserver(observer1, mTestExecutor); + watchdog1.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog1.registerHealthObserver(observer2, mTestExecutor); + watchdog1.startExplicitHealthCheck(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION); // Then advance time and run IO Handler so file is saved mTestLooper.dispatchAll(); // Then start a new watchdog PackageWatchdog watchdog2 = createWatchdog(); // Then resume observer1 and observer2 - watchdog2.registerHealthObserver(observer1); - watchdog2.registerHealthObserver(observer2); + watchdog2.registerHealthObserver(observer1, mTestExecutor); + watchdog2.registerHealthObserver(observer2, mTestExecutor); raiseFatalFailureAndDispatch(watchdog2, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE), new VersionedPackage(APP_B, VERSION_CODE)), @@ -367,6 +390,7 @@ public class PackageWatchdogTest { // We should receive failed packages as expected to ensure observers are persisted and // resumed correctly + mTestLooper.dispatchAll(); assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A); assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B); } @@ -380,12 +404,14 @@ public class PackageWatchdogTest { TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer2, mTestExecutor); + watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer1, mTestExecutor); + watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION); // Then fail APP_A below the threshold for (int i = 0; i < watchdog.getTriggerFailureCount() - 1; i++) { - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); } @@ -407,9 +433,10 @@ public class PackageWatchdogTest { TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - - watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION); + watchdog.registerHealthObserver(observer2, mTestExecutor); + watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer1, mTestExecutor); + watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_B), SHORT_DURATION); // Then fail APP_C (not observed) above the threshold raiseFatalFailureAndDispatch(watchdog, @@ -441,7 +468,8 @@ public class PackageWatchdogTest { } }; - watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer, mTestExecutor); + watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION); // Then fail APP_A (different version) above the threshold raiseFatalFailureAndDispatch(watchdog, @@ -470,13 +498,17 @@ public class PackageWatchdogTest { PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); // Start observing for all impact observers - watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D), + watchdog.registerHealthObserver(observerNone, mTestExecutor); + watchdog.startExplicitHealthCheck(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D), SHORT_DURATION); - watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C), + watchdog.registerHealthObserver(observerHigh, mTestExecutor); + watchdog.startExplicitHealthCheck(observerHigh, Arrays.asList(APP_A, APP_B, APP_C), SHORT_DURATION); - watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B), + watchdog.registerHealthObserver(observerMid, mTestExecutor); + watchdog.startExplicitHealthCheck(observerMid, Arrays.asList(APP_A, APP_B), SHORT_DURATION); - watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A), + watchdog.registerHealthObserver(observerLow, mTestExecutor); + watchdog.startExplicitHealthCheck(observerLow, Arrays.asList(APP_A), SHORT_DURATION); // Then fail all apps above the threshold @@ -516,13 +548,17 @@ public class PackageWatchdogTest { PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); // Start observing for all impact observers - watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D), + watchdog.registerHealthObserver(observerNone, mTestExecutor); + watchdog.startExplicitHealthCheck(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D), SHORT_DURATION); - watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C), + watchdog.registerHealthObserver(observerHigh, mTestExecutor); + watchdog.startExplicitHealthCheck(observerHigh, Arrays.asList(APP_A, APP_B, APP_C), SHORT_DURATION); - watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B), + watchdog.registerHealthObserver(observerMid, mTestExecutor); + watchdog.startExplicitHealthCheck(observerMid, Arrays.asList(APP_A, APP_B), SHORT_DURATION); - watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A), + watchdog.registerHealthObserver(observerLow, mTestExecutor); + watchdog.startExplicitHealthCheck(observerLow, Arrays.asList(APP_A), SHORT_DURATION); // Then fail all apps above the threshold @@ -570,8 +606,10 @@ public class PackageWatchdogTest { PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); // Start observing for observerFirst and observerSecond with failure handling - watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION); - watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION); + watchdog.registerHealthObserver(observerFirst, mTestExecutor); + watchdog.startExplicitHealthCheck(observerFirst, Arrays.asList(APP_A), LONG_DURATION); + watchdog.registerHealthObserver(observerSecond, mTestExecutor); + watchdog.startExplicitHealthCheck(observerSecond, Arrays.asList(APP_A), LONG_DURATION); // Then fail APP_A above the threshold raiseFatalFailureAndDispatch(watchdog, @@ -634,8 +672,10 @@ public class PackageWatchdogTest { PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); // Start observing for observerFirst and observerSecond with failure handling - watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION); - watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION); + watchdog.registerHealthObserver(observerFirst, mTestExecutor); + watchdog.startExplicitHealthCheck(observerFirst, Arrays.asList(APP_A), LONG_DURATION); + watchdog.registerHealthObserver(observerSecond, mTestExecutor); + watchdog.startExplicitHealthCheck(observerSecond, Arrays.asList(APP_A), LONG_DURATION); // Then fail APP_A above the threshold raiseFatalFailureAndDispatch(watchdog, @@ -702,8 +742,10 @@ public class PackageWatchdogTest { PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); // Start observing for observer1 and observer2 with failure handling - watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer2, mTestExecutor); + watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer1, mTestExecutor); + watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION); // Then fail APP_A above the threshold raiseFatalFailureAndDispatch(watchdog, @@ -724,8 +766,10 @@ public class PackageWatchdogTest { PackageHealthObserverImpact.USER_IMPACT_LEVEL_50); // Start observing for observer1 and observer2 with failure handling - watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer2, mTestExecutor); + watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer1, mTestExecutor); + watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION); // Then fail APP_A above the threshold raiseFatalFailureAndDispatch(watchdog, @@ -755,8 +799,10 @@ public class PackageWatchdogTest { // Start observing with explicit health checks for APP_A and APP_B respectively // with observer1 and observer2 controller.setSupportedPackages(Arrays.asList(APP_A, APP_B)); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); - watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION); + watchdog.registerHealthObserver(observer1, mTestExecutor); + watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer2, mTestExecutor); + watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_B), SHORT_DURATION); // Run handler so requests are dispatched to the controller mTestLooper.dispatchAll(); @@ -772,7 +818,8 @@ public class PackageWatchdogTest { // Observer3 didn't exist when we got the explicit health check above, so // it starts out with a non-passing explicit health check and has to wait for a pass // otherwise it would be notified of APP_A failure on expiry - watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer3, mTestExecutor); + watchdog.startExplicitHealthCheck(observer3, Arrays.asList(APP_A), SHORT_DURATION); // Then expire observers moveTimeForwardAndDispatch(SHORT_DURATION); @@ -802,8 +849,9 @@ public class PackageWatchdogTest { // Start observing with explicit health checks for APP_A and APP_B controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C)); - watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); - watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION); + watchdog.registerHealthObserver(observer, mTestExecutor); + watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_B), LONG_DURATION); // Run handler so requests are dispatched to the controller mTestLooper.dispatchAll(); @@ -839,7 +887,7 @@ public class PackageWatchdogTest { // Then set new supported packages controller.setSupportedPackages(Arrays.asList(APP_C)); // Start observing APP_A and APP_C; only APP_C has support for explicit health checks - watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION); + watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION); // Run handler so requests/cancellations are dispatched to the controller mTestLooper.dispatchAll(); @@ -870,7 +918,8 @@ public class PackageWatchdogTest { // package observation duration == LONG_DURATION // health check duration == SHORT_DURATION (set by default in the TestController) controller.setSupportedPackages(Arrays.asList(APP_A)); - watchdog.startObservingHealth(observer, Arrays.asList(APP_A), LONG_DURATION); + watchdog.registerHealthObserver(observer, mTestExecutor); + watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), LONG_DURATION); // Then APP_A has exceeded health check duration moveTimeForwardAndDispatch(SHORT_DURATION); @@ -901,7 +950,8 @@ public class PackageWatchdogTest { // package observation duration == SHORT_DURATION / 2 // health check duration == SHORT_DURATION (set by default in the TestController) controller.setSupportedPackages(Arrays.asList(APP_A)); - watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION / 2); + watchdog.registerHealthObserver(observer, mTestExecutor); + watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION / 2); // Forward time to expire the observation duration moveTimeForwardAndDispatch(SHORT_DURATION / 2); @@ -966,6 +1016,7 @@ public class PackageWatchdogTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_CRASHRECOVERY) public void testNetworkStackFailure() { mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); final PackageWatchdog wd = createWatchdog(); @@ -973,7 +1024,7 @@ public class PackageWatchdogTest { // Start observing with failure handling TestObserver observer = new TestObserver(OBSERVER_NAME_1, PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); - wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION); + wd.startExplicitHealthCheck(observer, Collections.singletonList(APP_A), SHORT_DURATION); // Notify of NetworkStack failure mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A); @@ -986,13 +1037,14 @@ public class PackageWatchdogTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_CRASHRECOVERY) public void testNetworkStackFailureRecoverabilityDetection() { final PackageWatchdog wd = createWatchdog(); // Start observing with failure handling TestObserver observer = new TestObserver(OBSERVER_NAME_1, PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); - wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION); + wd.startExplicitHealthCheck(observer, Collections.singletonList(APP_A), SHORT_DURATION); // Notify of NetworkStack failure mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A); @@ -1013,17 +1065,18 @@ public class PackageWatchdogTest { PackageWatchdog watchdog = createWatchdog(); TestObserver observer = new TestObserver(OBSERVER_NAME_1); - watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer, mTestExecutor); + watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION); // Fail APP_A below the threshold which should not trigger package failures for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) { - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); } mTestLooper.dispatchAll(); assertThat(observer.mHealthCheckFailedPackages).isEmpty(); // One more to trigger the package failure - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); mTestLooper.dispatchAll(); assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A); @@ -1041,11 +1094,12 @@ public class PackageWatchdogTest { PackageWatchdog watchdog = createWatchdog(); TestObserver observer = new TestObserver(OBSERVER_NAME_1); - watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE); - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + watchdog.registerHealthObserver(observer, mTestExecutor); + watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE); + watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1); - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); mTestLooper.dispatchAll(); @@ -1053,10 +1107,10 @@ public class PackageWatchdogTest { // DEFAULT_TRIGGER_FAILURE_DURATION_MS. assertThat(observer.mHealthCheckFailedPackages).isEmpty(); - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)), + watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS - 1); - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)), + watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); mTestLooper.dispatchAll(); @@ -1066,15 +1120,16 @@ public class PackageWatchdogTest { } /** - * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered - * an invalid durationMs. + * Test default monitoring duration is used when PackageWatchdog#startExplicitHealthCheck is + * offered an invalid durationMs. */ @Test public void testInvalidMonitoringDuration_beforeExpiry() { PackageWatchdog watchdog = createWatchdog(); TestObserver observer = new TestObserver(OBSERVER_NAME_1); - watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1); + watchdog.registerHealthObserver(observer, mTestExecutor); + watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), -1); // Note: Don't move too close to the expiration time otherwise the handler will be thrashed // by PackageWatchdog#scheduleNextSyncStateLocked which keeps posting runnables with very // small timeouts. @@ -1088,15 +1143,16 @@ public class PackageWatchdogTest { } /** - * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered - * an invalid durationMs. + * Test default monitoring duration is used when PackageWatchdog#startExplicitHealthCheck is + * offered an invalid durationMs. */ @Test public void testInvalidMonitoringDuration_afterExpiry() { PackageWatchdog watchdog = createWatchdog(); TestObserver observer = new TestObserver(OBSERVER_NAME_1); - watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1); + watchdog.registerHealthObserver(observer, mTestExecutor); + watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), -1); moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS + 1); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), @@ -1118,19 +1174,20 @@ public class PackageWatchdogTest { PackageWatchdog watchdog = createWatchdog(); TestObserver observer = new TestObserver(OBSERVER_NAME_1); - watchdog.startObservingHealth(observer, Arrays.asList(APP_A), Long.MAX_VALUE); + watchdog.registerHealthObserver(observer, mTestExecutor); + watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), Long.MAX_VALUE); // Raise 2 failures at t=0 and t=900 respectively - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); moveTimeForwardAndDispatch(900); - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); // Raise 2 failures at t=1100 moveTimeForwardAndDispatch(200); - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); - watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); mTestLooper.dispatchAll(); @@ -1145,8 +1202,10 @@ public class PackageWatchdogTest { TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); - watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION); + watchdog.registerHealthObserver(observer1, mTestExecutor); + watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer2, mTestExecutor); + watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_B), SHORT_DURATION); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH); @@ -1165,7 +1224,8 @@ public class PackageWatchdogTest { PackageWatchdog watchdog = createWatchdog(); TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); - watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer1, mTestExecutor); + watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); @@ -1185,7 +1245,8 @@ public class PackageWatchdogTest { persistentObserver.setPersistent(true); persistentObserver.setMayObservePackages(true); - watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION); + watchdog.registerHealthObserver(persistentObserver, mTestExecutor); + watchdog.startExplicitHealthCheck(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); @@ -1203,7 +1264,8 @@ public class PackageWatchdogTest { persistentObserver.setPersistent(true); persistentObserver.setMayObservePackages(false); - watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION); + watchdog.registerHealthObserver(persistentObserver, mTestExecutor); + watchdog.startExplicitHealthCheck(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION); raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); @@ -1214,13 +1276,15 @@ public class PackageWatchdogTest { /** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */ @Test public void testBootLoopDetection_meetsThreshold() { + Slog.w("hrm1243", "I should definitely be here try 1 "); mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); PackageWatchdog watchdog = createWatchdog(); TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1); - watchdog.registerHealthObserver(bootObserver); + watchdog.registerHealthObserver(bootObserver, mTestExecutor); for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { watchdog.noteBoot(); } + mTestLooper.dispatchAll(); assertThat(bootObserver.mitigatedBootLoop()).isTrue(); } @@ -1228,10 +1292,11 @@ public class PackageWatchdogTest { public void testBootLoopDetection_meetsThresholdRecoverability() { PackageWatchdog watchdog = createWatchdog(); TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1); - watchdog.registerHealthObserver(bootObserver); + watchdog.registerHealthObserver(bootObserver, mTestExecutor); for (int i = 0; i < 15; i++) { watchdog.noteBoot(); } + mTestLooper.dispatchAll(); assertThat(bootObserver.mitigatedBootLoop()).isTrue(); } @@ -1243,10 +1308,11 @@ public class PackageWatchdogTest { public void testBootLoopDetection_doesNotMeetThreshold() { PackageWatchdog watchdog = createWatchdog(); TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1); - watchdog.registerHealthObserver(bootObserver); + watchdog.registerHealthObserver(bootObserver, mTestExecutor); for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) { watchdog.noteBoot(); } + mTestLooper.dispatchAll(); assertThat(bootObserver.mitigatedBootLoop()).isFalse(); } @@ -1259,10 +1325,11 @@ public class PackageWatchdogTest { PackageWatchdog watchdog = createWatchdog(); TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1, PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); - watchdog.registerHealthObserver(bootObserver); + watchdog.registerHealthObserver(bootObserver, mTestExecutor); for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) { watchdog.noteBoot(); } + mTestLooper.dispatchAll(); assertThat(bootObserver.mitigatedBootLoop()).isFalse(); } @@ -1277,11 +1344,12 @@ public class PackageWatchdogTest { bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2); bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); - watchdog.registerHealthObserver(bootObserver1); - watchdog.registerHealthObserver(bootObserver2); + watchdog.registerHealthObserver(bootObserver1, mTestExecutor); + watchdog.registerHealthObserver(bootObserver2, mTestExecutor); for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { watchdog.noteBoot(); } + mTestLooper.dispatchAll(); assertThat(bootObserver1.mitigatedBootLoop()).isTrue(); assertThat(bootObserver2.mitigatedBootLoop()).isFalse(); } @@ -1293,11 +1361,12 @@ public class PackageWatchdogTest { bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2); bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); - watchdog.registerHealthObserver(bootObserver1); - watchdog.registerHealthObserver(bootObserver2); + watchdog.registerHealthObserver(bootObserver1, mTestExecutor); + watchdog.registerHealthObserver(bootObserver2, mTestExecutor); for (int i = 0; i < 15; i++) { watchdog.noteBoot(); } + mTestLooper.dispatchAll(); assertThat(bootObserver1.mitigatedBootLoop()).isTrue(); assertThat(bootObserver2.mitigatedBootLoop()).isFalse(); } @@ -1310,7 +1379,7 @@ public class PackageWatchdogTest { mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); PackageWatchdog watchdog = createWatchdog(); TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1); - watchdog.registerHealthObserver(bootObserver); + watchdog.registerHealthObserver(bootObserver, mTestExecutor); for (int i = 0; i < 4; i++) { for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; j++) { watchdog.noteBoot(); @@ -1324,7 +1393,7 @@ public class PackageWatchdogTest { watchdog.noteBoot(); } } - + mTestLooper.dispatchAll(); assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4)); } @@ -1333,7 +1402,7 @@ public class PackageWatchdogTest { PackageWatchdog watchdog = createWatchdog(); TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1, PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); - watchdog.registerHealthObserver(bootObserver); + watchdog.registerHealthObserver(bootObserver, mTestExecutor); for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; j++) { watchdog.noteBoot(); } @@ -1349,7 +1418,7 @@ public class PackageWatchdogTest { for (int i = 0; i < 4; i++) { watchdog.noteBoot(); } - + mTestLooper.dispatchAll(); assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4)); } @@ -1361,7 +1430,8 @@ public class PackageWatchdogTest { public void testNullFailedPackagesList() { PackageWatchdog watchdog = createWatchdog(); TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); - watchdog.startObservingHealth(observer1, List.of(APP_A), LONG_DURATION); + watchdog.registerHealthObserver(observer1, mTestExecutor); + watchdog.startExplicitHealthCheck(observer1, List.of(APP_A), LONG_DURATION); raiseFatalFailureAndDispatch(watchdog, null, PackageWatchdog.FAILURE_REASON_APP_CRASH); assertThat(observer1.mMitigatedPackages).isEmpty(); @@ -1379,18 +1449,18 @@ public class PackageWatchdogTest { PackageWatchdog watchdog = createWatchdog(testController, true); TestObserver testObserver1 = new TestObserver(OBSERVER_NAME_1); - watchdog.registerHealthObserver(testObserver1); - watchdog.startObservingHealth(testObserver1, List.of(APP_A), LONG_DURATION); + watchdog.registerHealthObserver(testObserver1, mTestExecutor); + watchdog.startExplicitHealthCheck(testObserver1, List.of(APP_A), LONG_DURATION); mTestLooper.dispatchAll(); TestObserver testObserver2 = new TestObserver(OBSERVER_NAME_2); - watchdog.registerHealthObserver(testObserver2); - watchdog.startObservingHealth(testObserver2, List.of(APP_B), LONG_DURATION); + watchdog.registerHealthObserver(testObserver2, mTestExecutor); + watchdog.startExplicitHealthCheck(testObserver2, List.of(APP_B), LONG_DURATION); mTestLooper.dispatchAll(); TestObserver testObserver3 = new TestObserver(OBSERVER_NAME_3); - watchdog.registerHealthObserver(testObserver3); - watchdog.startObservingHealth(testObserver3, List.of(APP_C), LONG_DURATION); + watchdog.registerHealthObserver(testObserver3, mTestExecutor); + watchdog.startExplicitHealthCheck(testObserver3, List.of(APP_C), LONG_DURATION); mTestLooper.dispatchAll(); watchdog.unregisterHealthObserver(testObserver1); @@ -1422,15 +1492,16 @@ public class PackageWatchdogTest { public void testFailureHistoryIsPreserved() { PackageWatchdog watchdog = createWatchdog(); TestObserver observer = new TestObserver(OBSERVER_NAME_1); - watchdog.startObservingHealth(observer, List.of(APP_A), SHORT_DURATION); + watchdog.registerHealthObserver(observer, mTestExecutor); + watchdog.startExplicitHealthCheck(observer, List.of(APP_A), SHORT_DURATION); for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) { - watchdog.onPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)), + watchdog.notifyPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); } mTestLooper.dispatchAll(); assertThat(observer.mMitigatedPackages).isEmpty(); - watchdog.startObservingHealth(observer, List.of(APP_A), LONG_DURATION); - watchdog.onPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)), + watchdog.startExplicitHealthCheck(observer, List.of(APP_A), LONG_DURATION); + watchdog.notifyPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN); mTestLooper.dispatchAll(); assertThat(observer.mMitigatedPackages).isEqualTo(List.of(APP_A)); @@ -1444,7 +1515,8 @@ public class PackageWatchdogTest { public void testMitigationSlidingWindow() { PackageWatchdog watchdog = createWatchdog(); TestObserver observer = new TestObserver(OBSERVER_NAME_1); - watchdog.startObservingHealth(observer, List.of(APP_A), + watchdog.registerHealthObserver(observer, mTestExecutor); + watchdog.startExplicitHealthCheck(observer, List.of(APP_A), PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS * 2); @@ -1728,7 +1800,7 @@ public class PackageWatchdogTest { triggerFailureCount = 1; } for (int i = 0; i < triggerFailureCount; i++) { - watchdog.onPackageFailure(packages, failureReason); + watchdog.notifyPackageFailure(packages, failureReason); } mTestLooper.dispatchAll(); if (Flags.recoverabilityDetection()) { @@ -1746,7 +1818,7 @@ public class PackageWatchdogTest { Handler handler = new Handler(mTestLooper.getLooper()); PackageWatchdog watchdog = new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller, - mConnectivityModuleConnector, mTestClock); + mTestClock); mockCrashRecoveryProperties(watchdog); // Verify controller is not automatically started @@ -1758,8 +1830,10 @@ public class PackageWatchdogTest { // Verify controller by default is started when packages are ready assertThat(controller.mIsEnabled).isTrue(); - verify(mConnectivityModuleConnector).registerHealthListener( - mConnectivityModuleCallbackCaptor.capture()); + if (!Flags.refactorCrashrecovery()) { + verify(mConnectivityModuleConnector).registerHealthListener( + mConnectivityModuleCallbackCaptor.capture()); + } } mAllocatedWatchdogs.add(watchdog); return watchdog; @@ -1859,8 +1933,8 @@ public class PackageWatchdogTest { return mImpact; } - public boolean execute(VersionedPackage versionedPackage, int failureReason, - int mitigationCount) { + public boolean onExecuteHealthCheckMitigation(VersionedPackage versionedPackage, + int failureReason, int mitigationCount) { mMitigatedPackages.add(versionedPackage.getPackageName()); mMitigationCounts.add(mitigationCount); mLastFailureReason = failureReason; @@ -1883,7 +1957,8 @@ public class PackageWatchdogTest { return mImpact; } - public boolean executeBootLoopMitigation(int level) { + public boolean onExecuteBootLoopMitigation(int level) { + Slog.w("hrm1243", "I'm here " + level); mMitigatedBootLoop = true; mBootMitigationCounts.add(level); return true; diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java index 314e95229d29..a6aa877c9097 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java @@ -82,7 +82,8 @@ public class NetworkStagedRollbackTest { Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS, Manifest.permission.FORCE_STOP_PACKAGES, - Manifest.permission.WRITE_DEVICE_CONFIG); + Manifest.permission.WRITE_DEVICE_CONFIG, + Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG); } /** 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 4cddcfeb91dc..32deb2e8fdfc 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -70,7 +70,8 @@ public class StagedRollbackTest { Manifest.permission.DELETE_PACKAGES, Manifest.permission.TEST_MANAGE_ROLLBACKS, Manifest.permission.FORCE_STOP_PACKAGES, - Manifest.permission.WRITE_DEVICE_CONFIG); + Manifest.permission.WRITE_DEVICE_CONFIG, + Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG); } /** diff --git a/tests/TouchLatency/app/src/main/res/values/styles.xml b/tests/TouchLatency/app/src/main/res/values/styles.xml index 5058331187e8..fa352cf1e832 100644 --- a/tests/TouchLatency/app/src/main/res/values/styles.xml +++ b/tests/TouchLatency/app/src/main/res/values/styles.xml @@ -18,7 +18,7 @@ <!-- Base application theme. --> <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar"> <!-- Customize your theme here. --> - <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> + <item name="android:windowLayoutInDisplayCutoutMode">default</item> </style> </resources> diff --git a/tests/Tracing/TEST_MAPPING b/tests/Tracing/TEST_MAPPING index 7f58fceee24d..f6e5221b721b 100644 --- a/tests/Tracing/TEST_MAPPING +++ b/tests/Tracing/TEST_MAPPING @@ -1,5 +1,5 @@ { - "postsubmit": [ + "presubmit": [ { "name": "TracingTests" } diff --git a/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java index 8913e8c1996e..05308464cb9b 100644 --- a/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java @@ -89,7 +89,7 @@ public class LegacyProtoLogImplTest { //noinspection ResultOfMethodCallIgnored mFile.delete(); mProtoLog = new LegacyProtoLogImpl(mFile, mViewerConfigFilename, - 1024 * 1024, mReader, 1024, () -> {}); + 1024 * 1024, mReader, 1024, (instance) -> {}); } @After diff --git a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java index 6f3deab1d4fa..ed256e72b415 100644 --- a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/ProcessedPerfettoProtoLogImplTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -40,7 +41,7 @@ import android.tools.traces.io.ResultWriter; import android.tools.traces.monitors.PerfettoTraceMonitor; import android.tools.traces.protolog.ProtoLogTrace; import android.tracing.perfetto.DataSource; -import android.util.proto.ProtoInputStream; +import android.tracing.perfetto.DataSourceParams; import androidx.test.platform.app.InstrumentationRegistry; @@ -58,6 +59,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mockito; +import org.mockito.stubbing.Answer; import perfetto.protos.Protolog; import perfetto.protos.ProtologCommon; @@ -74,7 +76,7 @@ import java.util.concurrent.atomic.AtomicInteger; @SuppressWarnings("ConstantConditions") @Presubmit @RunWith(JUnit4.class) -public class PerfettoProtoLogImplTest { +public class ProcessedPerfettoProtoLogImplTest { private static final String TEST_PROTOLOG_DATASOURCE_NAME = "test.android.protolog"; private static final String MOCK_VIEWER_CONFIG_FILE = "my/mock/viewer/config/file.pb"; private final File mTracingDirectory = InstrumentationRegistry.getInstrumentation() @@ -94,14 +96,14 @@ public class PerfettoProtoLogImplTest { ); private static ProtoLogConfigurationService sProtoLogConfigurationService; + private static ProtoLogDataSource sTestDataSource; private static PerfettoProtoLogImpl sProtoLog; private static Protolog.ProtoLogViewerConfig.Builder sViewerConfigBuilder; - private static Runnable sCacheUpdater; + private static ProtoLogCacheUpdater sCacheUpdater; private static ProtoLogViewerConfigReader sReader; - public PerfettoProtoLogImplTest() throws IOException { - } + public ProcessedPerfettoProtoLogImplTest() throws IOException { } @BeforeClass public static void setUp() throws Exception { @@ -151,37 +153,38 @@ public class PerfettoProtoLogImplTest { ViewerConfigInputStreamProvider viewerConfigInputStreamProvider = Mockito.mock( ViewerConfigInputStreamProvider.class); Mockito.when(viewerConfigInputStreamProvider.getInputStream()) - .thenAnswer(it -> new ProtoInputStream(sViewerConfigBuilder.build().toByteArray())); + .thenAnswer(it -> new AutoClosableProtoInputStream( + sViewerConfigBuilder.build().toByteArray())); - sCacheUpdater = () -> {}; + sCacheUpdater = (instance) -> {}; sReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider)); + sTestDataSource = new ProtoLogDataSource(TEST_PROTOLOG_DATASOURCE_NAME); + DataSourceParams params = + new DataSourceParams.Builder() + .setBufferExhaustedPolicy( + DataSourceParams + .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP) + .build(); + sTestDataSource.register(params); + busyWaitForDataSourceRegistration(TEST_PROTOLOG_DATASOURCE_NAME); - final ProtoLogDataSourceBuilder dataSourceBuilder = - (onStart, onFlush, onStop) -> new ProtoLogDataSource( - onStart, onFlush, onStop, TEST_PROTOLOG_DATASOURCE_NAME); final ViewerConfigFileTracer tracer = (dataSource, viewerConfigFilePath) -> { Utils.dumpViewerConfig(dataSource, () -> { if (!viewerConfigFilePath.equals(MOCK_VIEWER_CONFIG_FILE)) { throw new RuntimeException( "Unexpected viewer config file path provided"); } - return new ProtoInputStream(sViewerConfigBuilder.build().toByteArray()); + return new AutoClosableProtoInputStream(sViewerConfigBuilder.build().toByteArray()); }); }; sProtoLogConfigurationService = - new ProtoLogConfigurationServiceImpl(dataSourceBuilder, tracer); - - if (android.tracing.Flags.clientSideProtoLogging()) { - sProtoLog = new PerfettoProtoLogImpl( - MOCK_VIEWER_CONFIG_FILE, sReader, () -> sCacheUpdater.run(), - TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService); - } else { - sProtoLog = new PerfettoProtoLogImpl( - viewerConfigInputStreamProvider, sReader, () -> sCacheUpdater.run(), - TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService); - } + new ProtoLogConfigurationServiceImpl(sTestDataSource, tracer); - busyWaitForDataSourceRegistration(TEST_PROTOLOG_DATASOURCE_NAME); + sProtoLog = new ProcessedPerfettoProtoLogImpl(sTestDataSource, + MOCK_VIEWER_CONFIG_FILE, viewerConfigInputStreamProvider, sReader, + (instance) -> sCacheUpdater.update(instance), TestProtoLogGroup.values(), + sProtoLogConfigurationService); + sProtoLog.enable(); } @Before @@ -398,18 +401,17 @@ public class PerfettoProtoLogImplTest { } @Test - public void log_logcatEnabledNoMessage() { + public void log_logcatEnabledNoMessageThrows() { when(sReader.getViewerString(anyLong())).thenReturn(null); PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog); TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true); TestProtoLogGroup.TEST_GROUP.setLogToProto(false); - implSpy.log(LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, - new Object[]{5}); - - verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq( - LogLevel.INFO), eq("UNKNOWN MESSAGE args = (5)")); - verify(sReader).getViewerString(eq(1234L)); + var assertion = assertThrows(RuntimeException.class, () -> + implSpy.log(LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, + new Object[]{5})); + Truth.assertThat(assertion).hasMessageThat() + .contains("Failed to decode message for logcat"); } @Test @@ -539,16 +541,12 @@ public class PerfettoProtoLogImplTest { PerfettoTraceMonitor traceMonitor = PerfettoTraceMonitor.newBuilder() .enableProtoLog(TEST_PROTOLOG_DATASOURCE_NAME) .build(); - long before; - long after; try { traceMonitor.start(); - before = SystemClock.elapsedRealtimeNanos(); sProtoLog.log( LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash, 0b01100100, new Object[]{"test", 1, 0.1, true}); - after = SystemClock.elapsedRealtimeNanos(); } finally { traceMonitor.stop(mWriter); } @@ -606,14 +604,15 @@ public class PerfettoProtoLogImplTest { Truth.assertThat(stacktrace).doesNotContain(DataSource.class.getSimpleName() + ".java"); Truth.assertThat(stacktrace) .doesNotContain(ProtoLogImpl.class.getSimpleName() + ".java"); - Truth.assertThat(stacktrace).contains(PerfettoProtoLogImplTest.class.getSimpleName()); + Truth.assertThat(stacktrace) + .contains(ProcessedPerfettoProtoLogImplTest.class.getSimpleName()); Truth.assertThat(stacktrace).contains("stackTraceTrimmed"); } @Test public void cacheIsUpdatedWhenTracesStartAndStop() { final AtomicInteger cacheUpdateCallCount = new AtomicInteger(0); - sCacheUpdater = cacheUpdateCallCount::incrementAndGet; + sCacheUpdater = (instance) -> cacheUpdateCallCount.incrementAndGet(); PerfettoTraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder() .enableProtoLog(true, @@ -867,6 +866,39 @@ public class PerfettoProtoLogImplTest { .isEqualTo("This message should also be logged 567"); } + @Test + public void enablesLogGroupAfterLoadingConfig() { + sProtoLog.stopLoggingToLogcat( + new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {}); + Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse(); + + doAnswer((Answer<Void>) invocation -> { + // logToLogcat is still false before we laod the viewer config + Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse(); + return null; + }).when(sReader).unloadViewerConfig(any(), any()); + + sProtoLog.startLoggingToLogcat( + new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {}); + Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isTrue(); + } + + @Test + public void disablesLogGroupBeforeUnloadingConfig() { + sProtoLog.startLoggingToLogcat( + new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {}); + Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isTrue(); + + doAnswer((Answer<Void>) invocation -> { + // Already set logToLogcat to false by the time we unload the config + Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse(); + return null; + }).when(sReader).unloadViewerConfig(any(), any()); + sProtoLog.stopLoggingToLogcat( + new String[] { TestProtoLogGroup.TEST_GROUP.name() }, (msg) -> {}); + Truth.assertThat(TestProtoLogGroup.TEST_GROUP.isLogToLogcat()).isFalse(); + } + private enum TestProtoLogGroup implements IProtoLogGroup { TEST_GROUP(true, true, false, "TEST_TAG"); diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java index 9d56a92fad52..3d1e208189b0 100644 --- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java @@ -16,6 +16,8 @@ package com.android.internal.protolog; +import static org.junit.Assert.assertThrows; + import android.platform.test.annotations.Presubmit; import com.android.internal.protolog.common.IProtoLogGroup; @@ -44,8 +46,29 @@ public class ProtoLogTest { .containsExactly(TEST_GROUP_1, TEST_GROUP_2); } + @Test + public void deduplicatesRegisteringDuplicateGroup() { + ProtoLog.init(TEST_GROUP_1, TEST_GROUP_1, TEST_GROUP_2); + + final var instance = ProtoLog.getSingleInstance(); + Truth.assertThat(instance.getRegisteredGroups()) + .containsExactly(TEST_GROUP_1, TEST_GROUP_2); + } + + @Test + public void throwOnRegisteringGroupsWithIdCollisions() { + final var assertion = assertThrows(RuntimeException.class, + () -> ProtoLog.init(TEST_GROUP_1, TEST_GROUP_WITH_COLLISION, TEST_GROUP_2)); + + Truth.assertThat(assertion).hasMessageThat() + .contains("" + TEST_GROUP_WITH_COLLISION.getId()); + Truth.assertThat(assertion).hasMessageThat().contains("collision"); + } + private static final IProtoLogGroup TEST_GROUP_1 = new ProtoLogGroup("TEST_TAG_1", 1); private static final IProtoLogGroup TEST_GROUP_2 = new ProtoLogGroup("TEST_TAG_2", 2); + private static final IProtoLogGroup TEST_GROUP_WITH_COLLISION = + new ProtoLogGroup("TEST_TAG_WITH_COLLISION", 1); private static class ProtoLogGroup implements IProtoLogGroup { private final boolean mEnabled; diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java index 28d7b42764c4..9e029a8d5e57 100644 --- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java @@ -19,9 +19,12 @@ package com.android.internal.protolog; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import android.os.Build; import android.platform.test.annotations.Presubmit; -import android.util.proto.ProtoInputStream; +import com.google.common.truth.Truth; + +import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,6 +32,8 @@ import org.junit.runners.JUnit4; import perfetto.protos.ProtologCommon; +import java.io.File; + @Presubmit @RunWith(JUnit4.class) public class ProtoLogViewerConfigReaderTest { @@ -83,7 +88,7 @@ public class ProtoLogViewerConfigReaderTest { ).build().toByteArray(); private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider = - () -> new ProtoInputStream(TEST_VIEWER_CONFIG); + () -> new AutoClosableProtoInputStream(TEST_VIEWER_CONFIG); private ProtoLogViewerConfigReader mConfig; @@ -121,4 +126,37 @@ public class ProtoLogViewerConfigReaderTest { assertNull(mConfig.getViewerString(4)); assertNull(mConfig.getViewerString(5)); } + + @Test + public void viewerConfigIsOnDevice() { + Assume.assumeFalse(Build.FINGERPRINT.contains("robolectric")); + + final String[] viewerConfigPaths; + if (android.tracing.Flags.perfettoProtologTracing()) { + viewerConfigPaths = new String[] { + "/system_ext/etc/wmshell.protolog.pb", + "/system/etc/core.protolog.pb", + }; + } else { + viewerConfigPaths = new String[] { + "/system_ext/etc/wmshell.protolog.json.gz", + "/system/etc/protolog.conf.json.gz", + }; + } + + for (final var viewerConfigPath : viewerConfigPaths) { + File f = new File(viewerConfigPath); + + Truth.assertWithMessage(f.getAbsolutePath() + " exists").that(f.exists()).isTrue(); + } + + } + + @Test + public void loadUnloadAndReloadViewerConfig() { + loadViewerConfig(); + unloadViewerConfig(); + loadViewerConfig(); + unloadViewerConfig(); + } } diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java index ce519b7a1576..49249333b72b 100644 --- a/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java @@ -67,9 +67,6 @@ public class ProtologDataSourceTest { @Test public void allEnabledTraceMode() { - final ProtoLogDataSource ds = - new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {}); - final ProtoLogDataSource.TlsState tlsState = createTlsState( DataSourceConfigOuterClass.DataSourceConfig.newBuilder().setProtologConfig( ProtologConfig.ProtoLogConfig.newBuilder() @@ -154,8 +151,7 @@ public class ProtologDataSourceTest { private ProtoLogDataSource.TlsState createTlsState( DataSourceConfigOuterClass.DataSourceConfig config) { - final ProtoLogDataSource ds = - Mockito.spy(new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {})); + final ProtoLogDataSource ds = Mockito.spy(new ProtoLogDataSource()); ProtoInputStream configStream = new ProtoInputStream(config.toByteArray()); final ProtoLogDataSource.Instance dsInstance = Mockito.spy( diff --git a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java index e2099e652c49..635e5de935c7 100644 --- a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java +++ b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java @@ -18,19 +18,27 @@ package com.android.server.usblib; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbManager; +import android.hardware.usb.flags.Flags; import android.os.Binder; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Log; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.concurrent.atomic.AtomicInteger; /** @@ -43,13 +51,36 @@ public class UsbManagerTestLib { private UsbManager mUsbManagerSys; private UsbManager mUsbManagerMock; - @Mock private android.hardware.usb.IUsbManager mMockUsbService; + @Mock + private android.hardware.usb.IUsbManager mMockUsbService; + private TestParcelFileDescriptor mTestParcelFileDescriptor = new TestParcelFileDescriptor( + new ParcelFileDescriptor(new FileDescriptor())); + @Mock + private UsbAccessory mMockUsbAccessory; /** * Counter for tracking UsbOperation operations. */ private static final AtomicInteger sUsbOperationCount = new AtomicInteger(); + private class TestParcelFileDescriptor extends ParcelFileDescriptor { + + private final AtomicInteger mCloseCount = new AtomicInteger(); + + TestParcelFileDescriptor(ParcelFileDescriptor wrapped) { + super(wrapped); + } + + @Override + public void close() { + int unused = mCloseCount.incrementAndGet(); + } + + public void clearCloseCount() { + mCloseCount.set(0); + } + } + public UsbManagerTestLib(Context context) { MockitoAnnotations.initMocks(this); mContext = context; @@ -74,6 +105,34 @@ public class UsbManagerTestLib { mUsbManagerSys.setCurrentFunctions(functions); } + private InputStream openAccessoryInputStream(UsbAccessory accessory) { + try { + when(mMockUsbService.openAccessory(accessory)).thenReturn(mTestParcelFileDescriptor); + } catch (RemoteException remEx) { + Log.w(TAG, "RemoteException"); + } + + if (Flags.enableAccessoryStreamApi()) { + return mUsbManagerMock.openAccessoryInputStream(accessory); + } + + throw new UnsupportedOperationException("Stream APIs not available"); + } + + private OutputStream openAccessoryOutputStream(UsbAccessory accessory) { + try { + when(mMockUsbService.openAccessory(accessory)).thenReturn(mTestParcelFileDescriptor); + } catch (RemoteException remEx) { + Log.w(TAG, "RemoteException"); + } + + if (Flags.enableAccessoryStreamApi()) { + return mUsbManagerMock.openAccessoryOutputStream(accessory); + } + + throw new UnsupportedOperationException("Stream APIs not available"); + } + private void testSetGetCurrentFunctions_Matched(long functions) { setCurrentFunctions(functions); assertEquals("CurrentFunctions mismatched: ", functions, getCurrentFunctions()); @@ -94,7 +153,7 @@ public class UsbManagerTestLib { try { setCurrentFunctions(functions); - verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId); + verify(mMockUsbService).setCurrentFunctions(eq(functions), eq(operationId)); } catch (RemoteException remEx) { Log.w(TAG, "RemoteException"); } @@ -118,7 +177,7 @@ public class UsbManagerTestLib { int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid(); setCurrentFunctions(functions); - verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId); + verify(mMockUsbService).setCurrentFunctions(eq(functions), eq(operationId)); } public void testGetCurrentFunctions_shouldMatched() { @@ -138,4 +197,47 @@ public class UsbManagerTestLib { testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_RNDIS); testSetCurrentFunctionsMock_Matched(UsbManager.FUNCTION_NCM); } + + public void testParcelFileDescriptorClosedWhenAllOpenStreamsAreClosed() { + mTestParcelFileDescriptor.clearCloseCount(); + try { + try (InputStream ignored = openAccessoryInputStream(mMockUsbAccessory)) { + //noinspection EmptyTryBlock + try (OutputStream ignored2 = openAccessoryOutputStream(mMockUsbAccessory)) { + // do nothing + } + } + + // ParcelFileDescriptor is closed only once. + assertEquals(mTestParcelFileDescriptor.mCloseCount.get(), 1); + mTestParcelFileDescriptor.clearCloseCount(); + } catch (IOException e) { + // do nothing + } + } + + public void testOnlyOneOpenInputStreamAllowed() { + try { + //noinspection EmptyTryBlock + try (InputStream ignored = openAccessoryInputStream(mMockUsbAccessory)) { + assertThrows(IllegalStateException.class, + () -> openAccessoryInputStream(mMockUsbAccessory)); + } + } catch (IOException e) { + // do nothing + } + } + + public void testOnlyOneOpenOutputStreamAllowed() { + try { + //noinspection EmptyTryBlock + try (OutputStream ignored = openAccessoryOutputStream(mMockUsbAccessory)) { + assertThrows(IllegalStateException.class, + () -> openAccessoryOutputStream(mMockUsbAccessory)); + } + } catch (IOException e) { + // do nothing + } + } + } diff --git a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java index 8b21763b4a24..40fd0b431451 100644 --- a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java +++ b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbManagerApiTest.java @@ -18,17 +18,21 @@ package com.android.server.usbtest; import android.content.Context; import android.hardware.usb.UsbManager; +import android.hardware.usb.flags.Flags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import org.junit.Ignore; +import com.android.server.usblib.UsbManagerTestLib; + +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import com.android.server.usblib.UsbManagerTestLib; - /** * Unit tests for {@link android.hardware.usb.UsbManager}. * Note: MUST claimed MANAGE_USB permission in Manifest @@ -41,6 +45,9 @@ public class UsbManagerApiTest { private final UsbManagerTestLib mUsbManagerTestLib = new UsbManagerTestLib(mContext = InstrumentationRegistry.getContext()); + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); /** * Verify NO SecurityException * Go through System Server @@ -92,4 +99,23 @@ public class UsbManagerApiTest { public void testUsbApi_SetCurrentFunctions_shouldMatched() { mUsbManagerTestLib.testSetCurrentFunctions_shouldMatched(); } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API) + public void testUsbApi_closesParcelFileDescriptorAfterAllStreamsClosed() { + mUsbManagerTestLib.testParcelFileDescriptorClosedWhenAllOpenStreamsAreClosed(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API) + public void testUsbApi_callingOpenAccessoryInputStreamTwiceThrowsException() { + mUsbManagerTestLib.testOnlyOneOpenInputStreamAllowed(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_ACCESSORY_STREAM_API) + public void testUsbApi_callingOpenAccessoryOutputStreamTwiceThrowsException() { + mUsbManagerTestLib.testOnlyOneOpenOutputStreamAllowed(); + } + } diff --git a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java index 56845aeb6a2c..51d57f0a0de9 100644 --- a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java +++ b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java @@ -18,6 +18,10 @@ package com.android.server.usb; import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -31,12 +35,15 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.usb.IUsbOperationInternal; import android.hardware.usb.flags.Flags; +import android.hardware.usb.UsbPort; import android.os.RemoteException; import android.os.UserManager; import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.runner.AndroidJUnit4; +import com.android.server.LocalServices; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -71,26 +78,38 @@ public class UsbServiceTest { private static final int TEST_SECOND_CALLER_ID = 2000; + private static final int TEST_INTERNAL_REQUESTER_REASON_1 = 100; + + private static final int TEST_INTERNAL_REQUESTER_REASON_2 = 200; + private UsbService mUsbService; + private UsbManagerInternal mUsbManagerInternal; + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Before public void setUp() { mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING); + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING_INTERNAL); + LocalServices.removeAllServicesForTest(); MockitoAnnotations.initMocks(this); - when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID), anyBoolean(), eq(TEST_TRANSACTION_ID), - eq(mCallback), any())).thenReturn(true); + when(mUsbPortManager.enableUsbData(eq(TEST_PORT_ID), anyBoolean(), + eq(TEST_TRANSACTION_ID), eq(mCallback), any())).thenReturn(true); mUsbService = new UsbService(mContext, mUsbPortManager, mUsbAlsaManager, mUserManager, mUsbSettingsManager); + mUsbManagerInternal = LocalServices.getService(UsbManagerInternal.class); + assertWithMessage("LocalServices.getService(UsbManagerInternal.class)") + .that(mUsbManagerInternal).isNotNull(); } - private void assertToggleUsbSuccessfully(int uid, boolean enable) { + private void assertToggleUsbSuccessfully(int requester, boolean enable, + boolean isInternalRequest) { assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable, - TEST_TRANSACTION_ID, mCallback, uid)); + TEST_TRANSACTION_ID, mCallback, requester, isInternalRequest)); verify(mUsbPortManager).enableUsbData(TEST_PORT_ID, enable, TEST_TRANSACTION_ID, mCallback, null); @@ -100,9 +119,10 @@ public class UsbServiceTest { clearInvocations(mCallback); } - private void assertToggleUsbFailed(int uid, boolean enable) throws Exception { + private void assertToggleUsbFailed(int requester, boolean enable, + boolean isInternalRequest) throws Exception { assertFalse(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enable, - TEST_TRANSACTION_ID, mCallback, uid)); + TEST_TRANSACTION_ID, mCallback, requester, isInternalRequest)); verifyZeroInteractions(mUsbPortManager); verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL); @@ -116,15 +136,16 @@ public class UsbServiceTest { */ @Test public void disableUsb_successfullyDisable() { - assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false); + assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false); } /** - * Verify enableUsbData successfully enables USB port without error given no other stakers + * Verify enableUsbData successfully enables USB port without error given + * no other stakers */ @Test public void enableUsbWhenNoOtherStakers_successfullyEnable() { - assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true); + assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true, false); } /** @@ -132,47 +153,132 @@ public class UsbServiceTest { */ @Test public void enableUsbPortWithOtherStakers_failsToEnable() throws Exception { - assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false); + assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false); - assertToggleUsbFailed(TEST_SECOND_CALLER_ID, true); + assertToggleUsbFailed(TEST_SECOND_CALLER_ID, true, false); } /** - * Verify enableUsbData successfully enables USB port when the last staker is removed + * Verify enableUsbData successfully enables USB port when the last staker + * is removed */ @Test public void enableUsbByTheOnlyStaker_successfullyEnable() { - assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false); + assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false); - assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true); + assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, true, false); } /** - * Verify enableUsbDataWhileDockedInternal does not enable USB port if other stakers are present + * Verify enableUsbDataWhileDockedInternal does not enable USB port if other + * stakers are present */ @Test public void enableUsbWhileDockedWhenThereAreOtherStakers_failsToEnable() throws RemoteException { - assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false); + assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false); mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID, - mCallback, TEST_SECOND_CALLER_ID); + mCallback, TEST_SECOND_CALLER_ID, false); verifyZeroInteractions(mUsbPortManager); verify(mCallback).onOperationComplete(USB_OPERATION_ERROR_INTERNAL); } /** - * Verify enableUsbDataWhileDockedInternal does enable USB port if other stakers are - * not present + * Verify enableUsbDataWhileDockedInternal does enable USB port if other + * stakers are not present */ @Test public void enableUsbWhileDockedWhenThereAreNoStakers_SuccessfullyEnable() { mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID, - mCallback, TEST_SECOND_CALLER_ID); + mCallback, TEST_SECOND_CALLER_ID, false); verify(mUsbPortManager).enableUsbDataWhileDocked(TEST_PORT_ID, TEST_TRANSACTION_ID, mCallback, null); verifyZeroInteractions(mCallback); } + + /** + * Verify enableUsbData successfully enables USB port without error given no + * other stakers for internal requests + */ + @Test + public void enableUsbWhenNoOtherStakers_forInternalRequest_successfullyEnable() { + assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, true, true); + } + + /** + * Verify enableUsbData does not enable USB port if other internal stakers + * are present for internal requests + */ + @Test + public void enableUsbPortWithOtherInternalStakers_forInternalRequest_failsToEnable() + throws Exception { + assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, true); + + assertToggleUsbFailed(TEST_INTERNAL_REQUESTER_REASON_2, true, true); + } + + /** + * Verify enableUsbData does not enable USB port if other external stakers + * are present for internal requests + */ + @Test + public void enableUsbPortWithOtherExternalStakers_forInternalRequest_failsToEnable() + throws Exception { + assertToggleUsbSuccessfully(TEST_FIRST_CALLER_ID, false, false); + + assertToggleUsbFailed(TEST_INTERNAL_REQUESTER_REASON_2, true, true); + } + + /** + * Verify enableUsbData does not enable USB port if other internal stakers + * are present for external requests + */ + @Test + public void enableUsbPortWithOtherInternalStakers_forExternalRequest_failsToEnable() + throws Exception { + assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, true); + + assertToggleUsbFailed(TEST_FIRST_CALLER_ID, true, false); + } + + /** + * Verify enableUsbData successfully enables USB port when the last staker + * is removed for internal requests + */ + @Test + public void enableUsbByTheOnlyStaker_forInternalRequest_successfullyEnable() { + assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, false, false); + + assertToggleUsbSuccessfully(TEST_INTERNAL_REQUESTER_REASON_1, true, false); + } + + /** + * Verify USB Manager internal calls mPortManager to get UsbPorts + */ + @Test + public void usbManagerInternal_getPorts_callsPortManager() { + when(mUsbPortManager.getPorts()).thenReturn(new UsbPort[] {}); + + UsbPort[] ports = mUsbManagerInternal.getPorts(); + + verify(mUsbPortManager).getPorts(); + assertEquals(ports.length, 0); + } + + @Test + public void usbManagerInternal_enableUsbData_successfullyEnable() { + boolean desiredEnableState = true; + + assertTrue(mUsbManagerInternal.enableUsbData(TEST_PORT_ID, desiredEnableState, + TEST_TRANSACTION_ID, mCallback, TEST_INTERNAL_REQUESTER_REASON_1)); + + verify(mUsbPortManager).enableUsbData(TEST_PORT_ID, + desiredEnableState, TEST_TRANSACTION_ID, mCallback, null); + verifyZeroInteractions(mCallback); + clearInvocations(mUsbPortManager); + clearInvocations(mCallback); + } } diff --git a/tests/broadcasts/OWNERS b/tests/broadcasts/OWNERS deleted file mode 100644 index d2e1f815e8dc..000000000000 --- a/tests/broadcasts/OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -# Bug component: 316181 -include platform/frameworks/base:/BROADCASTS_OWNERS diff --git a/tests/broadcasts/unit/Android.bp b/tests/broadcasts/unit/Android.bp index 47166a713580..9e15ac41d84b 100644 --- a/tests/broadcasts/unit/Android.bp +++ b/tests/broadcasts/unit/Android.bp @@ -1,4 +1,3 @@ -// // Copyright (C) 2024 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. // - package { // See: http://go/android-license-faq // A large-scale-change added 'default_applicable_licenses' to import @@ -38,6 +36,7 @@ android_test { "truth", "flag-junit", "android.app.flags-aconfig-java", + "junit-params", ], certificate: "platform", platform_apis: true, diff --git a/tests/broadcasts/unit/AndroidManifest.xml b/tests/broadcasts/unit/AndroidManifest.xml index e9c5248e4d98..61eb230f7957 100644 --- a/tests/broadcasts/unit/AndroidManifest.xml +++ b/tests/broadcasts/unit/AndroidManifest.xml @@ -22,6 +22,6 @@ </application> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.broadcasts.unit" - android:label="Broadcasts Unit Tests"/> + android:targetPackage="com.android.broadcasts.unit" + android:label="Broadcasts Unit Tests"/> </manifest>
\ No newline at end of file diff --git a/tests/broadcasts/unit/OWNERS b/tests/broadcasts/unit/OWNERS new file mode 100644 index 000000000000..f1e450b7e5f9 --- /dev/null +++ b/tests/broadcasts/unit/OWNERS @@ -0,0 +1,2 @@ +# Bug component: 316181 +include platform/frameworks/base:/BROADCASTS_OWNERS
\ No newline at end of file diff --git a/tests/broadcasts/unit/TEST_MAPPING b/tests/broadcasts/unit/TEST_MAPPING index 0e824c54e92e..b920e2586c86 100644 --- a/tests/broadcasts/unit/TEST_MAPPING +++ b/tests/broadcasts/unit/TEST_MAPPING @@ -1,15 +1,7 @@ { "postsubmit": [ { - "name": "BroadcastUnitTests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "BroadcastUnitTests" } ] } diff --git a/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java index b7c412dea999..15a580c9e8f7 100644 --- a/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java +++ b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java @@ -13,246 +13,236 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.app; -import static android.content.Intent.ACTION_BATTERY_CHANGED; -import static android.content.Intent.ACTION_DEVICE_STORAGE_LOW; +package android.app; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; -import static org.mockito.ArgumentMatchers.anyLong; +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.Intent; import android.content.IntentFilter; -import android.os.BatteryManager; -import android.os.Bundle; -import android.os.SystemProperties; +import android.media.AudioManager; +import android.os.IpcDataCache; +import android.os.RemoteException; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; -import android.util.ArrayMap; - -import androidx.annotation.GuardedBy; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.internal.annotations.Keep; import com.android.modules.utils.testing.ExtendedMockitoRule; -import org.junit.After; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; + import org.junit.Before; -import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mockito; +import org.mockito.Mock; -@EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE) -@RunWith(AndroidJUnit4.class) -@SmallTest +@RunWith(JUnitParamsRunner.class) public class BroadcastStickyCacheTest { - @ClassRule - public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule(); - @Rule - public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule(); @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this) - .mockStatic(SystemProperties.class) + .mockStatic(IpcDataCache.class) + .mockStatic(ActivityManager.class) .build(); - private static final String PROP_KEY_BATTERY_CHANGED = BroadcastStickyCache.getKey( - ACTION_BATTERY_CHANGED); + @Mock + private IActivityManager mActivityManagerMock; - private final TestSystemProps mTestSystemProps = new TestSystemProps(); + @Mock + private IApplicationThread mIApplicationThreadMock; + + @Keep + private static Object stickyBroadcastList() { + return BroadcastStickyCache.STICKY_BROADCAST_ACTIONS; + } @Before public void setUp() { - doAnswer(invocation -> { - final String name = invocation.getArgument(0); - final long value = Long.parseLong(invocation.getArgument(1)); - mTestSystemProps.add(name, value); - return null; - }).when(() -> SystemProperties.set(anyString(), anyString())); - doAnswer(invocation -> { - final String name = invocation.getArgument(0); - final TestSystemProps.Handle testHandle = mTestSystemProps.query(name); - if (testHandle == null) { - return null; - } - final SystemProperties.Handle handle = Mockito.mock(SystemProperties.Handle.class); - doAnswer(handleInvocation -> testHandle.getLong(-1)).when(handle).getLong(anyLong()); - return handle; - }).when(() -> SystemProperties.find(anyString())); - } + BroadcastStickyCache.clearCacheForTest(); - @After - public void tearDown() { - mTestSystemProps.clear(); - BroadcastStickyCache.clearForTest(); + doNothing().when(() -> IpcDataCache.invalidateCache(anyString(), anyString())); } @Test - public void testUseCache_nullFilter() { - assertThat(BroadcastStickyCache.useCache(null)).isEqualTo(false); + @DisableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE) + public void useCache_flagDisabled_returnsFalse() { + assertFalse(BroadcastStickyCache.useCache(new IntentFilter(Intent.ACTION_BATTERY_CHANGED))); } @Test - public void testUseCache_noActions() { - final IntentFilter filter = new IntentFilter(); - assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false); + @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE) + public void useCache_nullFilter_returnsFalse() { + assertFalse(BroadcastStickyCache.useCache(null)); } @Test - public void testUseCache_multipleActions() { - final IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_DEVICE_STORAGE_LOW); - filter.addAction(ACTION_BATTERY_CHANGED); - assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false); + @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE) + public void useCache_filterWithoutAction_returnsFalse() { + assertFalse(BroadcastStickyCache.useCache(new IntentFilter())); } @Test - public void testUseCache_valueNotSet() { - final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED); - assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false); + @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE) + public void useCache_filterWithoutStickyBroadcastAction_returnsFalse() { + assertFalse(BroadcastStickyCache.useCache(new IntentFilter(Intent.ACTION_BOOT_COMPLETED))); } @Test - public void testUseCache() { - final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED); - final Intent intent = new Intent(ACTION_BATTERY_CHANGED) - .putExtra(BatteryManager.EXTRA_LEVEL, 90); - BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); - BroadcastStickyCache.add(filter, intent); - assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(true); - } + @DisableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE) + public void invalidateCache_flagDisabled_cacheNotInvalidated() { + final String apiName = BroadcastStickyCache.sActionApiNameMap.get( + AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); - @Test - public void testUseCache_versionMismatch() { - final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED); - final Intent intent = new Intent(ACTION_BATTERY_CHANGED) - .putExtra(BatteryManager.EXTRA_LEVEL, 90); - BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); - BroadcastStickyCache.add(filter, intent); - BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); - - assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false); - } + BroadcastStickyCache.invalidateCache( + AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); - @Test - public void testAdd() { - final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED); - Intent intent = new Intent(ACTION_BATTERY_CHANGED) - .putExtra(BatteryManager.EXTRA_LEVEL, 90); - BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); - BroadcastStickyCache.add(filter, intent); - assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(true); - Intent actualIntent = BroadcastStickyCache.getIntentUnchecked(filter); - assertThat(actualIntent).isNotNull(); - assertEquals(actualIntent, intent); - - intent = new Intent(ACTION_BATTERY_CHANGED) - .putExtra(BatteryManager.EXTRA_LEVEL, 99); - BroadcastStickyCache.add(filter, intent); - actualIntent = BroadcastStickyCache.getIntentUnchecked(filter); - assertThat(actualIntent).isNotNull(); - assertEquals(actualIntent, intent); + ExtendedMockito.verify( + () -> IpcDataCache.invalidateCache(eq(IpcDataCache.MODULE_SYSTEM), eq(apiName)), + times(0)); } @Test - public void testIncrementVersion_propExists() { - SystemProperties.set(PROP_KEY_BATTERY_CHANGED, String.valueOf(100)); + @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE) + public void invalidateCache_broadcastNotSticky_cacheNotInvalidated() { + BroadcastStickyCache.invalidateCache(Intent.ACTION_AIRPLANE_MODE_CHANGED); - BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); - assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(101); - BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); - assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(102); + ExtendedMockito.verify( + () -> IpcDataCache.invalidateCache(eq(IpcDataCache.MODULE_SYSTEM), anyString()), + times(0)); } @Test - public void testIncrementVersion_propNotExists() { - // Verify that the property doesn't exist - assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1); - - BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); - assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(1); - BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); - assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(2); - } + @EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE) + public void invalidateCache_withStickyBroadcast_cacheInvalidated() { + final String apiName = BroadcastStickyCache.sActionApiNameMap.get( + Intent.ACTION_BATTERY_CHANGED); - @Test - public void testIncrementVersionIfExists_propExists() { - BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); + BroadcastStickyCache.invalidateCache(Intent.ACTION_BATTERY_CHANGED); - BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED); - assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(2); - BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED); - assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(3); + ExtendedMockito.verify( + () -> IpcDataCache.invalidateCache(eq(IpcDataCache.MODULE_SYSTEM), eq(apiName)), + times(1)); } @Test - public void testIncrementVersionIfExists_propNotExists() { - // Verify that the property doesn't exist - assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1); - - BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED); - assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1); - // Verify that property is not added as part of the querying. - BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED); - assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1); - } + public void invalidateAllCaches_cacheInvalidated() { + BroadcastStickyCache.invalidateAllCaches(); - private void assertEquals(Intent actualIntent, Intent expectedIntent) { - assertThat(actualIntent.getAction()).isEqualTo(expectedIntent.getAction()); - assertEquals(actualIntent.getExtras(), expectedIntent.getExtras()); + for (int i = BroadcastStickyCache.sActionApiNameMap.size() - 1; i > -1; i--) { + final String apiName = BroadcastStickyCache.sActionApiNameMap.valueAt(i); + ExtendedMockito.verify(() -> IpcDataCache.invalidateCache(anyString(), + eq(apiName)), times(1)); + } } - private void assertEquals(Bundle actualExtras, Bundle expectedExtras) { - assertWithMessage("Extras expected=%s, actual=%s", expectedExtras, actualExtras) - .that(actualExtras.kindofEquals(expectedExtras)).isTrue(); - } + @Test + @Parameters(method = "stickyBroadcastList") + public void getIntent_createNewCache_verifyRegisterReceiverIsCalled(String action) + throws RemoteException { + setActivityManagerMock(action); + final IntentFilter filter = new IntentFilter(action); + final Intent intent = queryIntent(filter); - private static final class TestSystemProps { - @GuardedBy("mSysProps") - private final ArrayMap<String, Long> mSysProps = new ArrayMap<>(); + assertNotNull(intent); + assertEquals(intent.getAction(), action); + verify(mActivityManagerMock, times(1)).registerReceiverWithFeature( + eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(), + eq(filter), anyString(), anyInt(), anyInt()); + } - public void add(String name, long value) { - synchronized (mSysProps) { - mSysProps.put(name, value); - } - } + @Test + public void getIntent_querySameValueTwice_verifyRegisterReceiverIsCalledOnce() + throws RemoteException { + setActivityManagerMock(Intent.ACTION_DEVICE_STORAGE_LOW); + final Intent intent = queryIntent(new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW)); + final Intent cachedIntent = queryIntent(new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW)); - public long get(String name, long defaultValue) { - synchronized (mSysProps) { - final int idx = mSysProps.indexOfKey(name); - return idx >= 0 ? mSysProps.valueAt(idx) : defaultValue; - } - } + assertNotNull(intent); + assertEquals(intent.getAction(), Intent.ACTION_DEVICE_STORAGE_LOW); + assertNotNull(cachedIntent); + assertEquals(cachedIntent.getAction(), Intent.ACTION_DEVICE_STORAGE_LOW); - public Handle query(String name) { - synchronized (mSysProps) { - return mSysProps.containsKey(name) ? new Handle(name) : null; - } - } + verify(mActivityManagerMock, times(1)).registerReceiverWithFeature( + eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(), + any(), anyString(), anyInt(), anyInt()); + } - public void clear() { - synchronized (mSysProps) { - mSysProps.clear(); - } - } + @Test + public void getIntent_queryActionTwiceWithNullResult_verifyRegisterReceiverIsCalledOnce() + throws RemoteException { + setActivityManagerMock(null); + final Intent intent = queryIntent(new IntentFilter(Intent.ACTION_DEVICE_STORAGE_FULL)); + final Intent cachedIntent = queryIntent( + new IntentFilter(Intent.ACTION_DEVICE_STORAGE_FULL)); - public class Handle { - private final String mName; + assertNull(intent); + assertNull(cachedIntent); - Handle(String name) { - mName = name; - } + verify(mActivityManagerMock, times(1)).registerReceiverWithFeature( + eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(), + any(), anyString(), anyInt(), anyInt()); + } - public long getLong(long defaultValue) { - return get(mName, defaultValue); - } - } + @Test + public void getIntent_querySameActionWithDifferentFilter_verifyRegisterReceiverCalledTwice() + throws RemoteException { + setActivityManagerMock(Intent.ACTION_DEVICE_STORAGE_LOW); + final IntentFilter filter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW); + final Intent intent = queryIntent(filter); + + final IntentFilter newFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW); + newFilter.addDataScheme("file"); + final Intent newIntent = queryIntent(newFilter); + + assertNotNull(intent); + assertEquals(intent.getAction(), Intent.ACTION_DEVICE_STORAGE_LOW); + assertNotNull(newIntent); + assertEquals(newIntent.getAction(), Intent.ACTION_DEVICE_STORAGE_LOW); + + verify(mActivityManagerMock, times(1)).registerReceiverWithFeature( + eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(), + eq(filter), anyString(), anyInt(), anyInt()); + + verify(mActivityManagerMock, times(1)).registerReceiverWithFeature( + eq(mIApplicationThreadMock), anyString(), anyString(), anyString(), any(), + eq(newFilter), anyString(), anyInt(), anyInt()); + } + + private Intent queryIntent(IntentFilter filter) { + return BroadcastStickyCache.getIntent( + mIApplicationThreadMock, + "android", + "android", + filter, + "system", + 0, + 0 + ); + } + + private void setActivityManagerMock(String action) throws RemoteException { + when(ActivityManager.getService()).thenReturn(mActivityManagerMock); + when(mActivityManagerMock.registerReceiverWithFeature(any(), anyString(), + anyString(), anyString(), any(), any(), anyString(), anyInt(), + anyInt())).thenReturn(action != null ? new Intent(action) : null); } } diff --git a/tests/graphics/HwAccelerationTest/AndroidManifest.xml b/tests/graphics/HwAccelerationTest/AndroidManifest.xml index db3a992b9c7b..05b2f4c53b15 100644 --- a/tests/graphics/HwAccelerationTest/AndroidManifest.xml +++ b/tests/graphics/HwAccelerationTest/AndroidManifest.xml @@ -24,7 +24,7 @@ <uses-feature android:name="android.hardware.camera"/> <uses-feature android:name="android.hardware.camera.autofocus"/> - <uses-sdk android:minSdkVersion="21"/> + <uses-sdk android:minSdkVersion="21" /> <application android:label="HwUi" android:theme="@android:style/Theme.Material.Light"> @@ -409,6 +409,24 @@ </intent-filter> </activity> + <activity android:name="ScrollingZAboveSurfaceView" + android:label="SurfaceView/Z-Above scrolling" + 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="ScrollingZAboveScaledSurfaceView" + android:label="SurfaceView/Z-Above scrolling, scaled surface" + 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="StretchySurfaceViewActivity" android:label="SurfaceView/Stretchy Movement" android:exported="true"> diff --git a/tests/graphics/HwAccelerationTest/res/layout/scrolling_zabove_surfaceview.xml b/tests/graphics/HwAccelerationTest/res/layout/scrolling_zabove_surfaceview.xml new file mode 100644 index 000000000000..31e5774dd1ad --- /dev/null +++ b/tests/graphics/HwAccelerationTest/res/layout/scrolling_zabove_surfaceview.xml @@ -0,0 +1,131 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + tools:context=".MainActivity"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Above the ScrollView" + android:textColor="#FFFFFFFF" + android:background="#FF444444" + android:padding="32dp" /> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Scrolling Header" + android:background="#FFCCCCCC" + android:padding="32dp" /> + + <SurfaceView + android:layout_width="match_parent" + android:layout_height="500dp" + android:id="@+id/surfaceview" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Scrolling Item" + android:background="#FFCCCCCC" + android:padding="32dp" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Scrolling Item" + android:background="#FFCCCCCC" + android:padding="32dp" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Scrolling Item" + android:background="#FFCCCCCC" + android:padding="32dp" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Scrolling Item" + android:background="#FFCCCCCC" + android:padding="32dp" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Scrolling Item" + android:background="#FFCCCCCC" + android:padding="32dp" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Scrolling Item" + android:background="#FFCCCCCC" + android:padding="32dp" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Scrolling Item" + android:background="#FFCCCCCC" + android:padding="32dp" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Scrolling Item" + android:background="#FFCCCCCC" + android:padding="32dp" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Scrolling Item" + android:background="#FFCCCCCC" + android:padding="32dp" /> + + </LinearLayout> + + </ScrollView> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Below the ScrollView" + android:textColor="#FFFFFFFF" + android:background="#FF444444" + android:padding="32dp" /> + +</LinearLayout>
\ No newline at end of file diff --git a/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveScaledSurfaceView.kt b/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveScaledSurfaceView.kt new file mode 100644 index 000000000000..59ae885664db --- /dev/null +++ b/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveScaledSurfaceView.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.hwui + +import android.app.Activity +import android.graphics.Color +import android.graphics.Paint +import android.os.Bundle +import android.view.SurfaceHolder +import android.view.SurfaceView + +class ScrollingZAboveScaledSurfaceView : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.scrolling_zabove_surfaceview) + + findViewById<SurfaceView>(R.id.surfaceview).apply { + setZOrderOnTop(true) + holder.setFixedSize(1000, 2000) + holder.addCallback(object : SurfaceHolder.Callback { + override fun surfaceCreated(p0: SurfaceHolder) { + + } + + override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { + holder.unlockCanvasAndPost(holder.lockCanvas().apply { + drawColor(Color.BLUE) + val paint = Paint() + paint.textSize = 16 * resources.displayMetrics.density + paint.textAlign = Paint.Align.CENTER + paint.color = Color.WHITE + drawText("I'm a setZOrderOnTop(true) SurfaceView!", + (width / 2).toFloat(), (height / 2).toFloat(), paint) + }) + } + + override fun surfaceDestroyed(p0: SurfaceHolder) { + + } + + }) + } + } +}
\ No newline at end of file diff --git a/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveSurfaceView.kt b/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveSurfaceView.kt new file mode 100644 index 000000000000..ccb71ec0ff2a --- /dev/null +++ b/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveSurfaceView.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.hwui + +import android.app.Activity +import android.graphics.Color +import android.graphics.Paint +import android.os.Bundle +import android.view.SurfaceHolder +import android.view.SurfaceView + +class ScrollingZAboveSurfaceView : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.scrolling_zabove_surfaceview) + + findViewById<SurfaceView>(R.id.surfaceview).apply { + setZOrderOnTop(true) + holder.addCallback(object : SurfaceHolder.Callback { + override fun surfaceCreated(p0: SurfaceHolder) { + + } + + override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { + holder.unlockCanvasAndPost(holder.lockCanvas().apply { + drawColor(Color.BLUE) + val paint = Paint() + paint.textSize = 16 * resources.displayMetrics.density + paint.textAlign = Paint.Align.CENTER + paint.color = Color.WHITE + drawText("I'm a setZOrderOnTop(true) SurfaceView!", + (width / 2).toFloat(), (height / 2).toFloat(), paint) + }) + } + + override fun surfaceDestroyed(p0: SurfaceHolder) { + + } + + }) + } + } +}
\ No newline at end of file diff --git a/tests/graphics/SilkFX/AndroidManifest.xml b/tests/graphics/SilkFX/AndroidManifest.xml index 25092b52e2b6..c293589bdbaf 100644 --- a/tests/graphics/SilkFX/AndroidManifest.xml +++ b/tests/graphics/SilkFX/AndroidManifest.xml @@ -23,13 +23,12 @@ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <application android:label="SilkFX" - android:theme="@style/Theme.UsefulDefault"> + android:theme="@android:style/Theme.Material"> <activity android:name=".Main" android:label="SilkFX Demos" android:banner="@drawable/background1" - android:exported="true" - android:theme="@style/Theme.UsefulDefault"> + android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.DEFAULT"/> diff --git a/tests/graphics/SilkFX/res/layout/activity_background_blur.xml b/tests/graphics/SilkFX/res/layout/activity_background_blur.xml index f13c0883cb01..27eca82dcb23 100644 --- a/tests/graphics/SilkFX/res/layout/activity_background_blur.xml +++ b/tests/graphics/SilkFX/res/layout/activity_background_blur.xml @@ -13,161 +13,168 @@ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License. - --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +--> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/background" - android:layout_width="390dp" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:padding="15dp" - android:orientation="vertical" + android:fitsSystemWindows="true" + android:layout_width="match_parent" + android:layout_height="match_parent" tools:context=".materials.BackgroundBlurActivity"> - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center_horizontal" - android:padding="10dp" - android:textColor="#ffffffff" - android:text="Hello blurry world!"/> - <LinearLayout - android:layout_width="match_parent" + android:id="@+id/background" + android:layout_width="390dp" android:layout_height="wrap_content" - android:orientation="horizontal"> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_weight="1" - android:textColor="#ffffffff" - android:text="Background blur"/> + android:layout_gravity="center" + android:padding="15dp" + android:orientation="vertical"> - <SeekBar - android:id="@+id/set_background_blur" - android:min="0" - android:max="300" - android:layout_width="160dp" - android:layout_height="wrap_content"/> - <TextView - android:id="@+id/background_blur_radius" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textColor="#ffffffff" - android:ems="3" - android:gravity="center" - android:paddingLeft="10dp" - android:paddingRight="10dp" - android:text="TODO"/> - </LinearLayout> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> <TextView - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="1" + android:gravity="center_horizontal" + android:padding="10dp" android:textColor="#ffffffff" - android:text="Background alpha"/> + android:text="Hello blurry world!"/> - <SeekBar - android:id="@+id/set_background_alpha" - android:min="0" - android:max="100" - android:layout_width="160dp" - android:layout_height="wrap_content" /> - <TextView - android:id="@+id/background_alpha" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textColor="#ffffffff" - android:ems="3" - android:gravity="center" - android:paddingLeft="10dp" - android:paddingRight="10dp" - android:text="TODO"/> - </LinearLayout> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - <TextView - android:layout_width="wrap_content" + <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="1" - android:textColor="#ffffffff" - android:text="Blur behind"/> + android:orientation="horizontal"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="#ffffffff" + android:text="Background blur"/> - <SeekBar - android:id="@+id/set_blur_behind" - android:min="0" - android:max="300" - android:layout_width="160dp" - android:layout_height="wrap_content" /> - <TextView - android:id="@+id/blur_behind_radius" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="center" - android:textColor="#ffffffff" - android:paddingLeft="10dp" - android:paddingRight="10dp" - android:ems="3" - android:text="TODO"/> - </LinearLayout> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - <TextView - android:layout_width="wrap_content" + <SeekBar + android:id="@+id/set_background_blur" + android:min="0" + android:max="300" + android:layout_width="160dp" + android:layout_height="wrap_content"/> + <TextView + android:id="@+id/background_blur_radius" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="#ffffffff" + android:ems="3" + android:gravity="center" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:text="TODO"/> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="1" - android:textColor="#ffffffff" - android:text="Dim amount"/> + android:orientation="horizontal"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="#ffffffff" + android:text="Background alpha"/> - <SeekBar - android:id="@+id/set_dim_amount" - android:min="0" - android:max="100" - android:layout_width="160dp" - android:layout_height="wrap_content" /> - <TextView - android:id="@+id/dim_amount" - android:layout_width="wrap_content" + <SeekBar + android:id="@+id/set_background_alpha" + android:min="0" + android:max="100" + android:layout_width="160dp" + android:layout_height="wrap_content" /> + <TextView + android:id="@+id/background_alpha" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="#ffffffff" + android:ems="3" + android:gravity="center" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:text="TODO"/> + </LinearLayout> + <LinearLayout + android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="center" - android:textColor="#ffffffff" - android:paddingLeft="10dp" - android:paddingRight="10dp" - android:ems="3" - android:text="TODO"/> - </LinearLayout> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="5dp" - android:orientation="vertical" - android:gravity="center"> + android:orientation="horizontal"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="#ffffffff" + android:text="Blur behind"/> - <Button - android:id="@+id/toggle_blur_enabled" + <SeekBar + android:id="@+id/set_blur_behind" + android:min="0" + android:max="300" + android:layout_width="160dp" + android:layout_height="wrap_content" /> + <TextView + android:id="@+id/blur_behind_radius" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="#ffffffff" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:ems="3" + android:text="TODO"/> + </LinearLayout> + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="Disable blur" - android:onClick="toggleForceBlurDisabled"/> + android:orientation="horizontal"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:textColor="#ffffffff" + android:text="Dim amount"/> - <Button - android:id="@+id/toggle_battery_saving_mode" + <SeekBar + android:id="@+id/set_dim_amount" + android:min="0" + android:max="100" + android:layout_width="160dp" + android:layout_height="wrap_content" /> + <TextView + android:id="@+id/dim_amount" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:textColor="#ffffffff" + android:paddingLeft="10dp" + android:paddingRight="10dp" + android:ems="3" + android:text="TODO"/> + </LinearLayout> + + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="TODO" - android:onClick="toggleBatterySavingMode"/> - </LinearLayout> - <requestFocus/> + android:layout_gravity="center" + android:layout_marginTop="5dp" + android:orientation="vertical" + android:gravity="center"> + + <Button + android:id="@+id/toggle_blur_enabled" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Disable blur" + android:onClick="toggleForceBlurDisabled"/> -</LinearLayout> + <Button + android:id="@+id/toggle_battery_saving_mode" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="TODO" + android:onClick="toggleBatterySavingMode"/> + </LinearLayout> + <requestFocus/> + + </LinearLayout> +</FrameLayout> diff --git a/tests/graphics/SilkFX/res/layout/activity_glass.xml b/tests/graphics/SilkFX/res/layout/activity_glass.xml index aa09f276d5c8..d591fc4606b0 100644 --- a/tests/graphics/SilkFX/res/layout/activity_glass.xml +++ b/tests/graphics/SilkFX/res/layout/activity_glass.xml @@ -19,6 +19,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" tools:context=".MainActivity"> <ImageView @@ -300,4 +301,4 @@ </androidx.constraintlayout.widget.ConstraintLayout> -</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/tests/graphics/SilkFX/res/layout/color_mode_controls.xml b/tests/graphics/SilkFX/res/layout/color_mode_controls.xml index c0c0bab8a605..9b2b0c818a8e 100644 --- a/tests/graphics/SilkFX/res/layout/color_mode_controls.xml +++ b/tests/graphics/SilkFX/res/layout/color_mode_controls.xml @@ -19,6 +19,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_margin="8dp" android:orientation="vertical"> <TextView @@ -61,4 +62,4 @@ </LinearLayout> -</com.android.test.silkfx.common.ColorModeControls>
\ No newline at end of file +</com.android.test.silkfx.common.ColorModeControls> diff --git a/tests/graphics/SilkFX/res/layout/common_base.xml b/tests/graphics/SilkFX/res/layout/common_base.xml index c0eaf9bc1476..ce6d850af1bc 100644 --- a/tests/graphics/SilkFX/res/layout/common_base.xml +++ b/tests/graphics/SilkFX/res/layout/common_base.xml @@ -18,6 +18,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:orientation="vertical"> <include layout="@layout/color_mode_controls" /> @@ -26,4 +27,4 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/tests/graphics/SilkFX/res/layout/hdr_glows.xml b/tests/graphics/SilkFX/res/layout/hdr_glows.xml index b6050645866a..f1e553a3df23 100644 --- a/tests/graphics/SilkFX/res/layout/hdr_glows.xml +++ b/tests/graphics/SilkFX/res/layout/hdr_glows.xml @@ -18,6 +18,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:orientation="vertical"> <include layout="@layout/color_mode_controls" /> @@ -48,4 +49,4 @@ android:layout_height="50dp" android:layout_margin="8dp" /> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/tests/graphics/SilkFX/res/layout/view_blur_behind.xml b/tests/graphics/SilkFX/res/layout/view_blur_behind.xml new file mode 100644 index 000000000000..83b1fa4b73cb --- /dev/null +++ b/tests/graphics/SilkFX/res/layout/view_blur_behind.xml @@ -0,0 +1,148 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="wowwowwowwowwowwowwowwowwowwowwowwowwowwowwow" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="I'm a little teapot" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="Something. Something." /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="/\\/\\/\\/\\/\\/\\/\\/\\/\\/" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="wowwowwowwowwowwowwowwowwowwowwowwowwowwowwow" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="I'm a little teapot" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="Something. Something." /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="/\\/\\/\\/\\/\\/\\/\\/\\/\\/" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^" /> + + </LinearLayout> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <View + android:layout_width="match_parent" + android:layout_height="300dp" /> + + <com.android.test.silkfx.materials.BlurBehindContainer + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="#33AAAAAA" + android:padding="32dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="48dp" + android:text="Blur!" /> + + </com.android.test.silkfx.materials.BlurBehindContainer> + + <View + android:layout_width="match_parent" + android:layout_height="1024dp" /> + + </LinearLayout> + + </ScrollView> + +</FrameLayout>
\ No newline at end of file diff --git a/tests/graphics/SilkFX/res/values/style.xml b/tests/graphics/SilkFX/res/values/style.xml index 4dd626dfb8f5..75506978024b 100644 --- a/tests/graphics/SilkFX/res/values/style.xml +++ b/tests/graphics/SilkFX/res/values/style.xml @@ -16,21 +16,16 @@ --> <!-- Styles for immersive actions UI. --> <resources xmlns:android="http://schemas.android.com/apk/res/android"> - <style name="Theme.BackgroundBlurTheme" parent= "Theme.AppCompat.Dialog"> + <style name="Theme.BackgroundBlurTheme" parent="Theme.AppCompat.Dialog"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBlurBehindEnabled">true</item> <item name="android:backgroundDimEnabled">false</item> <item name="android:windowElevation">0dp</item> <item name="buttonStyle">@style/AppTheme.Button</item> <item name="colorAccent">#bbffffff</item> - <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> </style> <style name="AppTheme.Button" parent="Widget.AppCompat.Button"> <item name="android:textColor">#ffffffff</item> </style> - <style name="Theme.UsefulDefault" parent="android:Theme.Material"> - <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> - </style> - </resources> diff --git a/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt b/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt index 59a6078376cf..ad7cde44bb35 100644 --- a/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt +++ b/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt @@ -61,7 +61,8 @@ private val AllDemos = listOf( )), DemoGroup("Materials", listOf( Demo("Glass", GlassActivity::class), - Demo("Background Blur", BackgroundBlurActivity::class) + Demo("Background Blur", BackgroundBlurActivity::class), + Demo("View blur behind", R.layout.view_blur_behind, commonControls = false) )) ) @@ -71,6 +72,7 @@ class Main : Activity() { super.onCreate(savedInstanceState) val list = ExpandableListView(this) + list.setFitsSystemWindows(true) setContentView(list) diff --git a/tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt b/tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt new file mode 100644 index 000000000000..ce6348e32969 --- /dev/null +++ b/tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.test.silkfx.materials + +import android.content.Context +import android.graphics.RenderEffect +import android.graphics.Shader +import android.util.AttributeSet +import android.widget.FrameLayout + +class BlurBehindContainer(context: Context, attributeSet: AttributeSet) : FrameLayout(context, attributeSet) { + override fun onFinishInflate() { + super.onFinishInflate() + setBackdropRenderEffect( + RenderEffect.createBlurEffect(16.0f, 16.0f, Shader.TileMode.CLAMP)) + } +}
\ No newline at end of file diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java index 07b733830bd3..0da4521fca71 100644 --- a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java +++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java @@ -143,6 +143,38 @@ public class VibratorManagerServicePermissionTest { } @Test + public void testStartVendorVibrationSessionWithoutVibratePermissionFails() throws Exception { + getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( + Manifest.permission.VIBRATE_VENDOR_EFFECTS, + Manifest.permission.START_VIBRATION_SESSIONS); + expectSecurityException("VIBRATE"); + mVibratorService.startVendorVibrationSession(Process.myUid(), DEVICE_ID, PACKAGE_NAME, + new int[] { 1 }, ATTRS, "testVibrate", null); + } + + @Test + public void testStartVendorVibrationSessionWithoutVibrateVendorEffectsPermissionFails() + throws Exception { + getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( + Manifest.permission.VIBRATE, + Manifest.permission.START_VIBRATION_SESSIONS); + expectSecurityException("VIBRATE"); + mVibratorService.startVendorVibrationSession(Process.myUid(), DEVICE_ID, PACKAGE_NAME, + new int[] { 1 }, ATTRS, "testVibrate", null); + } + + @Test + public void testStartVendorVibrationSessionWithoutStartSessionPermissionFails() + throws Exception { + getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( + Manifest.permission.VIBRATE, + Manifest.permission.VIBRATE_VENDOR_EFFECTS); + expectSecurityException("VIBRATE"); + mVibratorService.startVendorVibrationSession(Process.myUid(), DEVICE_ID, PACKAGE_NAME, + new int[] { 1 }, ATTRS, "testVibrate", null); + } + + @Test public void testCancelVibrateFails() throws RemoteException { expectSecurityException("VIBRATE"); mVibratorService.cancelVibrate(/* usageFilter= */ -1, new Binder()); diff --git a/tests/testables/Android.bp b/tests/testables/Android.bp index 7596ee722d01..17cc0b2a5884 100644 --- a/tests/testables/Android.bp +++ b/tests/testables/Android.bp @@ -25,11 +25,18 @@ package { java_library { name: "testables", - srcs: ["src/**/*.java"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], libs: [ "android.test.runner.stubs.system", "android.test.mock.stubs.system", "androidx.test.rules", "mockito-target-inline-minus-junit4", ], + static_libs: [ + "PlatformMotionTesting", + "kotlinx_coroutines_test", + ], } diff --git a/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt new file mode 100644 index 000000000000..ded467993eef --- /dev/null +++ b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.animation + +import android.animation.AnimatorTestRuleToolkit.Companion.TAG +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.util.Log +import android.view.View +import androidx.core.graphics.drawable.toBitmap +import androidx.test.core.app.ActivityScenario +import java.util.concurrent.TimeUnit +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.takeWhile +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import platform.test.motion.MotionTestRule +import platform.test.motion.RecordedMotion +import platform.test.motion.RecordedMotion.Companion.create +import platform.test.motion.golden.DataPoint +import platform.test.motion.golden.Feature +import platform.test.motion.golden.FrameId +import platform.test.motion.golden.TimeSeries +import platform.test.motion.golden.TimeSeriesCaptureScope +import platform.test.motion.golden.TimestampFrameId +import platform.test.screenshot.captureToBitmapAsync + +class AnimatorTestRuleToolkit( + internal val animatorTestRule: AnimatorTestRule, + internal val testScope: TestScope, + internal val currentActivityScenario: () -> ActivityScenario<*>, +) { + internal companion object { + const val TAG = "AnimatorRuleToolkit" + } +} + +/** Capture utility to extract a [Bitmap] from a [drawable]. */ +fun captureDrawable(drawable: Drawable): Bitmap { + val width = drawable.bounds.right - drawable.bounds.left + val height = drawable.bounds.bottom - drawable.bounds.top + + // If either dimension is 0 this will fail, so we set it to 1 pixel instead. + return drawable.toBitmap( + width = + if (width > 0) { + width + } else { + 1 + }, + height = + if (height > 0) { + height + } else { + 1 + }, + ) +} + +/** Capture utility to extract a [Bitmap] from a [view]. */ +fun captureView(view: View): Bitmap { + return view.captureToBitmapAsync().get(10, TimeUnit.SECONDS) +} + +/** + * Controls the timing of the motion recording. + * + * The time series is recorded while the [recording] function is running. + */ +class MotionControl(val recording: MotionControlFn) + +typealias MotionControlFn = suspend MotionControlScope.() -> Unit + +interface MotionControlScope { + /** Waits until [check] returns true. Invoked on each frame. */ + suspend fun awaitCondition(check: () -> Boolean) + + /** Waits for [count] frames to be processed. */ + suspend fun awaitFrames(count: Int = 1) +} + +/** Defines the sampling of features during a test run. */ +data class AnimatorRuleRecordingSpec<T>( + /** The root `observing` object, available in [timeSeriesCapture]'s [TimeSeriesCaptureScope]. */ + val captureRoot: T, + + /** The timing for the recording. */ + val motionControl: MotionControl, + + /** Time interval between frame captures, in milliseconds. */ + val frameDurationMs: Long = 16L, + + /** Whether a sequence of screenshots should also be recorded. */ + val visualCapture: ((captureRoot: T) -> Bitmap)? = null, + + /** Produces the time-series, invoked on each animation frame. */ + val timeSeriesCapture: TimeSeriesCaptureScope<T>.() -> Unit, +) + +/** Records the time-series of the features specified in [recordingSpec]. */ +fun <T> MotionTestRule<AnimatorTestRuleToolkit>.recordMotion( + recordingSpec: AnimatorRuleRecordingSpec<T> +): RecordedMotion { + with(toolkit.animatorTestRule) { + val activityScenario = toolkit.currentActivityScenario() + val frameIdCollector = mutableListOf<FrameId>() + val propertyCollector = mutableMapOf<String, MutableList<DataPoint<*>>>() + val screenshotCollector = + if (recordingSpec.visualCapture != null) { + mutableListOf<Bitmap>() + } else { + null + } + + fun recordFrame(frameId: FrameId) { + Log.i(TAG, "recordFrame($frameId)") + frameIdCollector.add(frameId) + activityScenario.onActivity { + recordingSpec.timeSeriesCapture.invoke( + TimeSeriesCaptureScope(recordingSpec.captureRoot, propertyCollector) + ) + } + + val bitmap = recordingSpec.visualCapture?.invoke(recordingSpec.captureRoot) + if (bitmap != null) screenshotCollector!!.add(bitmap) + } + + val motionControl = + MotionControlImpl( + toolkit.animatorTestRule, + toolkit.testScope, + recordingSpec.frameDurationMs, + recordingSpec.motionControl, + ) + + Log.i(TAG, "recordMotion() begin recording") + + var startFrameTime: Long? = null + toolkit.currentActivityScenario().onActivity { startFrameTime = currentTime } + while (!motionControl.recordingEnded) { + var time: Long? = null + toolkit.currentActivityScenario().onActivity { time = currentTime } + recordFrame(TimestampFrameId(time!! - startFrameTime!!)) + toolkit.currentActivityScenario().onActivity { motionControl.nextFrame() } + } + + Log.i(TAG, "recordMotion() end recording") + + val timeSeries = + TimeSeries( + frameIdCollector.toList(), + propertyCollector.entries.map { entry -> Feature(entry.key, entry.value) }, + ) + + return create(timeSeries, screenshotCollector) + } +} + +@OptIn(ExperimentalCoroutinesApi::class) +private class MotionControlImpl( + val animatorTestRule: AnimatorTestRule, + val testScope: TestScope, + val frameMs: Long, + motionControl: MotionControl, +) : MotionControlScope { + private val recordingJob = motionControl.recording.launch() + + private val frameEmitter = MutableStateFlow<Long>(0) + private val onFrame = frameEmitter.asStateFlow() + + var recordingEnded: Boolean = false + + fun nextFrame() { + animatorTestRule.advanceTimeBy(frameMs) + + frameEmitter.tryEmit(animatorTestRule.currentTime) + testScope.runCurrent() + + if (recordingJob.isCompleted) { + recordingEnded = true + } + } + + override suspend fun awaitCondition(check: () -> Boolean) { + onFrame.takeWhile { !check() }.collect {} + } + + override suspend fun awaitFrames(count: Int) { + onFrame.take(count).collect {} + } + + private fun MotionControlFn.launch(): Job { + val function = this + return testScope.launch { function() } + } +} diff --git a/tests/testables/src/android/testing/TestWithLooperRule.java b/tests/testables/src/android/testing/TestWithLooperRule.java index 37b39c314e53..6a8e142e2314 100644 --- a/tests/testables/src/android/testing/TestWithLooperRule.java +++ b/tests/testables/src/android/testing/TestWithLooperRule.java @@ -34,13 +34,13 @@ import java.util.List; * Looper for the Statement. */ public class TestWithLooperRule implements MethodRule { - /* * This rule requires to be the inner most Rule, so the next statement is RunAfters * instead of another rule. You can set it by '@Rule(order = Integer.MAX_VALUE)' */ @Override public Statement apply(Statement base, FrameworkMethod method, Object target) { + // getting testRunner check, if AndroidTestingRunning then we skip this rule RunWith runWithAnnotation = target.getClass().getAnnotation(RunWith.class); if (runWithAnnotation != null) { @@ -97,6 +97,12 @@ public class TestWithLooperRule implements MethodRule { case "InvokeParameterizedMethod": this.wrapFieldMethodFor(next, "frameworkMethod", method, target); return; + case "ExpectException": + next = this.getNextStatement(next, "next"); + break; + case "UiThreadStatement": + next = this.getNextStatement(next, "base"); + break; default: throw new Exception( String.format("Unexpected Statement received: [%s]", diff --git a/tests/testables/tests/Android.bp b/tests/testables/tests/Android.bp index 1eb36fa5f908..f0cda535b3aa 100644 --- a/tests/testables/tests/Android.bp +++ b/tests/testables/tests/Android.bp @@ -29,13 +29,19 @@ android_test { "src/**/*.kt", "src/**/I*.aidl", ], + asset_dirs: ["goldens"], resource_dirs: ["res"], static_libs: [ + "PlatformMotionTesting", "androidx.core_core-animation", "androidx.core_core-ktx", + "androidx.test.ext.junit", "androidx.test.rules", "hamcrest-library", + "kotlinx_coroutines_test", "mockito-target-inline-minus-junit4", + "platform-screenshot-diff-core", + "platform-test-annotations", "testables", "truth", ], @@ -50,6 +56,7 @@ android_test { "android.test.mock.stubs.system", ], certificate: "platform", + test_config: "AndroidTest.xml", test_suites: [ "device-tests", "automotive-tests", diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml index 2bfb04fdb765..6cba59872710 100644 --- a/tests/testables/tests/AndroidManifest.xml +++ b/tests/testables/tests/AndroidManifest.xml @@ -23,6 +23,10 @@ <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> + <activity + android:name="platform.test.screenshot.ScreenshotActivity" + android:exported="true"> + </activity> </application> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" diff --git a/tests/testables/tests/AndroidTest.xml b/tests/testables/tests/AndroidTest.xml new file mode 100644 index 000000000000..85f6e6257770 --- /dev/null +++ b/tests/testables/tests/AndroidTest.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs Tests for Testables."> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="TestablesTests.apk" /> + <option name="install-arg" value="-t" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> + <option name="force-root" value="true" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> + <option name="screen-always-on" value="on" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="input keyevent KEYCODE_WAKEUP" /> + <option name="run-command" value="wm dismiss-keyguard" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="framework-base-presubmit" /> + <option name="test-tag" value="TestableTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.testables" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="test-filter-dir" value="/data/data/com.android.testables" /> + <option name="hidden-api-checks" value="false"/> + </test> + + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="directory-keys" value="/data/user/0/com.android.testables/files"/> + <option name="collect-on-run-ended-only" value="true"/> + <option name="clean-up" value="true"/> + </metrics_collector> +</configuration> diff --git a/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png b/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png Binary files differnew file mode 100644 index 000000000000..9aed2e970239 --- /dev/null +++ b/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png diff --git a/tests/testables/tests/goldens/recordFilmstrip_withSpring.png b/tests/testables/tests/goldens/recordFilmstrip_withSpring.png Binary files differnew file mode 100644 index 000000000000..1d0c0c3c3393 --- /dev/null +++ b/tests/testables/tests/goldens/recordFilmstrip_withSpring.png diff --git a/tests/testables/tests/goldens/recordTimeSeries_withAnimator.json b/tests/testables/tests/goldens/recordTimeSeries_withAnimator.json new file mode 100644 index 000000000000..73eb6c74fee6 --- /dev/null +++ b/tests/testables/tests/goldens/recordTimeSeries_withAnimator.json @@ -0,0 +1,64 @@ +{ + "frame_ids": [ + 0, + 20, + 40, + 60, + 80, + 100, + 120, + 140, + 160, + 180, + 200, + 220, + 240, + 260, + 280, + 300, + 320, + 340, + 360, + 380, + 400, + 420, + 440, + 460, + 480, + 500 + ], + "features": [ + { + "name": "alpha", + "type": "float", + "data_points": [ + 1, + 0.9960574, + 0.98429155, + 0.9648882, + 0.9381534, + 0.9045085, + 0.8644843, + 0.818712, + 0.76791346, + 0.7128896, + 0.65450853, + 0.5936906, + 0.5313952, + 0.46860474, + 0.40630943, + 0.34549147, + 0.2871104, + 0.23208654, + 0.181288, + 0.13551569, + 0.09549153, + 0.061846733, + 0.035111785, + 0.015708387, + 0.003942609, + 0 + ] + } + ] +} diff --git a/tests/testables/tests/goldens/recordTimeSeries_withSpring.json b/tests/testables/tests/goldens/recordTimeSeries_withSpring.json new file mode 100644 index 000000000000..2b97bad08e00 --- /dev/null +++ b/tests/testables/tests/goldens/recordTimeSeries_withSpring.json @@ -0,0 +1,48 @@ +{ + "frame_ids": [ + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272 + ], + "features": [ + { + "name": "alpha", + "type": "float", + "data_points": [ + 1, + 0.9488604, + 0.83574325, + 0.7016156, + 0.5691678, + 0.4497436, + 0.34789434, + 0.26431116, + 0.19766562, + 0.14572789, + 0.10601636, + 0.076149896, + 0.05401709, + 0.037837274, + 0.026161024, + 0.017839976, + 0.011983856, + 0.007914998 + ] + } + ] +} diff --git a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt new file mode 100644 index 000000000000..993c3fed9d59 --- /dev/null +++ b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.animation + +import android.graphics.Color +import android.platform.test.annotations.MotionTest +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.internal.dynamicanimation.animation.DynamicAnimation +import com.android.internal.dynamicanimation.animation.SpringAnimation +import com.android.internal.dynamicanimation.animation.SpringForce +import kotlinx.coroutines.test.TestScope +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import platform.test.motion.MotionTestRule +import platform.test.motion.RecordedMotion +import platform.test.motion.testing.createGoldenPathManager +import platform.test.motion.view.ViewFeatureCaptures +import platform.test.screenshot.DeviceEmulationRule +import platform.test.screenshot.DeviceEmulationSpec +import platform.test.screenshot.DisplaySpec +import platform.test.screenshot.ScreenshotActivity +import platform.test.screenshot.ScreenshotTestRule + +@SmallTest +@MotionTest +@RunWith(AndroidJUnit4::class) +class AnimatorTestRuleToolkitTest { + companion object { + private val GOLDEN_PATH_MANAGER = + createGoldenPathManager("frameworks/base/tests/testables/tests/goldens") + + private val EMULATION_SPEC = + DeviceEmulationSpec(DisplaySpec("phone", width = 320, height = 690, densityDpi = 160)) + } + + @get:Rule(order = 0) val deviceEmulationRule = DeviceEmulationRule(EMULATION_SPEC) + @get:Rule(order = 1) val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java) + @get:Rule(order = 2) val animatorTestRule = AnimatorTestRule(this) + @get:Rule(order = 3) val screenshotRule = ScreenshotTestRule(GOLDEN_PATH_MANAGER) + @get:Rule(order = 4) + val motionRule = + MotionTestRule( + AnimatorTestRuleToolkit(animatorTestRule, TestScope()) { activityRule.scenario }, + GOLDEN_PATH_MANAGER, + bitmapDiffer = screenshotRule, + ) + + @Test + fun recordFilmstrip_withAnimator() { + val animatedBox = createScene() + createAnimator(animatedBox).apply { getInstrumentation().runOnMainSync { start() } } + + val recordedMotion = + record( + animatedBox, + MotionControl { awaitFrames(count = 26) }, + sampleIntervalMs = 20L, + recordScreenshots = true, + ) + + motionRule.assertThat(recordedMotion).filmstripMatchesGolden("recordFilmstrip_withAnimator") + } + + @Test + fun recordTimeSeries_withAnimator() { + val animatedBox = createScene() + createAnimator(animatedBox).apply { getInstrumentation().runOnMainSync { start() } } + + val recordedMotion = + record( + animatedBox, + MotionControl { awaitFrames(count = 26) }, + sampleIntervalMs = 20L, + recordScreenshots = false, + ) + + motionRule + .assertThat(recordedMotion) + .timeSeriesMatchesGolden("recordTimeSeries_withAnimator") + } + + @Test + fun recordFilmstrip_withSpring() { + val animatedBox = createScene() + var isDone = false + createSpring(animatedBox).apply { + addEndListener { _, _, _, _ -> isDone = true } + getInstrumentation().runOnMainSync { start() } + } + + val recordedMotion = + record( + animatedBox, + MotionControl { awaitCondition { isDone } }, + sampleIntervalMs = 16L, + recordScreenshots = true, + ) + + motionRule.assertThat(recordedMotion).filmstripMatchesGolden("recordFilmstrip_withSpring") + } + + @Test + fun recordTimeSeries_withSpring() { + val animatedBox = createScene() + var isDone = false + createSpring(animatedBox).apply { + addEndListener { _, _, _, _ -> isDone = true } + getInstrumentation().runOnMainSync { start() } + } + + val recordedMotion = + record( + animatedBox, + MotionControl { awaitCondition { isDone } }, + sampleIntervalMs = 16L, + recordScreenshots = false, + ) + + motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordTimeSeries_withSpring") + } + + private fun createScene(): ViewGroup { + lateinit var sceneRoot: ViewGroup + activityRule.scenario.onActivity { activity -> + sceneRoot = FrameLayout(activity).apply { setBackgroundColor(Color.BLACK) } + activity.setContentView(sceneRoot) + } + getInstrumentation().waitForIdleSync() + return sceneRoot + } + + private fun createAnimator(animatedBox: ViewGroup): AnimatorSet { + return AnimatorSet().apply { + duration = 500 + play( + ValueAnimator.ofFloat(animatedBox.alpha, 0f).apply { + addUpdateListener { animatedBox.alpha = it.animatedValue as Float } + } + ) + } + } + + private fun createSpring(animatedBox: ViewGroup): SpringAnimation { + return SpringAnimation(animatedBox, DynamicAnimation.ALPHA).apply { + spring = + SpringForce(0f).apply { + stiffness = 500f + dampingRatio = 0.95f + } + + setStartValue(animatedBox.alpha) + setMinValue(0f) + setMaxValue(1f) + minimumVisibleChange = 0.01f + } + } + + private fun record( + container: ViewGroup, + motionControl: MotionControl, + sampleIntervalMs: Long, + recordScreenshots: Boolean, + ): RecordedMotion { + val visualCapture = + if (recordScreenshots) { + ::captureView + } else { + null + } + return motionRule.recordMotion( + AnimatorRuleRecordingSpec( + container, + motionControl, + sampleIntervalMs, + visualCapture, + ) { + feature(ViewFeatureCaptures.alpha, "alpha") + } + ) + } +} diff --git a/tests/testables/tests/src/android/testing/TestableLooperJUnit4Test.java b/tests/testables/tests/src/android/testing/TestableLooperJUnit4Test.java new file mode 100644 index 000000000000..b7d5e0e12942 --- /dev/null +++ b/tests/testables/tests/src/android/testing/TestableLooperJUnit4Test.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.testing; + +import android.testing.TestableLooper.RunWithLooper; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test that TestableLooper now handles expected exceptions in tests + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +@RunWithLooper +public class TestableLooperJUnit4Test { + @Rule + public final TestWithLooperRule mTestWithLooperRule = new TestWithLooperRule(); + + @Test(expected = Exception.class) + public void testException() throws Exception { + throw new Exception("this exception is expected"); + } +} + diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java index a826646f69f3..56b0a25ed2dd 100644 --- a/tests/utils/testutils/java/android/os/test/TestLooper.java +++ b/tests/utils/testutils/java/android/os/test/TestLooper.java @@ -93,8 +93,8 @@ public class TestLooper { try { mLooper = LOOPER_CONSTRUCTOR.newInstance(false); - ThreadLocal<Looper> threadLocalLooper = (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD - .get(null); + ThreadLocal<Looper> threadLocalLooper = + (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD.get(null); threadLocalLooper.set(mLooper); } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { throw new RuntimeException("Reflection error constructing or accessing looper", e); |