Accessibility framework performance tests
Bug: 270755989 Test: atest AccessibilityPerfTest Change-Id: Ie249cedc76e91717b12a0302e1e5f72f4f5e8918
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -552,6 +552,21 @@ public final class UiAutomation {
|
||||
return cache.isNodeInCache(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user