diff options
| author | 2023-06-06 00:32:40 +0000 | |
|---|---|---|
| committer | 2023-06-06 00:32:40 +0000 | |
| commit | ce82c0f4e99c87e2a6c721c2300650293443fbb1 (patch) | |
| tree | f9d23c422c1f88fc9c13738a03ca61ea9def1476 | |
| parent | f9fd0698ac91d1cde44ee3774fb68f05289ffb6a (diff) | |
| parent | 95e2618e9b0e62171d78eb618981531dbd2e558b (diff) | |
Merge "Accessibility framework performance tests"
3 files changed, 230 insertions, 2 deletions
diff --git a/apct-tests/perftests/core/src/android/accessibility/AccessibilityPerfTest.java b/apct-tests/perftests/core/src/android/accessibility/AccessibilityPerfTest.java new file mode 100644 index 000000000000..7927aa90695e --- /dev/null +++ b/apct-tests/perftests/core/src/android/accessibility/AccessibilityPerfTest.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accessibility; + +import static junit.framework.Assert.assertTrue; + +import android.app.Activity; +import android.app.Instrumentation; +import android.app.UiAutomation; +import android.perftests.utils.PerfTestActivity; +import android.platform.test.annotations.LargeTest; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.benchmark.BenchmarkState; +import androidx.benchmark.junit4.BenchmarkRule; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.ActivityTestRule; + +import com.android.compatibility.common.util.TestUtils; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@LargeTest +public class AccessibilityPerfTest { + + private static final String TEXT_KEY = "Child"; + + BenchmarkRule mBenchmarkRule = new BenchmarkRule(); + ActivityTestRule<PerfTestActivity> mActivityTestRule = + new ActivityTestRule(PerfTestActivity.class); + + @Rule + public RuleChain rules = + RuleChain.outerRule(mBenchmarkRule).around(mActivityTestRule); + + private static Instrumentation sInstrumentation; + + private Activity mActivity; + + private ViewGroup createTestViewGroup(int children) { + ViewGroup group = new LinearLayout(mActivity.getBaseContext()); + sInstrumentation.runOnMainSync(() -> { + mActivity.setContentView(group); + for (int i = 0; i < children; i++) { + TextView text = new TextView(mActivity.getBaseContext()); + text.setText(TEXT_KEY); + group.addView(text); + } + }); + + return group; + } + + @BeforeClass + public static void setUpClass() { + sInstrumentation = InstrumentationRegistry.getInstrumentation(); + } + + @Before + public void setUp() { + mActivity = mActivityTestRule.getActivity(); + } + + @Test + public void testCreateAccessibilityNodeInfo() { + final BenchmarkState state = mBenchmarkRule.getState(); + View view = new View(mActivity.getBaseContext()); + + while (state.keepRunning()) { + view.createAccessibilityNodeInfo(); + } + } + + @Test + public void testCreateViewGroupAccessibilityNodeInfo() { + final BenchmarkState state = mBenchmarkRule.getState(); + ViewGroup group = createTestViewGroup(10); + + while (state.keepRunning()) { + group.createAccessibilityNodeInfo(); + } + } + + @Test + public void testCreateAccessibilityEvent() { + final BenchmarkState state = mBenchmarkRule.getState(); + View view = new View(mActivity.getBaseContext()); + + while (state.keepRunning()) { + view.onInitializeAccessibilityEvent( + new AccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED)); + } + } + + @Test + public void testPrefetching() throws Exception { + final BenchmarkState state = mBenchmarkRule.getState(); + createTestViewGroup(AccessibilityNodeInfo.MAX_NUMBER_OF_PREFETCHED_NODES); + UiAutomation uiAutomation = sInstrumentation.getUiAutomation(); + + while (state.keepRunning()) { + state.pauseTiming(); + uiAutomation.clearCache(); + CountDownLatch latch = new CountDownLatch( + AccessibilityNodeInfo.MAX_NUMBER_OF_PREFETCHED_NODES); + uiAutomation.getCache().registerOnNodeAddedListener( + (node) -> { + latch.countDown(); + }); + state.resumeTiming(); + // Get the root node, and await for the latch to have seen the expected max number + // of prefetched nodes. + uiAutomation.getRootInActiveWindow( + AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID + | AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE); + assertTrue(latch.await(100, TimeUnit.MILLISECONDS)); + } + } + + @Test + public void testConnectUiAutomation() throws Exception { + final BenchmarkState state = mBenchmarkRule.getState(); + while (state.keepRunning()) { + UiAutomation uiAutomation = sInstrumentation.getUiAutomation(); + state.pauseTiming(); + uiAutomation.destroy(); + TestUtils.waitUntil( + "UiAutomation did not disconnect.", 10, + () -> uiAutomation.isDestroyed() + ); + state.resumeTiming(); + } + // We currently run into an exception + // if a test ends with UiAutomation explicitly disconnected, + // which seems to be the result of some commands being run by benchmarking. + sInstrumentation.getUiAutomation(); + } +} diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 247d5bc77ffb..b613faefe06d 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -553,6 +553,21 @@ public final class UiAutomation { } /** + * Provides reference to the cache through a locked connection. + * + * @return the accessibility cache. + * @hide + */ + public @Nullable AccessibilityCache getCache() { + final int connectionId; + synchronized (mLock) { + throwIfNotConnectedLocked(); + connectionId = mConnectionId; + } + return AccessibilityInteractionClient.getCache(connectionId); + } + + /** * Adopt the permission identity of the shell UID for all permissions. This allows * you to call APIs protected permissions which normal apps cannot hold but are * granted to the shell UID. If you already adopted all shell permissions by calling @@ -827,6 +842,22 @@ public final class UiAutomation { * established. */ public AccessibilityNodeInfo getRootInActiveWindow() { + return getRootInActiveWindow(AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID); + } + + /** + * Gets the root {@link AccessibilityNodeInfo} in the active window. + * + * @param prefetchingStrategy the prefetching strategy. + * @return The root info. + * @throws IllegalStateException If the connection to the accessibility subsystem is not + * established. + * + * @hide + */ + @Nullable + public AccessibilityNodeInfo getRootInActiveWindow( + @AccessibilityNodeInfo.PrefetchingStrategy int prefetchingStrategy) { final int connectionId; synchronized (mLock) { throwIfNotConnectedLocked(); @@ -834,8 +865,7 @@ public final class UiAutomation { } // Calling out without a lock held. return AccessibilityInteractionClient.getInstance() - .getRootInActiveWindow(connectionId, - AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID); + .getRootInActiveWindow(connectionId, prefetchingStrategy); } /** diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java index e6385a5ecbd5..f3cde43ff25b 100644 --- a/core/java/android/view/accessibility/AccessibilityCache.java +++ b/core/java/android/view/accessibility/AccessibilityCache.java @@ -72,6 +72,8 @@ public class AccessibilityCache { private final AccessibilityNodeRefresher mAccessibilityNodeRefresher; + private OnNodeAddedListener mOnNodeAddedListener; + private long mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; private long mInputFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; /** @@ -542,6 +544,10 @@ public class AccessibilityCache { mInputFocus = sourceId; mInputFocusWindow = windowId; } + + if (mOnNodeAddedListener != null) { + mOnNodeAddedListener.onNodeAdded(clone); + } } } @@ -881,6 +887,26 @@ public class AccessibilityCache { } } + /** + * Registers a listener to receive callbacks whenever nodes are added to cache. + * + * @param listener the listener to be registered. + */ + public void registerOnNodeAddedListener(OnNodeAddedListener listener) { + synchronized (mLock) { + mOnNodeAddedListener = listener; + } + } + + /** + * Clears the current reference to an OnNodeAddedListener, if one exists. + */ + public void clearOnNodeAddedListener() { + synchronized (mLock) { + mOnNodeAddedListener = null; + } + } + // Layer of indirection included to break dependency chain for testing public static class AccessibilityNodeRefresher { /** Refresh the given AccessibilityNodeInfo object. */ @@ -893,4 +919,12 @@ public class AccessibilityCache { return info.refresh(); } } + + /** + * Listener interface that receives callbacks when nodes are added to cache. + */ + public interface OnNodeAddedListener { + /** Called when a node is added to cache. */ + void onNodeAdded(AccessibilityNodeInfo node); + } } |