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

Commit 02107855 authored by Svetoslav Ganov's avatar Svetoslav Ganov
Browse files

Adding APIs to enable reporting virtual view hierarchies to accessibility serivces.

Added an interface that is the contract for a client to expose a virtual
view hierarchy to accessibility services. Clients impement this interface
and set it in the View that is the root of the virtual sub-tree. Adding
this finctionality via compostion as opposed to inheritance enables apps
to maintain backwards compatibility by setting the accessibility virtual
hierarchy provider on the View only if the API version is high enough.

bug:5382859

Change-Id: I7e3927b71a5517943c6cb071be2e87fba23132bf
parent 2b41c2f3
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -22873,6 +22873,7 @@ package android.view {
    method public boolean fitsSystemWindows();
    method public android.view.View focusSearch(int);
    method public void forceLayout();
    method public android.view.accessibility.AccessibilityNodeProvider getAccessibilityNodeProvider();
    method public float getAlpha();
    method public android.view.animation.Animation getAnimation();
    method public android.os.IBinder getApplicationWindowToken();
@@ -23273,6 +23274,7 @@ package android.view {
  public static class View.AccessibilityDelegate {
    ctor public View.AccessibilityDelegate();
    method public boolean dispatchPopulateAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
    method public android.view.accessibility.AccessibilityNodeProvider getAccessibilityNodeProvider(android.view.View);
    method public void onInitializeAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
    method public void onInitializeAccessibilityNodeInfo(android.view.View, android.view.accessibility.AccessibilityNodeInfo);
    method public void onPopulateAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
@@ -24028,6 +24030,7 @@ package android.view.accessibility {
  public class AccessibilityNodeInfo implements android.os.Parcelable {
    method public void addAction(int);
    method public void addChild(android.view.View);
    method public void addChild(android.view.View, int);
    method public int describeContents();
    method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(java.lang.String);
    method public int getActions();
@@ -24052,6 +24055,7 @@ package android.view.accessibility {
    method public boolean isScrollable();
    method public boolean isSelected();
    method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View);
    method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int);
    method public static android.view.accessibility.AccessibilityNodeInfo obtain();
    method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.accessibility.AccessibilityNodeInfo);
    method public boolean performAction(int);
@@ -24069,10 +24073,12 @@ package android.view.accessibility {
    method public void setLongClickable(boolean);
    method public void setPackageName(java.lang.CharSequence);
    method public void setParent(android.view.View);
    method public void setParent(android.view.View, int);
    method public void setPassword(boolean);
    method public void setScrollable(boolean);
    method public void setSelected(boolean);
    method public void setSource(android.view.View);
    method public void setSource(android.view.View, int);
    method public void setText(java.lang.CharSequence);
    method public void writeToParcel(android.os.Parcel, int);
    field public static final int ACTION_CLEAR_FOCUS = 2; // 0x2
@@ -24082,6 +24088,13 @@ package android.view.accessibility {
    field public static final android.os.Parcelable.Creator CREATOR;
  }
  public abstract class AccessibilityNodeProvider {
    ctor public AccessibilityNodeProvider();
    method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo(int);
    method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(java.lang.String, int);
    method public boolean performAccessibilityAction(int, int);
  }
  public class AccessibilityRecord {
    method public int getAddedCount();
    method public java.lang.CharSequence getBeforeText();
@@ -24127,6 +24140,7 @@ package android.view.accessibility {
    method public void setScrollY(int);
    method public void setScrollable(boolean);
    method public void setSource(android.view.View);
    method public void setSource(android.view.View, int);
    method public void setToIndex(int);
  }
+9 −9
Original line number Diff line number Diff line
@@ -33,14 +33,14 @@ interface IAccessibilityServiceConnection {
     * Finds an {@link AccessibilityNodeInfo} by accessibility id.
     *
     * @param accessibilityWindowId A unique window id.
     * @param accessibilityViewId A unique View accessibility id.
     * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
     * @param interactionId The id of the interaction for matching with the callback result.
     * @param callback Callback which to receive the result.
     * @param threadId The id of the calling thread.
     * @return The current window scale, where zero means a failure.
     */
    float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
        int accessibilityViewId, int interactionId,
        long accessibilityNodeId, int interactionId,
        IAccessibilityInteractionConnectionCallback callback, long threadId);

    /**
@@ -51,15 +51,15 @@ interface IAccessibilityServiceConnection {
     *
     * @param text The searched text.
     * @param accessibilityWindowId A unique window id.
     * @param accessibilityViewId A unique View accessibility id from where to start the search.
     *        Use {@link android.view.View#NO_ID} to start from the root.
     * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id) from
     *        where to start the search. Use {@link android.view.View#NO_ID} to start from the root.
     * @param interactionId The id of the interaction for matching with the callback result.
     * @param callback Callback which to receive the result.
     * @param threadId The id of the calling thread.
     * @return The current window scale, where zero means a failure.
     */
    float findAccessibilityNodeInfosByViewText(String text, int accessibilityWindowId,
        int accessibilityViewId, int interractionId,
    float findAccessibilityNodeInfosByText(String text, int accessibilityWindowId,
        long accessibilityNodeId, int interractionId,
        IAccessibilityInteractionConnectionCallback callback, long threadId);

    /**
@@ -75,7 +75,7 @@ interface IAccessibilityServiceConnection {
     * @param threadId The id of the calling thread.
     * @return The current window scale, where zero means a failure.
     */
    float findAccessibilityNodeInfosByViewTextInActiveWindow(String text,
    float findAccessibilityNodeInfosByTextInActiveWindow(String text,
        int interactionId, IAccessibilityInteractionConnectionCallback callback,
        long threadId);

@@ -96,14 +96,14 @@ interface IAccessibilityServiceConnection {
     * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
     *
     * @param accessibilityWindowId The id of the window.
     * @param accessibilityViewId A unique View accessibility id.
     * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
     * @param action The action to perform.
     * @param interactionId The id of the interaction for matching with the callback result.
     * @param callback Callback which to receive the result.
     * @param threadId The id of the calling thread.
     * @return Whether the action was performed.
     */
    boolean performAccessibilityAction(int accessibilityWindowId, int accessibilityViewId,
    boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
        int action, int interactionId, IAccessibilityInteractionConnectionCallback callback,
        long threadId);
}
+247 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 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.util;

import com.android.internal.util.ArrayUtils;

/**
 * SparseLongArrays map integers to longs.  Unlike a normal array of longs,
 * there can be gaps in the indices.  It is intended to be more efficient
 * than using a HashMap to map Integers to Longs.
 *
 * @hide
 */
public class SparseLongArray implements Cloneable {

    private int[] mKeys;
    private long[] mValues;
    private int mSize;

    /**
     * Creates a new SparseLongArray containing no mappings.
     */
    public SparseLongArray() {
        this(10);
    }

    /**
     * Creates a new SparseLongArray containing no mappings that will not
     * require any additional memory allocation to store the specified
     * number of mappings.
     */
    public SparseLongArray(int initialCapacity) {
        initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);

        mKeys = new int[initialCapacity];
        mValues = new long[initialCapacity];
        mSize = 0;
    }

    @Override
    public SparseLongArray clone() {
        SparseLongArray clone = null;
        try {
            clone = (SparseLongArray) super.clone();
            clone.mKeys = mKeys.clone();
            clone.mValues = mValues.clone();
        } catch (CloneNotSupportedException cnse) {
            /* ignore */
        }
        return clone;
    }

    /**
     * Gets the long mapped from the specified key, or <code>0</code>
     * if no such mapping has been made.
     */
    public long get(int key) {
        return get(key, 0);
    }

    /**
     * Gets the long mapped from the specified key, or the specified value
     * if no such mapping has been made.
     */
    public long get(int key, long valueIfKeyNotFound) {
        int i = binarySearch(mKeys, 0, mSize, key);

        if (i < 0) {
            return valueIfKeyNotFound;
        } else {
            return mValues[i];
        }
    }

    /**
     * Removes the mapping from the specified key, if there was any.
     */
    public void delete(int key) {
        int i = binarySearch(mKeys, 0, mSize, key);

        if (i >= 0) {
            removeAt(i);
        }
    }

    /**
     * Removes the mapping at the given index.
     */
    public void removeAt(int index) {
        System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
        System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
        mSize--;
    }

    /**
     * Adds a mapping from the specified key to the specified value,
     * replacing the previous mapping from the specified key if there
     * was one.
     */
    public void put(int key, long value) {
        int i = binarySearch(mKeys, 0, mSize, key);

        if (i >= 0) {
            mValues[i] = value;
        } else {
            i = ~i;

            if (mSize >= mKeys.length) {
                growKeyAndValueArrays(mSize + 1);
            }

            if (mSize - i != 0) {
                System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
                System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
            }

            mKeys[i] = key;
            mValues[i] = value;
            mSize++;
        }
    }

    /**
     * Returns the number of key-value mappings that this SparseIntArray
     * currently stores.
     */
    public int size() {
        return mSize;
    }

    /**
     * Given an index in the range <code>0...size()-1</code>, returns
     * the key from the <code>index</code>th key-value mapping that this
     * SparseLongArray stores.
     */
    public int keyAt(int index) {
        return mKeys[index];
    }

    /**
     * Given an index in the range <code>0...size()-1</code>, returns
     * the value from the <code>index</code>th key-value mapping that this
     * SparseLongArray stores.
     */
    public long valueAt(int index) {
        return mValues[index];
    }

    /**
     * Returns the index for which {@link #keyAt} would return the
     * specified key, or a negative number if the specified
     * key is not mapped.
     */
    public int indexOfKey(int key) {
        return binarySearch(mKeys, 0, mSize, key);
    }

    /**
     * Returns an index for which {@link #valueAt} would return the
     * specified key, or a negative number if no keys map to the
     * specified value.
     * Beware that this is a linear search, unlike lookups by key,
     * and that multiple keys can map to the same value and this will
     * find only one of them.
     */
    public int indexOfValue(long value) {
        for (int i = 0; i < mSize; i++)
            if (mValues[i] == value)
                return i;

        return -1;
    }

    /**
     * Removes all key-value mappings from this SparseIntArray.
     */
    public void clear() {
        mSize = 0;
    }

    /**
     * Puts a key/value pair into the array, optimizing for the case where
     * the key is greater than all existing keys in the array.
     */
    public void append(int key, long value) {
        if (mSize != 0 && key <= mKeys[mSize - 1]) {
            put(key, value);
            return;
        }

        int pos = mSize;
        if (pos >= mKeys.length) {
            growKeyAndValueArrays(pos + 1);
        }

        mKeys[pos] = key;
        mValues[pos] = value;
        mSize = pos + 1;
    }

    private void growKeyAndValueArrays(int minNeededSize) {
        int n = ArrayUtils.idealLongArraySize(minNeededSize);

        int[] nkeys = new int[n];
        long[] nvalues = new long[n];

        System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
        System.arraycopy(mValues, 0, nvalues, 0, mValues.length);

        mKeys = nkeys;
        mValues = nvalues;
    }

    private static int binarySearch(int[] a, int start, int len, long key) {
        int high = start + len, low = start - 1, guess;

        while (high - low > 1) {
            guess = (high + low) / 2;

            if (a[guess] < key)
                low = guess;
            else
                high = guess;
        }

        if (high == start + len)
            return ~(start + len);
        else if (a[high] == key)
            return high;
        else
            return ~high;
    }
}
+80 −6
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityEventSource;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.EditorInfo;
@@ -1968,6 +1969,21 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
     */
    public static final int FIND_VIEWS_WITH_CONTENT_DESCRIPTION = 0x00000002;
    /**
     * Find views that contain {@link AccessibilityNodeProvider}. Such
     * a View is a root of virtual view hierarchy and may contain the searched
     * text. If this flag is set Views with providers are automatically
     * added and it is a responsibility of the client to call the APIs of
     * the provider to determine whether the virtual tree rooted at this View
     * contains the text, i.e. getting the list of {@link AccessibilityNodeInfo}s
     * represeting the virtual views with this text.
     *
     * @see #findViewsWithText(ArrayList, CharSequence, int)
     *
     * @hide
     */
    public static final int FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS = 0x00000004;
    /**
     * Controls the over-scroll mode for this view.
     * See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)},
@@ -4058,15 +4074,21 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
     * Note: The client is responsible for recycling the obtained instance by calling
     *       {@link AccessibilityNodeInfo#recycle()} to minimize object creation.
     * </p>
     *
     * @return A populated {@link AccessibilityNodeInfo}.
     *
     * @see AccessibilityNodeInfo
     */
    public AccessibilityNodeInfo createAccessibilityNodeInfo() {
        AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
        if (provider != null) {
            return provider.createAccessibilityNodeInfo(View.NO_ID);
        } else {
            AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(this);
            onInitializeAccessibilityNodeInfo(info);
            return info;
        }
    }
    /**
     * Initializes an {@link AccessibilityNodeInfo} with information about this view.
@@ -4167,6 +4189,36 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
        mAccessibilityDelegate = delegate;
    }
    /**
     * Gets the provider for managing a virtual view hierarchy rooted at this View
     * and reported to {@link android.accessibilityservice.AccessibilityService}s
     * that explore the window content.
     * <p>
     * If this method returns an instance, this instance is responsible for managing
     * {@link AccessibilityNodeInfo}s describing the virtual sub-tree rooted at this
     * View including the one representing the View itself. Similarly the returned
     * instance is responsible for performing accessibility actions on any virtual
     * view or the root view itself.
     * </p>
     * <p>
     * If an {@link AccessibilityDelegate} has been specified via calling
     * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
     * {@link AccessibilityDelegate#getAccessibilityNodeProvider(View)}
     * is responsible for handling this call.
     * </p>
     *
     * @return The provider.
     *
     * @see AccessibilityNodeProvider
     */
    public AccessibilityNodeProvider getAccessibilityNodeProvider() {
        if (mAccessibilityDelegate != null) {
            return mAccessibilityDelegate.getAccessibilityNodeProvider(this);
        } else {
            return null;
        }
    }
    /**
     * Gets the unique identifier of this view on the screen for accessibility purposes.
     * If this {@link View} is not attached to any window, {@value #NO_ID} is returned.
@@ -5192,8 +5244,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
     * @see #setContentDescription(CharSequence)
     */
    public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
        if ((flags & FIND_VIEWS_WITH_CONTENT_DESCRIPTION) != 0 && !TextUtils.isEmpty(searched)
                && !TextUtils.isEmpty(mContentDescription)) {
        if (getAccessibilityNodeProvider() != null) {
            if ((flags & FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS) != 0) {
                outViews.add(this);
            }
        } else if ((flags & FIND_VIEWS_WITH_CONTENT_DESCRIPTION) != 0
                && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mContentDescription)) {
            String searchedLowerCase = searched.toString().toLowerCase();
            String contentDescriptionLowerCase = mContentDescription.toString().toLowerCase();
            if (contentDescriptionLowerCase.contains(searchedLowerCase)) {
@@ -14886,5 +14942,23 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
                AccessibilityEvent event) {
            return host.onRequestSendAccessibilityEventInternal(child, event);
        }
        /**
         * Gets the provider for managing a virtual view hierarchy rooted at this View
         * and reported to {@link android.accessibilityservice.AccessibilityService}s
         * that explore the window content.
         * <p>
         * The default implementation behaves as
         * {@link View#getAccessibilityNodeProvider() View#getAccessibilityNodeProvider()} for
         * the case of no accessibility delegate been set.
         * </p>
         *
         * @return The provider.
         *
         * @see AccessibilityNodeProvider
         */
        public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
            return null;
        }
    }
}
+145 −136

File changed.

Preview size limit exceeded, changes collapsed.

Loading