From 2cdedffcfa5594f9d516fa235d5edf4d4f92c21d Mon Sep 17 00:00:00 2001 From: Svetoslav Ganov Date: Mon, 3 Oct 2011 14:18:42 -0700 Subject: [PATCH] Accessibility services cannot obtain the source of an event coming from a root namespace descendant. 1. The user can touch the screen at an arbitrary location potentially crossing the root namespace bounday which will send an accessibility event to accessibility services and they should be able to obtain the event source. Also accessibility ids are guaranteed to be unique in the window. Added a package scoped findViewByAccessibilityId method that dives into nested root namespaces. 2. Added accessibility support to the AnalogClock. bug:5405934 Change-Id: I84edcb554bae41aafcbbc2723c5e62c1ef8a6ddf --- core/java/android/view/View.java | 33 +++++++++++++++++++++++ core/java/android/view/ViewGroup.java | 18 +++++++++++++ core/java/android/view/ViewRootImpl.java | 28 +++---------------- core/java/android/widget/AnalogClock.java | 10 +++++++ core/java/android/widget/TimePicker.java | 4 +-- 5 files changed, 66 insertions(+), 27 deletions(-) diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index d193d6e41504d..31740ef01ad07 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -12092,6 +12092,39 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal return findViewTraversal(id); } + /** + * Finds a view by its unuque and stable accessibility id. + * + * @param accessibilityId The searched accessibility id. + * @return The found view. + */ + final View findViewByAccessibilityId(int accessibilityId) { + if (accessibilityId < 0) { + return null; + } + return findViewByAccessibilityIdTraversal(accessibilityId); + } + + /** + * Performs the traversal to find a view by its unuque and stable accessibility id. + * + * Note:This method does not stop at the root namespace + * boundary since the user can touch the screen at an arbitrary location + * potentially crossing the root namespace bounday which will send an + * accessibility event to accessibility services and they should be able + * to obtain the event source. Also accessibility ids are guaranteed to be + * unique in the window. + * + * @param accessibilityId The accessibility id. + * @return The found view. + */ + View findViewByAccessibilityIdTraversal(int accessibilityId) { + if (getAccessibilityViewId() == accessibilityId) { + return this; + } + return null; + } + /** * Look for a child view with the given tag. If this view has the given * tag, return this view. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index fb0d80a688e4b..5b4a6f8a78e62 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -821,6 +821,24 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + @Override + View findViewByAccessibilityIdTraversal(int accessibilityId) { + View foundView = super.findViewByAccessibilityIdTraversal(accessibilityId); + if (foundView != null) { + return foundView; + } + final int childrenCount = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < childrenCount; i++) { + View child = children[i]; + foundView = child.findViewByAccessibilityIdTraversal(accessibilityId); + if (foundView != null) { + return foundView; + } + } + return null; + } + /** * {@inheritDoc} */ diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 9cb4e5e7b0a8c..a9a0347d10f61 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -74,7 +74,6 @@ import android.view.inputmethod.InputMethodManager; import android.widget.Scroller; import com.android.internal.policy.PolicyManager; -import com.android.internal.util.Predicate; import com.android.internal.view.BaseSurfaceHolder; import com.android.internal.view.IInputMethodCallback; import com.android.internal.view.IInputMethodSession; @@ -4462,9 +4461,6 @@ public final class ViewRootImpl extends Handler implements ViewParent, final class AccessibilityInteractionController { private static final int POOL_SIZE = 5; - private FindByAccessibilitytIdPredicate mFindByAccessibilityIdPredicate = - new FindByAccessibilitytIdPredicate(); - private ArrayList mTempAccessibilityNodeInfoList = new ArrayList(); @@ -4551,11 +4547,8 @@ public final class ViewRootImpl extends Handler implements ViewParent, AccessibilityNodeInfo info = null; try { - FindByAccessibilitytIdPredicate predicate = mFindByAccessibilityIdPredicate; - predicate.init(accessibilityId); - View root = ViewRootImpl.this.mView; - View target = root.findViewByPredicate(predicate); - if (target != null && target.getVisibility() == View.VISIBLE) { + View target = findViewByAccessibilityId(accessibilityId); + if (target != null) { info = target.createAccessibilityNodeInfo(); } } finally { @@ -4794,25 +4787,12 @@ public final class ViewRootImpl extends Handler implements ViewParent, if (root == null) { return null; } - mFindByAccessibilityIdPredicate.init(accessibilityId); - View foundView = root.findViewByPredicate(mFindByAccessibilityIdPredicate); - if (foundView == null || foundView.getVisibility() != View.VISIBLE) { + View foundView = root.findViewByAccessibilityId(accessibilityId); + if (foundView != null && foundView.getVisibility() != View.VISIBLE) { return null; } return foundView; } - - private final class FindByAccessibilitytIdPredicate implements Predicate { - public int mSearchedId; - - public void init(int searchedId) { - mSearchedId = searchedId; - } - - public boolean apply(View view) { - return (view.getAccessibilityViewId() == mSearchedId); - } - } } private class SendWindowContentChangedAccessibilityEvent implements Runnable { diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java index 84ebec34e3d1f..63a0870307f79 100644 --- a/core/java/android/widget/AnalogClock.java +++ b/core/java/android/widget/AnalogClock.java @@ -25,6 +25,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.os.Handler; +import android.text.format.DateUtils; import android.text.format.Time; import android.util.AttributeSet; import android.view.View; @@ -228,6 +229,8 @@ public class AnalogClock extends View { mMinutes = minute + second / 60.0f; mHour = hour + mMinutes / 60.0f; mChanged = true; + + updateContentDescription(mCalendar); } private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @@ -243,4 +246,11 @@ public class AnalogClock extends View { invalidate(); } }; + + private void updateContentDescription(Time time) { + final int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR; + String contentDescription = DateUtils.formatDateTime(mContext, + time.toMillis(false), flags); + setContentDescription(contentDescription); + } } diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index 7444d46178a6d..f52e7739e9a37 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -237,9 +237,7 @@ public class TimePicker extends FrameLayout { } // set the content descriptions - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - setContentDescriptions(); - } + setContentDescriptions(); } @Override