Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ce82c0f4 authored by Riley Jones's avatar Riley Jones Committed by Android (Google) Code Review
Browse files

Merge "Accessibility framework performance tests"

parents f9fd0698 95e2618e
Loading
Loading
Loading
Loading
+164 −0
Original line number Diff line number Diff line
/*
 * 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();
    }
}
+32 −2
Original line number Diff line number Diff line
@@ -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);
    }

    /**
+34 −0
Original line number Diff line number Diff line
@@ -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);
    }
}