diff options
Diffstat (limited to 'core/java')
19 files changed, 2297 insertions, 86 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 28fc21a..8bb305d 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -25,6 +25,7 @@ import android.os.Message; import android.os.RemoteException; import android.util.Log; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; /** * An accessibility service runs in the background and receives callbacks by the system @@ -51,7 +52,11 @@ import android.view.accessibility.AccessibilityEvent; * enabling or disabling it in the device settings. After the system binds to a service it * calls {@link AccessibilityService#onServiceConnected()}. This method can be * overriden by clients that want to perform post binding setup. + * </p> * <p> + * An accessibility service can be configured to receive specific types of accessibility events, + * listen only to specific packages, get events from each type only once in a given time frame, + * retrieve window content, specify a settings activity, etc. * </p> * There are two approaches for configuring an accessibility service: * <ul> @@ -59,20 +64,23 @@ import android.view.accessibility.AccessibilityEvent; * Providing a {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring * the service. A service declaration with a meta-data tag is presented below: * <p> - * <code> - * <service android:name=".MyAccessibilityService"><br> - * <intent-filter><br> - * <action android:name="android.accessibilityservice.AccessibilityService" /><br> - * </intent-filter><br> - * <meta-data android:name="android.accessibilityservice.as" android:resource="@xml/accessibilityservice" /><br> - * </service><br> - * </code> + * <code> + * <service android:name=".MyAccessibilityService"><br> + * <intent-filter><br> + * <action android:name="android.accessibilityservice.AccessibilityService" /><br> + * </intent-filter><br> + * <meta-data android:name="android.accessibilityservice.as" android:resource="@xml/accessibilityservice" /><br> + * </service><br> + * </code> * </p> * <p> * <strong> * This approach enables setting all accessibility service properties. * </strong> * </p> + * <p> + * For more details refer to {@link #SERVICE_META_DATA}. + * </p> * </li> * <li> * Calling {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}. Note @@ -88,6 +96,9 @@ import android.view.accessibility.AccessibilityEvent; * {@link AccessibilityServiceInfo#packageNames} * </strong> * </p> + * <p> + * For more details refer to {@link AccessibilityServiceInfo}. + * </p> * </li> * </ul> * <p> @@ -151,16 +162,49 @@ public abstract class AccessibilityService extends Service { * <code> * <?xml version="1.0" encoding="utf-8"?><br> * <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"<br> - * android:eventTypes="typeViewClicked|typeViewFocused"<br> + * android:accessibilityEventTypes="typeViewClicked|typeViewFocused"<br> * android:packageNames="foo.bar, foo.baz"<br> - * android:feedbackType="feedbackSpoken"<br> + * android:accessibilityFeedbackType="feedbackSpoken"<br> * android:notificationTimeout="100"<br> - * android:flags="flagDefault"<br> + * android:accessibilityFlags="flagDefault"<br> * android:settingsActivity="foo.bar.TestBackActivity"<br> * . . .<br> * /> * </code> * </p> + * <p> + * <strong>Note:</strong> A service can retrieve only the content of the active window. + * An active window is the source of the most recent event of type + * {@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START}, + * {@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END}, + * {@link AccessibilityEvent#TYPE_VIEW_CLICKED}, + * {@link AccessibilityEvent#TYPE_VIEW_FOCUSED}, + * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}, + * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}, + * {@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED}, + * {@link AccessibilityEvent#TYPE_VIEW_SELECTED}, + * {@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED}, + * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}. + * Therefore the service should: + * <ul> + * <li> + * Register for all event types with no notification timeout and keep track + * for the active window by calling + * {@link AccessibilityEvent#getAccessibilityWindowId()} of the last received + * event and compare this with the + * {@link AccessibilityNodeInfo#getAccessibilityWindowId()} before calling + * retrieval methods on the latter. + * </li> + * <li> + * Prepare that a retrieval method on {@link AccessibilityNodeInfo} may fail + * since the active window has changed and the service did not get the + * accessibility event. Note that it is possible to have a retrieval method + * failing event adopting the strategy specified in the previous bullet + * because the accessibility event dispatch is asynchronous and crosses + * process boundaries. + * </li> + * <ul> + * </p> */ public static final String SERVICE_META_DATA = "android.accessibilityservice"; @@ -224,7 +268,7 @@ public abstract class AccessibilityService extends Service { /** * Implement to return the implementation of the internal accessibility - * service interface. Subclasses should not override. + * service interface. */ @Override public final IBinder onBind(Intent intent) { diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 7157def..19f0bf0 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -17,14 +17,72 @@ package android.accessibilityservice; import android.accessibilityservice.AccessibilityServiceInfo; +import android.view.accessibility.AccessibilityNodeInfo; /** - * Interface AccessibilityManagerService#Service implements, and passes to an - * AccessibilityService so it can dynamically configure how the system handles it. + * Interface given to an AccessibilitySerivce to talk to the AccessibilityManagerService. * * @hide */ -oneway interface IAccessibilityServiceConnection { +interface IAccessibilityServiceConnection { void setServiceInfo(in AccessibilityServiceInfo info); + + /** + * Finds an {@link AccessibilityNodeInfo} by accessibility id. + * <p> + * <strong> + * It is a client responsibility to recycle the received info by + * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating + * of multiple instances. + * </strong> + * </p> + * + * @param accessibilityWindowId A unique window id. + * @param accessibilityViewId A unique View accessibility id. + * @return The node info. + */ + AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, + int accessibilityViewId); + + /** + * Finds {@link AccessibilityNodeInfo}s by View text. The match is case + * insensitive containment. + * <p> + * <strong> + * It is a client responsibility to recycle the received infos by + * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating + * of multiple instances. + * </strong> + * </p> + * + * @param text The searched text. + * @return A list of node info. + */ + List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewText(String text); + + /** + * Finds an {@link AccessibilityNodeInfo} by View id. + * <p> + * <strong> + * It is a client responsibility to recycle the received info by + * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating + * of multiple instances. + * </strong> + * </p> + * + * @param id The id of the node. + * @return The node info. + */ + AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int viewId); + + /** + * Performs an accessibility action on an {@link AccessibilityNodeInfo}. + * + * @param accessibilityWindowId The id of the window. + * @param accessibilityViewId The of a view in the . + * @return Whether the action was performed. + */ + boolean performAccessibilityAction(int accessibilityWindowId, int accessibilityViewId, + int action); } diff --git a/core/java/android/util/FinitePool.java b/core/java/android/util/FinitePool.java index 3ef8293..4ae21ad 100644 --- a/core/java/android/util/FinitePool.java +++ b/core/java/android/util/FinitePool.java @@ -69,6 +69,7 @@ class FinitePool<T extends Poolable<T>> implements Pool<T> { if (element != null) { element.setNextPoolable(null); + element.setPooled(false); mManager.onAcquired(element); } @@ -76,9 +77,13 @@ class FinitePool<T extends Poolable<T>> implements Pool<T> { } public void release(T element) { + if (element.isPooled()) { + throw new IllegalArgumentException("Element already in the pool."); + } if (mInfinite || mPoolCount < mLimit) { mPoolCount++; element.setNextPoolable(mRoot); + element.setPooled(true); mRoot = element; } mManager.onReleased(element); diff --git a/core/java/android/util/Poolable.java b/core/java/android/util/Poolable.java index fd9bd9b..87e0529 100644 --- a/core/java/android/util/Poolable.java +++ b/core/java/android/util/Poolable.java @@ -22,4 +22,6 @@ package android.util; public interface Poolable<T> { void setNextPoolable(T element); T getNextPoolable(); + boolean isPooled(); + void setPooled(boolean isPooled); } diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java index fccef2b..5a91d31 100644 --- a/core/java/android/view/VelocityTracker.java +++ b/core/java/android/view/VelocityTracker.java @@ -50,6 +50,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { private int mPtr; private VelocityTracker mNext; + private boolean mIsPooled; private static native int nativeInitialize(); private static native void nativeDispose(int ptr); @@ -93,6 +94,20 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { return mNext; } + /** + * @hide + */ + public boolean isPooled() { + return mIsPooled; + } + + /** + * @hide + */ + public void setPooled(boolean isPooled) { + mIsPooled = isPooled; + } + private VelocityTracker() { mPtr = nativeInitialize(); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 014f19f..51eb13b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -61,6 +61,7 @@ import android.view.ContextMenu.ContextMenuInfo; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEventSource; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.inputmethod.EditorInfo; @@ -1492,6 +1493,11 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit private static final Object sTagsLock = new Object(); /** + * The next available accessiiblity id. + */ + private static int sNextAccessibilityViewId; + + /** * The animation currently associated with this view. * @hide */ @@ -1533,6 +1539,11 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit int mID = NO_ID; /** + * The stable ID of this view for accessibility porposes. + */ + int mAccessibilityViewId = NO_ID; + + /** * The view's tag. * {@hide} * @@ -3649,6 +3660,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit * @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent) */ public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + event.setSource(this); event.setClassName(getClass().getName()); event.setPackageName(getContext().getPackageName()); event.setEnabled(isEnabled()); @@ -3664,6 +3676,112 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit } /** + * Returns an {@link AccessibilityNodeInfo} representing this view from the + * point of view of an {@link android.accessibilityservice.AccessibilityService}. + * This method is responsible for obtaining an accessibility node info from a + * pool of reusable instances and calling + * {@link #onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} on this view to + * initialize the former. + * <p> + * 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() { + AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(this); + onInitializeAccessibilityNodeInfo(info); + return info; + } + + /** + * Initializes an {@link AccessibilityNodeInfo} with information about this view. + * The base implementation sets: + * <ul> + * <li>{@link AccessibilityNodeInfo#setParent(View)},</li> + * <li>{@link AccessibilityNodeInfo#setBounds(Rect)},</li> + * <li>{@link AccessibilityNodeInfo#setPackageName(CharSequence)},</li> + * <li>{@link AccessibilityNodeInfo#setClassName(CharSequence)},</li> + * <li>{@link AccessibilityNodeInfo#setContentDescription(CharSequence)},</li> + * <li>{@link AccessibilityNodeInfo#setEnabled(boolean)},</li> + * <li>{@link AccessibilityNodeInfo#setClickable(boolean)},</li> + * <li>{@link AccessibilityNodeInfo#setFocusable(boolean)},</li> + * <li>{@link AccessibilityNodeInfo#setFocused(boolean)},</li> + * <li>{@link AccessibilityNodeInfo#setLongClickable(boolean)},</li> + * <li>{@link AccessibilityNodeInfo#setSelected(boolean)},</li> + * </ul> + * <p> + * Subclasses should override this method, call the super implementation, + * and set additional attributes. + * </p> + * @param info The instance to initialize. + */ + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + Rect bounds = mAttachInfo.mTmpInvalRect; + getDrawingRect(bounds); + info.setBounds(bounds); + + ViewParent parent = getParent(); + if (parent instanceof View) { + View parentView = (View) parent; + info.setParent(parentView); + } + + info.setPackageName(mContext.getPackageName()); + info.setClassName(getClass().getName()); + info.setContentDescription(getContentDescription()); + + info.setEnabled(isEnabled()); + info.setClickable(isClickable()); + info.setFocusable(isFocusable()); + info.setFocused(isFocused()); + info.setSelected(isSelected()); + info.setLongClickable(isLongClickable()); + + // TODO: These make sense only if we are in an AdapterView but all + // views can be selected. Maybe from accessiiblity perspective + // we should report as selectable view in an AdapterView. + info.addAction(AccessibilityNodeInfo.ACTION_SELECT); + info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION); + + if (isFocusable()) { + if (isFocused()) { + info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS); + } else { + info.addAction(AccessibilityNodeInfo.ACTION_FOCUS); + } + } + } + + /** + * 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. + * + * @return The view accessibility id. + * + * @hide + */ + public int getAccessibilityViewId() { + if (mAccessibilityViewId == NO_ID) { + mAccessibilityViewId = sNextAccessibilityViewId++; + } + return mAccessibilityViewId; + } + + /** + * Gets the unique identifier of the window in which this View reseides. + * + * @return The window accessibility id. + * + * @hide + */ + public int getAccessibilityWindowId() { + return mAttachInfo != null ? mAttachInfo.mAccessibilityWindowId : NO_ID; + } + + /** * Gets the {@link View} description. It briefly describes the view and is * primarily used for accessibility support. Set this property to enable * better accessibility support for your application. This is especially @@ -4571,6 +4689,16 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit } /** + * Finds the Views that contain given text. The containment is case insensitive. + * As View's text is considered any text content that View renders. + * + * @param outViews The output list of matching Views. + * @param text The text to match against. + */ + public void findViewsWithText(ArrayList<View> outViews, CharSequence text) { + } + + /** * Find and return all touchable views that are descendants of this view, * possibly including this view if it is touchable itself. * @@ -4677,8 +4805,8 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit // need to be focusable in touch mode if in touch mode if (isInTouchMode() && - (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) { - return false; + (FOCUSABLE_IN_TOUCH_MODE != (mViewFlags & FOCUSABLE_IN_TOUCH_MODE))) { + return false; } // need to not have any parents blocking us @@ -12719,6 +12847,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit ); private InvalidateInfo mNext; + private boolean mIsPooled; View target; @@ -12742,6 +12871,14 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit void release() { sPool.release(this); } + + public boolean isPooled() { + return mIsPooled; + } + + public void setPooled(boolean isPooled) { + mIsPooled = isPooled; + } } final IWindowSession mSession; @@ -12952,6 +13089,11 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit final ArrayList<View> mFocusablesTempList = new ArrayList<View>(24); /** + * The id of the window for accessibility purposes. + */ + int mAccessibilityWindowId = View.NO_ID; + + /** * Creates a new set of attachment information with the specified * events handler and thread. * diff --git a/core/java/android/view/ViewAncestor.java b/core/java/android/view/ViewAncestor.java index 1e72529..9ab4c82 100644 --- a/core/java/android/view/ViewAncestor.java +++ b/core/java/android/view/ViewAncestor.java @@ -50,18 +50,28 @@ import android.util.AndroidRuntimeException; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; +import android.util.Pool; +import android.util.Poolable; +import android.util.PoolableManager; +import android.util.Pools; import android.util.Slog; import android.util.SparseArray; import android.util.TypedValue; import android.view.View.MeasureSpec; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.IAccessibilityInteractionConnection; +import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Interpolator; import android.view.inputmethod.InputConnection; 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; @@ -71,6 +81,7 @@ import java.io.IOException; import java.io.OutputStream; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.List; /** * The top of a view hierarchy, implementing the needed protocol between View @@ -248,6 +259,12 @@ public final class ViewAncestor extends Handler implements ViewParent, */ AudioManager mAudioManager; + final AccessibilityManager mAccessibilityManager; + + AccessibilityInteractionController mAccessibilityInteractionContrtoller; + + AccessibilityInteractionConnectionManager mAccessibilityInteractionConnectionManager; + private final int mDensity; /** @@ -285,7 +302,7 @@ public final class ViewAncestor extends Handler implements ViewParent, // done here instead of in the static block because Zygote does not // allow the spawning of threads. getWindowSession(context.getMainLooper()); - + mThread = Thread.currentThread(); mLocation = new WindowLeaked(null); mLocation.fillInStackTrace(); @@ -302,6 +319,11 @@ public final class ViewAncestor extends Handler implements ViewParent, mPreviousTransparentRegion = new Region(); mFirst = true; // true for the first time the view is added mAdded = false; + mAccessibilityManager = AccessibilityManager.getInstance(context); + mAccessibilityInteractionConnectionManager = + new AccessibilityInteractionConnectionManager(); + mAccessibilityManager.addAccessibilityStateChangeListener( + mAccessibilityInteractionConnectionManager); mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, this); mViewConfiguration = ViewConfiguration.get(context); mDensity = context.getResources().getDisplayMetrics().densityDpi; @@ -490,10 +512,14 @@ public final class ViewAncestor extends Handler implements ViewParent, InputQueue.registerInputChannel(mInputChannel, mInputHandler, Looper.myQueue()); } - + view.assignParent(this); mAddedTouchMode = (res&WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE) != 0; mAppVisible = (res&WindowManagerImpl.ADD_FLAG_APP_VISIBLE) != 0; + + if (mAccessibilityManager.isEnabled()) { + mAccessibilityInteractionConnectionManager.ensureConnection(); + } } } } @@ -2004,6 +2030,10 @@ public final class ViewAncestor extends Handler implements ViewParent, mView.dispatchDetachedFromWindow(); } + mAccessibilityInteractionConnectionManager.ensureNoConnection(); + mAccessibilityManager.removeAccessibilityStateChangeListener( + mAccessibilityInteractionConnectionManager); + mView = null; mAttachInfo.mRootView = null; mAttachInfo.mSurface = null; @@ -2020,7 +2050,6 @@ public final class ViewAncestor extends Handler implements ViewParent, InputQueue.unregisterInputChannel(mInputChannel); } } - try { sWindowSession.remove(mWindow); } catch (RemoteException e) { @@ -2098,6 +2127,10 @@ public final class ViewAncestor extends Handler implements ViewParent, public final static int DISPATCH_DRAG_LOCATION_EVENT = 1016; public final static int DISPATCH_SYSTEM_UI_VISIBILITY = 1017; public final static int DISPATCH_GENERIC_MOTION = 1018; + public final static int DO_PERFORM_ACCESSIBILITY_ACTION = 1019; + public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 1020; + public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 1021; + public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT = 1022; @Override public void handleMessage(Message msg) { @@ -2298,9 +2331,25 @@ public final class ViewAncestor extends Handler implements ViewParent, case DISPATCH_SYSTEM_UI_VISIBILITY: { handleDispatchSystemUiVisibilityChanged(msg.arg1); } break; + case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: { + getAccessibilityInteractionController() + .findAccessibilityNodeInfoByAccessibilityIdUiThread(msg); + } break; + case DO_PERFORM_ACCESSIBILITY_ACTION: { + getAccessibilityInteractionController() + .perfromAccessibilityActionUiThread(msg); + } break; + case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: { + getAccessibilityInteractionController() + .findAccessibilityNodeInfoByViewIdUiThread(msg); + } break; + case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT: { + getAccessibilityInteractionController() + .findAccessibilityNodeInfosByViewTextUiThread(msg); + } break; } } - + private void startInputEvent(InputQueue.FinishedCallback finishedCallback) { if (mFinishedCallback != null) { Slog.w(TAG, "Received a new input event from the input queue but there is " @@ -3220,6 +3269,17 @@ public final class ViewAncestor extends Handler implements ViewParent, return mAudioManager; } + public AccessibilityInteractionController getAccessibilityInteractionController() { + if (mView == null) { + throw new IllegalStateException("getAccessibilityInteractionController" + + " called when there is no mView"); + } + if (mAccessibilityInteractionContrtoller == null) { + mAccessibilityInteractionContrtoller = new AccessibilityInteractionController(); + } + return mAccessibilityInteractionContrtoller; + } + private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending) throws RemoteException { @@ -3517,7 +3577,7 @@ public final class ViewAncestor extends Handler implements ViewParent, * send an {@link AccessibilityEvent} to announce that. */ private void sendAccessibilityEvents() { - if (!AccessibilityManager.getInstance(mView.getContext()).isEnabled()) { + if (!mAccessibilityManager.isEnabled()) { return; } mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); @@ -3545,7 +3605,7 @@ public final class ViewAncestor extends Handler implements ViewParent, if (mView == null) { return false; } - AccessibilityManager.getInstance(child.mContext).sendAccessibilityEvent(event); + mAccessibilityManager.sendAccessibilityEvent(event); return true; } @@ -3608,18 +3668,18 @@ public final class ViewAncestor extends Handler implements ViewParent, static class InputMethodCallback extends IInputMethodCallback.Stub { private WeakReference<ViewAncestor> mViewAncestor; - public InputMethodCallback(ViewAncestor viewRoot) { - mViewAncestor = new WeakReference<ViewAncestor>(viewRoot); + public InputMethodCallback(ViewAncestor viewAncestor) { + mViewAncestor = new WeakReference<ViewAncestor>(viewAncestor); } public void finishedEvent(int seq, boolean handled) { - final ViewAncestor viewRoot = mViewAncestor.get(); - if (viewRoot != null) { - viewRoot.dispatchFinishedEvent(seq, handled); + final ViewAncestor viewAncestor = mViewAncestor.get(); + if (viewAncestor != null) { + viewAncestor.dispatchFinishedEvent(seq, handled); } } - public void sessionCreated(IInputMethodSession session) throws RemoteException { + public void sessionCreated(IInputMethodSession session) { // Stub -- not for use in the client. } } @@ -3627,36 +3687,37 @@ public final class ViewAncestor extends Handler implements ViewParent, static class W extends IWindow.Stub { private final WeakReference<ViewAncestor> mViewAncestor; - W(ViewAncestor viewRoot) { - mViewAncestor = new WeakReference<ViewAncestor>(viewRoot); + W(ViewAncestor viewAncestor) { + mViewAncestor = new WeakReference<ViewAncestor>(viewAncestor); } public void resized(int w, int h, Rect coveredInsets, Rect visibleInsets, boolean reportDraw, Configuration newConfig) { - final ViewAncestor viewRoot = mViewAncestor.get(); - if (viewRoot != null) { - viewRoot.dispatchResized(w, h, coveredInsets, visibleInsets, reportDraw, newConfig); + final ViewAncestor viewAncestor = mViewAncestor.get(); + if (viewAncestor != null) { + viewAncestor.dispatchResized(w, h, coveredInsets, visibleInsets, reportDraw, + newConfig); } } public void dispatchAppVisibility(boolean visible) { - final ViewAncestor viewRoot = mViewAncestor.get(); - if (viewRoot != null) { - viewRoot.dispatchAppVisibility(visible); + final ViewAncestor viewAncestor = mViewAncestor.get(); + if (viewAncestor != null) { + viewAncestor.dispatchAppVisibility(visible); } } public void dispatchGetNewSurface() { - final ViewAncestor viewRoot = mViewAncestor.get(); - if (viewRoot != null) { - viewRoot.dispatchGetNewSurface(); + final ViewAncestor viewAncestor = mViewAncestor.get(); + if (viewAncestor != null) { + viewAncestor.dispatchGetNewSurface(); } } public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) { - final ViewAncestor viewRoot = mViewAncestor.get(); - if (viewRoot != null) { - viewRoot.windowFocusChanged(hasFocus, inTouchMode); + final ViewAncestor viewAncestor = mViewAncestor.get(); + if (viewAncestor != null) { + viewAncestor.windowFocusChanged(hasFocus, inTouchMode); } } @@ -3674,9 +3735,9 @@ public final class ViewAncestor extends Handler implements ViewParent, } public void executeCommand(String command, String parameters, ParcelFileDescriptor out) { - final ViewAncestor viewRoot = mViewAncestor.get(); - if (viewRoot != null) { - final View view = viewRoot.mView; + final ViewAncestor viewAncestor = mViewAncestor.get(); + if (viewAncestor != null) { + final View view = viewAncestor.mView; if (view != null) { if (checkCallingPermission(Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -3705,9 +3766,9 @@ public final class ViewAncestor extends Handler implements ViewParent, } public void closeSystemDialogs(String reason) { - final ViewAncestor viewRoot = mViewAncestor.get(); - if (viewRoot != null) { - viewRoot.dispatchCloseSystemDialogs(reason); + final ViewAncestor viewAncestor = mViewAncestor.get(); + if (viewAncestor != null) { + viewAncestor.dispatchCloseSystemDialogs(reason); } } @@ -3720,7 +3781,7 @@ public final class ViewAncestor extends Handler implements ViewParent, } } } - + public void dispatchWallpaperCommand(String action, int x, int y, int z, Bundle extras, boolean sync) { if (sync) { @@ -3733,17 +3794,16 @@ public final class ViewAncestor extends Handler implements ViewParent, /* Drag/drop */ public void dispatchDragEvent(DragEvent event) { - final ViewAncestor viewRoot = mViewAncestor.get(); - if (viewRoot != null) { - viewRoot.dispatchDragEvent(event); + final ViewAncestor viewAncestor = mViewAncestor.get(); + if (viewAncestor != null) { + viewAncestor.dispatchDragEvent(event); } } - @Override public void dispatchSystemUiVisibilityChanged(int visibility) { - final ViewAncestor viewRoot = mViewAncestor.get(); - if (viewRoot != null) { - viewRoot.dispatchSystemUiVisibilityChanged(visibility); + final ViewAncestor viewAncestor = mViewAncestor.get(); + if (viewAncestor != null) { + viewAncestor.dispatchSystemUiVisibilityChanged(visibility); } } } @@ -4053,5 +4113,395 @@ public final class ViewAncestor extends Handler implements ViewParent, } } + /** + * Class for managing the accessibility interaction connection + * based on the global accessibility state. + */ + final class AccessibilityInteractionConnectionManager + implements AccessibilityStateChangeListener { + public void onAccessibilityStateChanged(boolean enabled) { + if (enabled) { + ensureConnection(); + } else { + ensureNoConnection(); + } + } + + public void ensureConnection() { + final boolean registered = mAttachInfo.mAccessibilityWindowId != View.NO_ID; + if (!registered) { + mAttachInfo.mAccessibilityWindowId = + mAccessibilityManager.addAccessibilityInteractionConnection(mWindow, + new AccessibilityInteractionConnection(ViewAncestor.this)); + } + } + + public void ensureNoConnection() { + final boolean registered = mAttachInfo.mAccessibilityWindowId != View.NO_ID; + if (registered) { + mAttachInfo.mAccessibilityWindowId = View.NO_ID; + mAccessibilityManager.removeAccessibilityInteractionConnection(mWindow); + } + } + } + + /** + * This class is an interface this ViewAncestor provides to the + * AccessibilityManagerService to the latter can interact with + * the view hierarchy in this ViewAncestor. + */ + final class AccessibilityInteractionConnection + extends IAccessibilityInteractionConnection.Stub { + private final WeakReference<ViewAncestor> mViewAncestor; + + AccessibilityInteractionConnection(ViewAncestor viewAncestor) { + mViewAncestor = new WeakReference<ViewAncestor>(viewAncestor); + } + + public void findAccessibilityNodeInfoByAccessibilityId(int accessibilityId, + int interactionId, IAccessibilityInteractionConnectionCallback callback) { + final ViewAncestor viewAncestor = mViewAncestor.get(); + if (viewAncestor == null) { + return; + } + getAccessibilityInteractionController() + .findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityId, + interactionId, callback); + } + + public void performAccessibilityAction(int accessibilityId, int action, + int interactionId, IAccessibilityInteractionConnectionCallback callback) { + final ViewAncestor viewAncestor = mViewAncestor.get(); + if (viewAncestor == null) { + return; + } + getAccessibilityInteractionController() + .performAccessibilityActionClientThread(accessibilityId, action, interactionId, + callback); + } + + public void findAccessibilityNodeInfoByViewId(int viewId, + int interactionId, IAccessibilityInteractionConnectionCallback callback) { + final ViewAncestor viewAncestor = mViewAncestor.get(); + if (viewAncestor == null) { + return; + } + getAccessibilityInteractionController() + .findAccessibilityNodeInfoByViewIdClientThread(viewId, interactionId, callback); + } + + public void findAccessibilityNodeInfosByViewText(String text, int interactionId, + IAccessibilityInteractionConnectionCallback callback) { + final ViewAncestor viewAncestor = mViewAncestor.get(); + if (viewAncestor == null) { + return; + } + getAccessibilityInteractionController() + .findAccessibilityNodeInfosByViewTextClientThread(text, interactionId, callback); + } + } + + /** + * Class for managing accessibility interactions initiated from the system + * and targeting the view hierarchy. A *ClientThread method is to be + * called from the interaction connection this ViewAncestor gives the + * system to talk to it and a corresponding *UiThread method that is executed + * on the UI thread. + */ + final class AccessibilityInteractionController { + private static final int POOL_SIZE = 5; + + private FindByAccessibilitytIdPredicate mFindByAccessibilityIdPredicate = + new FindByAccessibilitytIdPredicate(); + + private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList = + new ArrayList<AccessibilityNodeInfo>(); + + // Reusable poolable arguments for interacting with the view hierarchy + // to fit more arguments than Message and to avoid sharing objects between + // two messages since several threads can send messages concurrently. + private final Pool<SomeArgs> mPool = Pools.synchronizedPool(Pools.finitePool( + new PoolableManager<SomeArgs>() { + public SomeArgs newInstance() { + return new SomeArgs(); + } + + public void onAcquired(SomeArgs info) { + /* do nothing */ + } + + public void onReleased(SomeArgs info) { + info.clear(); + } + }, POOL_SIZE) + ); + + public class SomeArgs implements Poolable<SomeArgs> { + private SomeArgs mNext; + private boolean mIsPooled; + + public Object arg1; + public Object arg2; + public int argi1; + public int argi2; + public int argi3; + + public SomeArgs getNextPoolable() { + return mNext; + } + + public boolean isPooled() { + return mIsPooled; + } + + public void setNextPoolable(SomeArgs args) { + mNext = args; + } + + public void setPooled(boolean isPooled) { + mIsPooled = isPooled; + } + + private void clear() { + arg1 = null; + arg2 = null; + argi1 = 0; + argi2 = 0; + argi3 = 0; + } + } + + public void findAccessibilityNodeInfoByAccessibilityIdClientThread(int accessibilityId, + int interactionId, IAccessibilityInteractionConnectionCallback callback) { + Message message = Message.obtain(); + message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID; + message.arg1 = accessibilityId; + message.arg2 = interactionId; + message.obj = callback; + sendMessage(message); + } + + public void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) { + final int accessibilityId = message.arg1; + final int interactionId = message.arg2; + final IAccessibilityInteractionConnectionCallback callback = + (IAccessibilityInteractionConnectionCallback) message.obj; + + View root = ViewAncestor.this.mView; + if (root == null) { + return; + } + + FindByAccessibilitytIdPredicate predicate = mFindByAccessibilityIdPredicate; + predicate.init(accessibilityId); + + View target = root.findViewByPredicate(predicate); + if (target != null) { + AccessibilityNodeInfo info = target.createAccessibilityNodeInfo(); + try { + callback.setFindAccessibilityNodeInfoResult(info, interactionId); + } catch (RemoteException re) { + /* ignore - the other side will time out */ + } + } + } + + public void findAccessibilityNodeInfoByViewIdClientThread(int viewId, int interactionId, + IAccessibilityInteractionConnectionCallback callback) { + Message message = Message.obtain(); + message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID; + message.arg1 = viewId; + message.arg2 = interactionId; + message.obj = callback; + sendMessage(message); + } + + public void findAccessibilityNodeInfoByViewIdUiThread(Message message) { + final int viewId = message.arg1; + final int interactionId = message.arg2; + final IAccessibilityInteractionConnectionCallback callback = + (IAccessibilityInteractionConnectionCallback) message.obj; + + View root = ViewAncestor.this.mView; + if (root == null) { + return; + } + View target = root.findViewById(viewId); + if (target != null) { + AccessibilityNodeInfo info = target.createAccessibilityNodeInfo(); + try { + callback.setFindAccessibilityNodeInfoResult(info, interactionId); + } catch (RemoteException re) { + /* ignore - the other side will time out */ + } + } + } + + public void findAccessibilityNodeInfosByViewTextClientThread(String text, int interactionId, + IAccessibilityInteractionConnectionCallback callback) { + Message message = Message.obtain(); + message.what = DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT; + SomeArgs args = mPool.acquire(); + args.arg1 = text; + args.argi1 = interactionId; + args.arg2 = callback; + message.obj = args; + sendMessage(message); + } + + public void findAccessibilityNodeInfosByViewTextUiThread(Message message) { + SomeArgs args = (SomeArgs) message.obj; + final String text = (String) args.arg1; + final int interactionId = args.argi1; + final IAccessibilityInteractionConnectionCallback callback = + (IAccessibilityInteractionConnectionCallback) args.arg2; + mPool.release(args); + + View root = ViewAncestor.this.mView; + if (root == null) { + return; + } + + ArrayList<View> foundViews = mAttachInfo.mFocusablesTempList; + foundViews.clear(); + + root.findViewsWithText(foundViews, text); + if (foundViews.isEmpty()) { + return; + } + + List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList; + infos.clear(); + + final int viewCount = foundViews.size(); + for (int i = 0; i < viewCount; i++) { + View foundView = foundViews.get(i); + infos.add(foundView.createAccessibilityNodeInfo()); + } + + try { + callback.setFindAccessibilityNodeInfosResult(infos, interactionId); + } catch (RemoteException re) { + /* ignore - the other side will time out */ + } + } + + public void performAccessibilityActionClientThread(int accessibilityId, int action, + int interactionId, IAccessibilityInteractionConnectionCallback callback) { + Message message = Message.obtain(); + message.what = DO_PERFORM_ACCESSIBILITY_ACTION; + SomeArgs args = mPool.acquire(); + args.argi1 = accessibilityId; + args.argi2 = action; + args.argi3 = interactionId; + args.arg1 = callback; + message.obj = args; + sendMessage(message); + } + + public void perfromAccessibilityActionUiThread(Message message) { + SomeArgs args = (SomeArgs) message.obj; + final int accessibilityId = args.argi1; + final int action = args.argi2; + final int interactionId = args.argi3; + final IAccessibilityInteractionConnectionCallback callback = + (IAccessibilityInteractionConnectionCallback) args.arg1; + mPool.release(args); + + if (ViewAncestor.this.mView == null) { + return; + } + + boolean succeeded = false; + switch (action) { + case AccessibilityNodeInfo.ACTION_FOCUS: { + succeeded = performActionFocus(accessibilityId); + } break; + case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: { + succeeded = performActionClearFocus(accessibilityId); + } break; + case AccessibilityNodeInfo.ACTION_SELECT: { + succeeded = performActionSelect(accessibilityId); + } break; + case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: { + succeeded = performActionClearSelection(accessibilityId); + } break; + } + + try { + callback.setPerformAccessibilityActionResult(succeeded, interactionId); + } catch (RemoteException re) { + /* ignore - the other side will time out */ + } + } + + private boolean performActionFocus(int accessibilityId) { + View target = findViewByAccessibilityId(accessibilityId); + if (target == null) { + return false; + } + // Get out of touch mode since accessibility wants to move focus around. + ensureTouchMode(false); + return target.requestFocus(); + } + + private boolean performActionClearFocus(int accessibilityId) { + View target = findViewByAccessibilityId(accessibilityId); + if (target == null) { + return false; + } + if (!target.isFocused()) { + return false; + } + target.clearFocus(); + return !target.isFocused(); + } + + private boolean performActionSelect(int accessibilityId) { + View target = findViewByAccessibilityId(accessibilityId); + if (target == null) { + return false; + } + if (target.isSelected()) { + return false; + } + target.setSelected(true); + return target.isSelected(); + } + + private boolean performActionClearSelection(int accessibilityId) { + View target = findViewByAccessibilityId(accessibilityId); + if (target == null) { + return false; + } + if (!target.isSelected()) { + return false; + } + target.setSelected(false); + return !target.isSelected(); + } + + private View findViewByAccessibilityId(int accessibilityId) { + View root = ViewAncestor.this.mView; + if (root == null) { + return null; + } + mFindByAccessibilityIdPredicate.init(accessibilityId); + return root.findViewByPredicate(mFindByAccessibilityIdPredicate); + } + + private final class FindByAccessibilitytIdPredicate implements Predicate<View> { + public int mSerchedId; + + public void init(int searchedId) { + mSerchedId = searchedId; + } + + public boolean apply(View view) { + return (view.getAccessibilityViewId() == mSerchedId); + } + } + } + private static native void nativeShowFPS(Canvas canvas, int durationMillis); } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index f504b90..57ee8a0 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -35,6 +35,7 @@ import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -772,6 +773,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + @Override + public void findViewsWithText(ArrayList<View> outViews, CharSequence text) { + final int childrenCount = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < childrenCount; i++) { + View child = children[i]; + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { + child.findViewsWithText(outViews, text); + } + } + } + /** * {@inheritDoc} */ @@ -2007,6 +2020,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return false; } + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + + for (int i = 0, count = mChildrenCount; i < count; i++) { + View child = mChildren[i]; + info.addChild(child); + } + } + /** * {@inheritDoc} */ diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 7b80797..32bfa2f 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -16,9 +16,12 @@ package android.view.accessibility; +import android.accessibilityservice.IAccessibilityServiceConnection; import android.os.Parcel; import android.os.Parcelable; +import android.os.RemoteException; import android.text.TextUtils; +import android.view.View; import java.util.ArrayList; @@ -159,6 +162,7 @@ import java.util.ArrayList; * @see android.accessibilityservice.AccessibilityService */ public final class AccessibilityEvent extends AccessibilityRecord implements Parcelable { + private static final boolean DEBUG = false; /** * Invalid selection/focus position. @@ -256,21 +260,38 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par private static final Object sPoolLock = new Object(); private static AccessibilityEvent sPool; private static int sPoolSize; - private AccessibilityEvent mNext; private boolean mIsInPool; private int mEventType; + private int mSourceAccessibilityViewId; + private int mSourceAccessibilityWindowId; private CharSequence mPackageName; private long mEventTime; private final ArrayList<AccessibilityRecord> mRecords = new ArrayList<AccessibilityRecord>(); + private IAccessibilityServiceConnection mConnection; + /* * Hide constructor from clients. */ private AccessibilityEvent() { + } + /** + * Initialize an event from another one. + * + * @param event The event to initialize from. + */ + void init(AccessibilityEvent event) { + super.init(event); + mEventType = event.mEventType; + mEventTime = event.mEventTime; + mSourceAccessibilityWindowId = event.mSourceAccessibilityWindowId; + mSourceAccessibilityViewId = event.mSourceAccessibilityViewId; + mPackageName = event.mPackageName; + mConnection = event.mConnection; } /** @@ -286,8 +307,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * Appends an {@link AccessibilityRecord} to the end of event records. * * @param record The record to append. + * + * @throws IllegalStateException If called from an AccessibilityService. */ public void appendRecord(AccessibilityRecord record) { + enforceNotSealed(); mRecords.add(record); } @@ -311,11 +335,89 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par } /** + * Sets the event source. + * + * @param source The source. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setSource(View source) { + enforceNotSealed(); + if (source != null) { + mSourceAccessibilityWindowId = source.getAccessibilityWindowId(); + mSourceAccessibilityViewId = source.getAccessibilityViewId(); + } else { + mSourceAccessibilityWindowId = View.NO_ID; + mSourceAccessibilityViewId = View.NO_ID; + } + } + + /** + * Gets the {@link AccessibilityNodeInfo} of the event source. + * <p> + * <strong> + * It is a client responsibility to recycle the received info by + * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating + * of multiple instances. + * </strong> + * </p> + * @return The info. + */ + public AccessibilityNodeInfo getSource() { + enforceSealed(); + if (mSourceAccessibilityWindowId == View.NO_ID + || mSourceAccessibilityViewId == View.NO_ID) { + return null; + } + try { + return mConnection.findAccessibilityNodeInfoByAccessibilityId( + mSourceAccessibilityWindowId, mSourceAccessibilityViewId); + } catch (RemoteException e) { + return null; + } + } + + /** + * Gets the id of the window from which the event comes from. + * + * @return The window id. + */ + public int getAccessibilityWindowId() { + return mSourceAccessibilityWindowId; + } + + /** + * Sets the client token for the accessibility service that + * provided this node info. + * + * @param connection The connection. + * + * @hide + */ + public final void setConnection(IAccessibilityServiceConnection connection) { + mConnection = connection; + } + + /** + * Gets the accessibility window id of the source window. + * + * @return The id. + * + * @hide + */ + public int getSourceAccessibilityWindowId() { + return mSourceAccessibilityWindowId; + } + + /** * Sets the event type. * * @param eventType The event type. + * + * @throws IllegalStateException If called from an AccessibilityService. */ public void setEventType(int eventType) { + enforceNotSealed(); mEventType = eventType; } @@ -332,8 +434,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * Sets the time in which this event was sent. * * @param eventTime The event time. + * + * @throws IllegalStateException If called from an AccessibilityService. */ public void setEventTime(long eventTime) { + enforceNotSealed(); mEventTime = eventTime; } @@ -350,8 +455,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * Sets the package name of the source. * * @param packageName The package name. + * + * @throws IllegalStateException If called from an AccessibilityService. */ public void setPackageName(CharSequence packageName) { + enforceNotSealed(); mPackageName = packageName; } @@ -370,6 +478,27 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par /** * Returns a cached instance if such is available or a new one is + * instantiated with type property set. + * + * @param event The other event. + * @return An instance. + */ + public static AccessibilityEvent obtain(AccessibilityEvent event) { + AccessibilityEvent eventClone = AccessibilityEvent.obtain(); + eventClone.init(event); + + final int recordCount = event.mRecords.size(); + for (int i = 0; i < recordCount; i++) { + AccessibilityRecord record = event.mRecords.get(i); + AccessibilityRecord recordClone = AccessibilityRecord.obtain(record); + eventClone.mRecords.add(recordClone); + } + + return eventClone; + } + + /** + * Returns a cached instance if such is available or a new one is * instantiated. * * @return An instance. @@ -413,11 +542,16 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par /** * Clears the state of this instance. + * + * @hide */ @Override protected void clear() { super.clear(); + mConnection = null; mEventType = 0; + mSourceAccessibilityViewId = View.NO_ID; + mSourceAccessibilityWindowId = View.NO_ID; mPackageName = null; mEventTime = 0; while (!mRecords.isEmpty()) { @@ -432,7 +566,14 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * @param parcel A parcel containing the state of a {@link AccessibilityEvent}. */ public void initFromParcel(Parcel parcel) { + if (parcel.readInt() == 1) { + mConnection = IAccessibilityServiceConnection.Stub.asInterface( + parcel.readStrongBinder()); + } + setSealed(parcel.readInt() == 1); mEventType = parcel.readInt(); + mSourceAccessibilityWindowId = parcel.readInt(); + mSourceAccessibilityViewId = parcel.readInt(); mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); mEventTime = parcel.readLong(); readAccessibilityRecordFromParcel(this, parcel); @@ -471,7 +612,16 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * {@inheritDoc} */ public void writeToParcel(Parcel parcel, int flags) { + if (mConnection == null) { + parcel.writeInt(0); + } else { + parcel.writeInt(1); + parcel.writeStrongBinder(mConnection.asBinder()); + } + parcel.writeInt(isSealed() ? 1 : 0); parcel.writeInt(mEventType); + parcel.writeInt(mSourceAccessibilityWindowId); + parcel.writeInt(mSourceAccessibilityViewId); TextUtils.writeToParcel(mPackageName, parcel, 0); parcel.writeLong(mEventTime); writeAccessibilityRecordToParcel(this, parcel, flags); @@ -519,18 +669,36 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par builder.append("; EventType: ").append(eventTypeToString(mEventType)); builder.append("; EventTime: ").append(mEventTime); builder.append("; PackageName: ").append(mPackageName); - builder.append(" \n{\n"); builder.append(super.toString()); - builder.append("\n"); - for (int i = 0; i < mRecords.size(); i++) { - AccessibilityRecord record = mRecords.get(i); - builder.append(" Record "); - builder.append(i); - builder.append(":"); - builder.append(record.toString()); + if (DEBUG) { builder.append("\n"); + builder.append("; sourceAccessibilityWindowId: ").append(mSourceAccessibilityWindowId); + builder.append("; sourceAccessibilityViewId: ").append(mSourceAccessibilityViewId); + for (int i = 0; i < mRecords.size(); i++) { + AccessibilityRecord record = mRecords.get(i); + builder.append(" Record "); + builder.append(i); + builder.append(":"); + builder.append(" [ ClassName: " + record.mClassName); + builder.append("; Text: " + record.mText); + builder.append("; ContentDescription: " + record.mContentDescription); + builder.append("; ItemCount: " + record.mItemCount); + builder.append("; CurrentItemIndex: " + record.mCurrentItemIndex); + builder.append("; IsEnabled: " + record.isEnabled()); + builder.append("; IsPassword: " + record.isPassword()); + builder.append("; IsChecked: " + record.isChecked()); + builder.append("; IsFullScreen: " + record.isFullScreen()); + builder.append("; BeforeText: " + record.mBeforeText); + builder.append("; FromIndex: " + record.mFromIndex); + builder.append("; AddedCount: " + record.mAddedCount); + builder.append("; RemovedCount: " + record.mRemovedCount); + builder.append("; ParcelableData: " + record.mParcelableData); + builder.append(" ]"); + builder.append("\n"); + } + } else { + builder.append("; recordCount: ").append(getAddedCount()); } - builder.append("}\n"); return builder.toString(); } diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 88f8878..eece64a 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -28,10 +28,13 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.util.Log; +import android.view.IWindow; +import android.view.View; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; /** * System level service that serves as an event dispatch for {@link AccessibilityEvent}s. @@ -62,6 +65,21 @@ public final class AccessibilityManager { boolean mIsEnabled; + final CopyOnWriteArrayList<AccessibilityStateChangeListener> mAccessibilityStateChangeListeners = + new CopyOnWriteArrayList<AccessibilityStateChangeListener>(); + + /** + * Listener for the accessibility state. + */ + public interface AccessibilityStateChangeListener { + /** + * Called back on change in the accessibility state. + * + * @param enabled + */ + public void onAccessibilityStateChanged(boolean enabled); + } + final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() { public void setEnabled(boolean enabled) { mHandler.obtainMessage(DO_SET_ENABLED, enabled ? 1 : 0, 0).sendToTarget(); @@ -78,9 +96,8 @@ public final class AccessibilityManager { public void handleMessage(Message message) { switch (message.what) { case DO_SET_ENABLED : - synchronized (mHandler) { - mIsEnabled = (message.arg1 == 1); - } + final boolean isEnabled = (message.arg1 == 1); + setAccessibilityState(isEnabled); return; default : Log.w(LOG_TAG, "Unknown message type: " + message.what); @@ -117,7 +134,8 @@ public final class AccessibilityManager { mService = service; try { - mIsEnabled = mService.addClient(mClient); + final boolean isEnabled = mService.addClient(mClient); + setAccessibilityState(isEnabled); } catch (RemoteException re) { Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); } @@ -253,4 +271,81 @@ public final class AccessibilityManager { } return Collections.unmodifiableList(services); } + + /** + * Registers an {@link AccessibilityStateChangeListener}. + * + * @param listener The listener. + * @return True if successfully registered. + */ + public boolean addAccessibilityStateChangeListener( + AccessibilityStateChangeListener listener) { + return mAccessibilityStateChangeListeners.add(listener); + } + + /** + * Unregisters an {@link AccessibilityStateChangeListener}. + * + * @param listener The listener. + * @return True if successfully unregistered. + */ + public boolean removeAccessibilityStateChangeListener( + AccessibilityStateChangeListener listener) { + return mAccessibilityStateChangeListeners.remove(listener); + } + + /** + * Sets the enabled state. + * + * @param isEnabled The accessibility state. + */ + private void setAccessibilityState(boolean isEnabled) { + synchronized (mHandler) { + if (isEnabled != mIsEnabled) { + mIsEnabled = isEnabled; + notifyAccessibilityStateChanged(); + } + } + } + + /** + * Notifies the registered {@link AccessibilityStateChangeListener}s. + */ + private void notifyAccessibilityStateChanged() { + final int listenerCount = mAccessibilityStateChangeListeners.size(); + for (int i = 0; i < listenerCount; i++) { + mAccessibilityStateChangeListeners.get(i).onAccessibilityStateChanged(mIsEnabled); + } + } + + /** + * Adds an accessibility interaction connection interface for a given window. + * @param windowToken The window token to which a connection is added. + * @param connection The connection. + * + * @hide + */ + public int addAccessibilityInteractionConnection(IWindow windowToken, + IAccessibilityInteractionConnection connection) { + try { + return mService.addAccessibilityInteractionConnection(windowToken, connection); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re); + } + return View.NO_ID; + } + + /** + * Removed an accessibility interaction connection interface for a given window. + * @param windowToken The window token to which a connection is removed. + * + * @hide + */ + public void removeAccessibilityInteractionConnection(IWindow windowToken) { + try { + mService.removeAccessibilityInteractionConnection(windowToken); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while removing an accessibility interaction connection. ", re); + } + } } diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.aidl b/core/java/android/view/accessibility/AccessibilityNodeInfo.aidl new file mode 100644 index 0000000..59175ce --- /dev/null +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.aidl @@ -0,0 +1,19 @@ +/** + * 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.view.accessibility; + +parcelable AccessibilityNodeInfo; diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java new file mode 100644 index 0000000..752f864 --- /dev/null +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -0,0 +1,947 @@ +/* + * 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.view.accessibility; + +import android.accessibilityservice.IAccessibilityServiceConnection; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.SparseArray; +import android.util.SparseIntArray; +import android.view.View; + +/** + * This class represents a node of the screen content. From the point of + * view of an accessibility service the screen content is presented as tree + * of accessibility nodes. + * + * TODO(svertoslavganov): Update the documentation, add sample, and describe + * the security policy. + */ +public class AccessibilityNodeInfo implements Parcelable { + + private static final boolean DEBUG = false; + + // Actions. + + /** + * Action that focuses the node. + */ + public static final int ACTION_FOCUS = 0x00000001; + + /** + * Action that unfocuses the node. + */ + public static final int ACTION_CLEAR_FOCUS = 0x00000002; + + /** + * Action that selects the node. + */ + public static final int ACTION_SELECT = 0x00000004; + + /** + * Action that unselects the node. + */ + public static final int ACTION_CLEAR_SELECTION = 0x00000008; + + // Boolean attributes. + + private static final int PROPERTY_CHECKABLE = 0x00000001; + + private static final int PROPERTY_CHECKED = 0x00000002; + + private static final int PROPERTY_FOCUSABLE = 0x00000004; + + private static final int PROPERTY_FOCUSED = 0x00000008; + + private static final int PROPERTY_SELECTED = 0x00000010; + + private static final int PROPERTY_CLICKABLE = 0x00000020; + + private static final int PROPERTY_LONG_CLICKABLE = 0x00000040; + + private static final int PROPERTY_ENABLED = 0x00000080; + + private static final int PROPERTY_PASSWORD = 0x00000100; + + // Readable representations - lazily initialized. + private static SparseArray<String> sActionSymbolicNames; + + // Housekeeping. + private static final int MAX_POOL_SIZE = 50; + private static final Object sPoolLock = new Object(); + private static AccessibilityNodeInfo sPool; + private static int sPoolSize; + private AccessibilityNodeInfo mNext; + private boolean mIsInPool; + private boolean mSealed; + + // Data. + private int mAccessibilityViewId; + private int mAccessibilityWindowId; + private int mParentAccessibilityViewId; + private int mBooleanProperties; + private final Rect mBounds = new Rect(); + + private CharSequence mPackageName; + private CharSequence mClassName; + private CharSequence mText; + private CharSequence mContentDescription; + + private final SparseIntArray mChildAccessibilityIds = new SparseIntArray(); + private int mActions; + + private IAccessibilityServiceConnection mConnection; + + /** + * Hide constructor from clients. + */ + private AccessibilityNodeInfo() { + /* do nothing */ + } + + /** + * Sets the source. + * + * @param source The info source. + */ + public void setSource(View source) { + enforceNotSealed(); + mAccessibilityViewId = source.getAccessibilityViewId(); + mAccessibilityWindowId = source.getAccessibilityWindowId(); + } + + /** + * Gets the id of the window from which the info comes from. + * + * @return The window id. + */ + public int getAccessibilityWindowId() { + return mAccessibilityWindowId; + } + + /** + * Gets the number of children. + * + * @return The child count. + */ + public int getChildCount() { + return mChildAccessibilityIds.size(); + } + + /** + * Get the child at given index. + * <p> + * <strong> + * It is a client responsibility to recycle the received info by + * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating + * of multiple instances. + * </strong> + * </p> + * @param index The child index. + * @return The child node. + * + * @throws IllegalStateException If called outside of an AccessibilityService. + * + */ + public AccessibilityNodeInfo getChild(int index) { + enforceSealed(); + final int childAccessibilityViewId = mChildAccessibilityIds.get(index); + try { + return mConnection.findAccessibilityNodeInfoByAccessibilityId(mAccessibilityWindowId, + childAccessibilityViewId); + } catch (RemoteException e) { + return null; + } + } + + /** + * Adds a child. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param child The child. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void addChild(View child) { + enforceNotSealed(); + final int childAccessibilityViewId = child.getAccessibilityViewId(); + final int index = mChildAccessibilityIds.size(); + mChildAccessibilityIds.put(index, childAccessibilityViewId); + } + + /** + * Gets the actions that can be performed on the node. + * + * @return The bit mask of with actions. + * + * @see AccessibilityNodeInfo#ACTION_FOCUS + * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS + * @see AccessibilityNodeInfo#ACTION_SELECT + * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION + */ + public int getActions() { + return mActions; + } + + /** + * Adds an action that can be performed on the node. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param action The action. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void addAction(int action) { + enforceNotSealed(); + mActions |= action; + } + + /** + * Performs an action on the node. + * <p> + * Note: An action can be performed only if the request is made + * from an {@link android.accessibilityservice.AccessibilityService}. + * </p> + * @param action The action to perform. + * @return True if the action was performed. + * + * @throws IllegalStateException If called outside of an AccessibilityService. + */ + public boolean performAction(int action) { + enforceSealed(); + try { + return mConnection.performAccessibilityAction(mAccessibilityWindowId, + mAccessibilityViewId, action); + } catch (RemoteException e) { + return false; + } + } + + /** + * Gets the unique id identifying this node's parent. + * <p> + * <strong> + * It is a client responsibility to recycle the received info by + * calling {@link AccessibilityNodeInfo#recycle()} to avoid creating + * of multiple instances. + * </strong> + * </p> + * @return The node's patent id. + */ + public AccessibilityNodeInfo getParent() { + enforceSealed(); + try { + return mConnection.findAccessibilityNodeInfoByAccessibilityId(mAccessibilityWindowId, + mParentAccessibilityViewId); + } catch (RemoteException e) { + return null; + } + } + + /** + * Sets the parent. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param parent The parent. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setParent(View parent) { + enforceNotSealed(); + mParentAccessibilityViewId = parent.getAccessibilityViewId(); + } + + /** + * Gets the node bounds in parent coordinates. + * + * @param outBounds The output node bounds. + */ + public void getBounds(Rect outBounds) { + outBounds.set(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom); + } + + /** + * Sets the node bounds in parent coordinates. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param bounds The node bounds. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setBounds(Rect bounds) { + enforceNotSealed(); + mBounds.set(bounds.left, bounds.top, bounds.right, bounds.bottom); + } + + /** + * Gets whether this node is checkable. + * + * @return True if the node is checkable. + */ + public boolean isCheckable() { + return getBooleanProperty(PROPERTY_CHECKABLE); + } + + /** + * Sets whether this node is checkable. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param checkable True if the node is checkable. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setCheckable(boolean checkable) { + setBooleanProperty(PROPERTY_CHECKABLE, checkable); + } + + /** + * Gets whether this node is checked. + * + * @return True if the node is checked. + */ + public boolean isChecked() { + return getBooleanProperty(PROPERTY_CHECKED); + } + + /** + * Sets whether this node is checked. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param checked True if the node is checked. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setChecked(boolean checked) { + setBooleanProperty(PROPERTY_CHECKED, checked); + } + + /** + * Gets whether this node is focusable. + * + * @return True if the node is focusable. + */ + public boolean isFocusable() { + return getBooleanProperty(PROPERTY_FOCUSABLE); + } + + /** + * Sets whether this node is focusable. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param focusable True if the node is focusable. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setFocusable(boolean focusable) { + setBooleanProperty(PROPERTY_FOCUSABLE, focusable); + } + + /** + * Gets whether this node is focused. + * + * @return True if the node is focused. + */ + public boolean isFocused() { + return getBooleanProperty(PROPERTY_FOCUSED); + } + + /** + * Sets whether this node is focused. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param focused True if the node is focused. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setFocused(boolean focused) { + setBooleanProperty(PROPERTY_FOCUSED, focused); + } + + /** + * Gets whether this node is selected. + * + * @return True if the node is selected. + */ + public boolean isSelected() { + return getBooleanProperty(PROPERTY_SELECTED); + } + + /** + * Sets whether this node is selected. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param selected True if the node is selected. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setSelected(boolean selected) { + setBooleanProperty(PROPERTY_SELECTED, selected); + } + + /** + * Gets whether this node is clickable. + * + * @return True if the node is clickable. + */ + public boolean isClickable() { + return getBooleanProperty(PROPERTY_CLICKABLE); + } + + /** + * Sets whether this node is clickable. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param clickable True if the node is clickable. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setClickable(boolean clickable) { + setBooleanProperty(PROPERTY_CLICKABLE, clickable); + } + + /** + * Gets whether this node is long clickable. + * + * @return True if the node is long clickable. + */ + public boolean isLongClickable() { + return getBooleanProperty(PROPERTY_LONG_CLICKABLE); + } + + /** + * Sets whether this node is long clickable. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param longClickable True if the node is long clickable. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setLongClickable(boolean longClickable) { + setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable); + } + + /** + * Gets whether this node is enabled. + * + * @return True if the node is enabled. + */ + public boolean isEnabled() { + return getBooleanProperty(PROPERTY_ENABLED); + } + + /** + * Sets whether this node is enabled. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param enabled True if the node is enabled. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setEnabled(boolean enabled) { + setBooleanProperty(PROPERTY_ENABLED, enabled); + } + + /** + * Gets whether this node is a password. + * + * @return True if the node is a password. + */ + public boolean isPassword() { + return getBooleanProperty(PROPERTY_PASSWORD); + } + + /** + * Sets whether this node is a password. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param password True if the node is a password. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setPassword(boolean password) { + setBooleanProperty(PROPERTY_PASSWORD, password); + } + + /** + * Gets the package this node comes from. + * + * @return The package name. + */ + public CharSequence getPackageName() { + return mPackageName; + } + + /** + * Sets the package this node comes from. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param packageName The package name. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setPackageName(CharSequence packageName) { + enforceNotSealed(); + mPackageName = packageName; + } + + /** + * Gets the class this node comes from. + * + * @return The class name. + */ + public CharSequence getClassName() { + return mClassName; + } + + /** + * Sets the class this node comes from. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param className The class name. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setClassName(CharSequence className) { + enforceNotSealed(); + mClassName = className; + } + + /** + * Gets the text of this node. + * + * @return The text. + */ + public CharSequence getText() { + return mText; + } + + /** + * Sets the text of this node. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param text The text. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setText(CharSequence text) { + enforceNotSealed(); + mText = text; + } + + /** + * Gets the content description of this node. + * + * @return The content description. + */ + public CharSequence getContentDescription() { + return mContentDescription; + } + + /** + * Sets the content description of this node. + * <p> + * Note: Cannot be called from an {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * @param contentDescription The content description. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setContentDescription(CharSequence contentDescription) { + enforceNotSealed(); + mContentDescription = contentDescription; + } + + /** + * Gets the value of a boolean property. + * + * @param property The property. + * @return The value. + */ + private boolean getBooleanProperty(int property) { + return (mBooleanProperties & property) != 0; + } + + /** + * Sets a boolean property. + * + * @param property The property. + * @param value The value. + * + * @throws IllegalStateException If called from an AccessibilityService. + */ + private void setBooleanProperty(int property, boolean value) { + enforceNotSealed(); + if (value) { + mBooleanProperties |= property; + } else { + mBooleanProperties &= ~property; + } + } + + /** + * Sets the connection for interacting with the system. + * + * @param connection The client token. + * + * @hide + */ + public final void setConnection(IAccessibilityServiceConnection connection) { + mConnection = connection; + } + + /** + * {@inheritDoc} + */ + public int describeContents() { + return 0; + } + + /** + * Sets if this instance is sealed. + * + * @param sealed Whether is sealed. + * + * @hide + */ + public void setSealed(boolean sealed) { + mSealed = sealed; + } + + /** + * Gets if this instance is sealed. + * + * @return Whether is sealed. + * + * @hide + */ + public boolean isSealed() { + return mSealed; + } + + /** + * Enforces that this instance is sealed. + * + * @throws IllegalStateException If this instance is not sealed. + * + * @hide + */ + protected void enforceSealed() { + if (!isSealed()) { + throw new IllegalStateException("Cannot perform this " + + "action on a not sealed instance."); + } + } + + /** + * Enforces that this instance is not sealed. + * + * @throws IllegalStateException If this instance is sealed. + * + * @hide + */ + protected void enforceNotSealed() { + if (isSealed()) { + throw new IllegalStateException("Cannot perform this " + + "action on an sealed instance."); + } + } + + /** + * Returns a cached instance if such is available otherwise a new one + * and sets the source. + * + * @return An instance. + * + * @see #setSource(View) + */ + public static AccessibilityNodeInfo obtain(View source) { + AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); + info.setSource(source); + return info; + } + + /** + * Returns a cached instance if such is available otherwise a new one. + * + * @return An instance. + */ + public static AccessibilityNodeInfo obtain() { + synchronized (sPoolLock) { + if (sPool != null) { + AccessibilityNodeInfo info = sPool; + sPool = sPool.mNext; + sPoolSize--; + info.mNext = null; + info.mIsInPool = false; + } + return new AccessibilityNodeInfo(); + } + } + + /** + * Return an instance back to be reused. + * <p> + * <b>Note: You must not touch the object after calling this function.</b> + * + * @throws IllegalStateException If the info is already recycled. + */ + public void recycle() { + if (mIsInPool) { + throw new IllegalStateException("Info already recycled!"); + } + clear(); + synchronized (sPoolLock) { + if (sPoolSize <= MAX_POOL_SIZE) { + mNext = sPool; + sPool = this; + mIsInPool = true; + sPoolSize++; + } + } + } + + /** + * {@inheritDoc} + * <p> + * <b>Note: After the instance is written to a parcel it is recycled. + * You must not touch the object after calling this function.</b> + * </p> + */ + public void writeToParcel(Parcel parcel, int flags) { + if (mConnection == null) { + parcel.writeInt(0); + } else { + parcel.writeInt(1); + parcel.writeStrongBinder(mConnection.asBinder()); + } + parcel.writeInt(isSealed() ? 1 : 0); + parcel.writeInt(mAccessibilityViewId); + parcel.writeInt(mAccessibilityWindowId); + parcel.writeInt(mParentAccessibilityViewId); + + SparseIntArray childIds = mChildAccessibilityIds; + final int childIdsSize = childIds.size(); + parcel.writeInt(childIdsSize); + for (int i = 0; i < childIdsSize; i++) { + parcel.writeInt(childIds.valueAt(i)); + } + + parcel.writeInt(mBounds.top); + parcel.writeInt(mBounds.bottom); + parcel.writeInt(mBounds.left); + parcel.writeInt(mBounds.right); + + parcel.writeInt(mActions); + + parcel.writeInt(mBooleanProperties); + + TextUtils.writeToParcel(mPackageName, parcel, flags); + TextUtils.writeToParcel(mClassName, parcel, flags); + TextUtils.writeToParcel(mText, parcel, flags); + TextUtils.writeToParcel(mContentDescription, parcel, flags); + + // Since instances of this class are fetched via synchronous i.e. blocking + // calls in IPCs and we always recycle as soon as the instance is marshaled. + recycle(); + } + + /** + * Creates a new instance from a {@link Parcel}. + * + * @param parcel A parcel containing the state of a {@link AccessibilityNodeInfo}. + */ + private void initFromParcel(Parcel parcel) { + if (parcel.readInt() == 1) { + mConnection = IAccessibilityServiceConnection.Stub.asInterface( + parcel.readStrongBinder()); + } + mSealed = (parcel.readInt() == 1); + mAccessibilityViewId = parcel.readInt(); + mAccessibilityWindowId = parcel.readInt(); + mParentAccessibilityViewId = parcel.readInt(); + + SparseIntArray childIds = mChildAccessibilityIds; + final int childrenSize = parcel.readInt(); + for (int i = 0; i < childrenSize; i++) { + final int childId = parcel.readInt(); + childIds.put(i, childId); + } + + mBounds.top = parcel.readInt(); + mBounds.bottom = parcel.readInt(); + mBounds.left = parcel.readInt(); + mBounds.right = parcel.readInt(); + + mActions = parcel.readInt(); + + mBooleanProperties = parcel.readInt(); + + mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); + mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); + mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); + mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); + } + + /** + * Clears the state of this instance. + */ + private void clear() { + mSealed = false; + mConnection = null; + mAccessibilityViewId = View.NO_ID; + mParentAccessibilityViewId = View.NO_ID; + mChildAccessibilityIds.clear(); + mBounds.set(0, 0, 0, 0); + mBooleanProperties = 0; + mPackageName = null; + mClassName = null; + mText = null; + mContentDescription = null; + mActions = 0; + } + + /** + * Gets the human readable action symbolic name. + * + * @param action The action. + * @return The symbolic name. + */ + private static String getActionSymbolicName(int action) { + SparseArray<String> actionSymbolicNames = sActionSymbolicNames; + if (actionSymbolicNames == null) { + actionSymbolicNames = sActionSymbolicNames = new SparseArray<String>(); + actionSymbolicNames.put(ACTION_FOCUS, "ACTION_FOCUS"); + actionSymbolicNames.put(ACTION_CLEAR_FOCUS, "ACTION_UNFOCUS"); + actionSymbolicNames.put(ACTION_SELECT, "ACTION_SELECT"); + actionSymbolicNames.put(ACTION_CLEAR_SELECTION, "ACTION_UNSELECT"); + } + return actionSymbolicNames.get(action); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mAccessibilityViewId; + result = prime * result + mAccessibilityWindowId; + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(super.toString()); + + if (DEBUG) { + builder.append("; accessibilityId: " + mAccessibilityViewId); + builder.append("; parentAccessibilityId: " + mParentAccessibilityViewId); + SparseIntArray childIds = mChildAccessibilityIds; + builder.append("; childAccessibilityIds: ["); + for (int i = 0, count = childIds.size(); i < count; i++) { + builder.append(childIds.valueAt(i)); + if (i < count - 1) { + builder.append(", "); + } + } + builder.append("]"); + } + + builder.append("; bounds: " + mBounds); + + builder.append("; packageName: ").append(mPackageName); + builder.append("; className: ").append(mClassName); + builder.append("; text: ").append(mText); + builder.append("; contentDescription: ").append(mContentDescription); + + builder.append("; checkable: ").append(isCheckable()); + builder.append("; checked: ").append(isChecked()); + builder.append("; focusable: ").append(isFocusable()); + builder.append("; focused: ").append(isFocused()); + builder.append("; selected: ").append(isSelected()); + builder.append("; clickable: ").append(isClickable()); + builder.append("; longClickable: ").append(isLongClickable()); + builder.append("; enabled: ").append(isEnabled()); + builder.append("; password: ").append(isPassword()); + + builder.append("; ["); + + for (int actionBits = mActions; actionBits != 0;) { + final int action = 1 << Integer.numberOfTrailingZeros(actionBits); + actionBits &= ~action; + builder.append(getActionSymbolicName(action)); + if (actionBits != 0) { + builder.append(", "); + } + } + + builder.append("]"); + + return builder.toString(); + } + + /** + * @see Parcelable.Creator + */ + public static final Parcelable.Creator<AccessibilityNodeInfo> CREATOR = + new Parcelable.Creator<AccessibilityNodeInfo>() { + public AccessibilityNodeInfo createFromParcel(Parcel parcel) { + AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); + info.initFromParcel(parcel); + return info; + } + + public AccessibilityNodeInfo[] newArray(int size) { + return new AccessibilityNodeInfo[size]; + } + }; +} diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index 7819b17..4bf03a7 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -38,13 +38,14 @@ public class AccessibilityRecord { private static final int PROPERTY_PASSWORD = 0x00000004; private static final int PROPERTY_FULL_SCREEN = 0x00000080; + // Housekeeping private static final int MAX_POOL_SIZE = 10; private static final Object sPoolLock = new Object(); private static AccessibilityRecord sPool; private static int sPoolSize; - private AccessibilityRecord mNext; private boolean mIsInPool; + private boolean mSealed; protected int mBooleanProperties; protected int mCurrentItemIndex; @@ -68,6 +69,26 @@ public class AccessibilityRecord { } /** + * Initialize this record from another one. + * + * @param record The to initialize from. + */ + void init(AccessibilityRecord record) { + mSealed = record.isSealed(); + mBooleanProperties = record.mBooleanProperties; + mCurrentItemIndex = record.mCurrentItemIndex; + mItemCount = record.mItemCount; + mFromIndex = record.mFromIndex; + mAddedCount = record.mAddedCount; + mRemovedCount = record.mRemovedCount; + mClassName = record.mClassName; + mContentDescription = record.mContentDescription; + mBeforeText = record.mBeforeText; + mParcelableData = record.mParcelableData; + mText.addAll(record.mText); + } + + /** * Gets if the source is checked. * * @return True if the view is checked, false otherwise. @@ -80,8 +101,11 @@ public class AccessibilityRecord { * Sets if the source is checked. * * @param isChecked True if the view is checked, false otherwise. + * + * @throws IllegalStateException If called from an AccessibilityService. */ public void setChecked(boolean isChecked) { + enforceNotSealed(); setBooleanProperty(PROPERTY_CHECKED, isChecked); } @@ -98,8 +122,11 @@ public class AccessibilityRecord { * Sets if the source is enabled. * * @param isEnabled True if the view is enabled, false otherwise. + * + * @throws IllegalStateException If called from an AccessibilityService. */ public void setEnabled(boolean isEnabled) { + enforceNotSealed(); setBooleanProperty(PROPERTY_ENABLED, isEnabled); } @@ -116,27 +143,33 @@ public class AccessibilityRecord { * Sets if the source is a password field. * * @param isPassword True if the view is a password field, false otherwise. + * + * @throws IllegalStateException If called from an AccessibilityService. */ public void setPassword(boolean isPassword) { + enforceNotSealed(); setBooleanProperty(PROPERTY_PASSWORD, isPassword); } /** - * Sets if the source is taking the entire screen. + * Gets if the source is taking the entire screen. * - * @param isFullScreen True if the source is full screen, false otherwise. + * @return True if the source is full screen, false otherwise. */ - public void setFullScreen(boolean isFullScreen) { - setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen); + public boolean isFullScreen() { + return getBooleanProperty(PROPERTY_FULL_SCREEN); } /** - * Gets if the source is taking the entire screen. + * Sets if the source is taking the entire screen. * - * @return True if the source is full screen, false otherwise. + * @param isFullScreen True if the source is full screen, false otherwise. + * + * @throws IllegalStateException If called from an AccessibilityService. */ - public boolean isFullScreen() { - return getBooleanProperty(PROPERTY_FULL_SCREEN); + public void setFullScreen(boolean isFullScreen) { + enforceNotSealed(); + setBooleanProperty(PROPERTY_FULL_SCREEN, isFullScreen); } /** @@ -152,8 +185,11 @@ public class AccessibilityRecord { * Sets the number of items that can be visited. * * @param itemCount The number of items. + * + * @throws IllegalStateException If called from an AccessibilityService. */ public void setItemCount(int itemCount) { + enforceNotSealed(); mItemCount = itemCount; } @@ -170,8 +206,11 @@ public class AccessibilityRecord { * Sets the index of the source in the list of items that can be visited. * * @param currentItemIndex The current item index. + * + * @throws IllegalStateException If called from an AccessibilityService. */ public void setCurrentItemIndex(int currentItemIndex) { + enforceNotSealed(); mCurrentItemIndex = currentItemIndex; } @@ -188,8 +227,11 @@ public class AccessibilityRecord { * Sets the index of the first character of the changed sequence. * * @param fromIndex The index of the first character. + * + * @throws IllegalStateException If called from an AccessibilityService. */ public void setFromIndex(int fromIndex) { + enforceNotSealed(); mFromIndex = fromIndex; } @@ -206,8 +248,11 @@ public class AccessibilityRecord { * Sets the number of added characters. * * @param addedCount The number of added characters. + * + * @throws IllegalStateException If called from an AccessibilityService. */ public void setAddedCount(int addedCount) { + enforceNotSealed(); mAddedCount = addedCount; } @@ -224,8 +269,11 @@ public class AccessibilityRecord { * Sets the number of removed characters. * * @param removedCount The number of removed characters. + * + * @throws IllegalStateException If called from an AccessibilityService. */ public void setRemovedCount(int removedCount) { + enforceNotSealed(); mRemovedCount = removedCount; } @@ -242,8 +290,11 @@ public class AccessibilityRecord { * Sets the class name of the source. * * @param className The lass name. + * + * @throws IllegalStateException If called from an AccessibilityService. */ public void setClassName(CharSequence className) { + enforceNotSealed(); mClassName = className; } @@ -270,8 +321,11 @@ public class AccessibilityRecord { * Sets the text before a change. * * @param beforeText The text before the change. + * + * @throws IllegalStateException If called from an AccessibilityService. */ public void setBeforeText(CharSequence beforeText) { + enforceNotSealed(); mBeforeText = beforeText; } @@ -288,8 +342,11 @@ public class AccessibilityRecord { * Sets the description of the source. * * @param contentDescription The description. + * + * @throws IllegalStateException If called from an AccessibilityService. */ public void setContentDescription(CharSequence contentDescription) { + enforceNotSealed(); mContentDescription = contentDescription; } @@ -306,18 +363,71 @@ public class AccessibilityRecord { * Sets the {@link Parcelable} data of the event. * * @param parcelableData The parcelable data. + * + * @throws IllegalStateException If called from an AccessibilityService. */ public void setParcelableData(Parcelable parcelableData) { + enforceNotSealed(); mParcelableData = parcelableData; } /** + * Sets if this instance is sealed. + * + * @param sealed Whether is sealed. + * + * @hide + */ + public void setSealed(boolean sealed) { + mSealed = sealed; + } + + /** + * Gets if this instance is sealed. + * + * @return Whether is sealed. + * + * @hide + */ + public boolean isSealed() { + return mSealed; + } + + /** + * Enforces that this instance is sealed. + * + * @throws IllegalStateException If this instance is not sealed. + * + * @hide + */ + protected void enforceSealed() { + if (!isSealed()) { + throw new IllegalStateException("Cannot perform this " + + "action on a not sealed instance."); + } + } + + /** + * Enforces that this instance is not sealed. + * + * @throws IllegalStateException If this instance is sealed. + * + * @hide + */ + protected void enforceNotSealed() { + if (isSealed()) { + throw new IllegalStateException("Cannot perform this " + + "action on an sealed instance."); + } + } + + /** * Gets the value of a boolean property. * * @param property The property. * @return The value. */ - public boolean getBooleanProperty(int property) { + private boolean getBooleanProperty(int property) { return (mBooleanProperties & property) == property; } @@ -337,6 +447,19 @@ public class AccessibilityRecord { /** * Returns a cached instance if such is available or a new one is + * instantiated. The instance is initialized with data from the + * given record. + * + * @return An instance. + */ + public static AccessibilityRecord obtain(AccessibilityRecord record) { + AccessibilityRecord clone = AccessibilityRecord.obtain(); + clone.init(record); + return clone; + } + + /** + * Returns a cached instance if such is available or a new one is * instantiated. * * @return An instance. @@ -379,8 +502,11 @@ public class AccessibilityRecord { /** * Clears the state of this instance. + * + * @hide */ protected void clear() { + mSealed = false; mBooleanProperties = 0; mCurrentItemIndex = INVALID_POSITION; mItemCount = 0; diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl new file mode 100644 index 0000000..77dcd07 --- /dev/null +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl @@ -0,0 +1,41 @@ +/* + * 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.view.accessibility; + +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.IAccessibilityInteractionConnectionCallback; + +/** + * Interface for interaction between the AccessibilityManagerService + * and the ViewRoot in a given window. + * + * @hide + */ +oneway interface IAccessibilityInteractionConnection { + + void findAccessibilityNodeInfoByAccessibilityId(int accessibilityViewId, int interactionId, + IAccessibilityInteractionConnectionCallback callback); + + void findAccessibilityNodeInfoByViewId(int id, int interactionId, + IAccessibilityInteractionConnectionCallback callback); + + void findAccessibilityNodeInfosByViewText(String text, int interactionId, + IAccessibilityInteractionConnectionCallback callback); + + void performAccessibilityAction(int accessibilityId, int action, int interactionId, + IAccessibilityInteractionConnectionCallback callback); +} diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl new file mode 100644 index 0000000..9c5e8dc --- /dev/null +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl @@ -0,0 +1,36 @@ +/* + * 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.view.accessibility; + +import android.view.accessibility.AccessibilityNodeInfo; +import java.util.List; + +/** + * Callback for specifying the result for an asynchronous request made + * via calling a method on IAccessibilityInteractionConnectionCallback. + * + * @hide + */ +oneway interface IAccessibilityInteractionConnectionCallback { + + void setFindAccessibilityNodeInfoResult(in AccessibilityNodeInfo info, int interactionId); + + void setFindAccessibilityNodeInfosResult(in List<AccessibilityNodeInfo> infos, + int interactionId); + + void setPerformAccessibilityActionResult(boolean succeeded, int interactionId); +} diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 6b2aae2..b14f02a 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -18,8 +18,13 @@ package android.view.accessibility; import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.IAccessibilityServiceConnection; +import android.accessibilityservice.IEventListener; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.accessibility.IAccessibilityManagerClient; +import android.view.IWindow; /** * Interface implemented by the AccessibilityManagerService called by @@ -38,4 +43,11 @@ interface IAccessibilityManager { List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType); void interrupt(); + + int addAccessibilityInteractionConnection(IWindow windowToken, + in IAccessibilityInteractionConnection connection); + + void removeAccessibilityInteractionConnection(IWindow windowToken); + + IAccessibilityServiceConnection registerEventListener(IEventListener client); } diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java index 5562bf7..a4771d5 100644 --- a/core/java/android/widget/RelativeLayout.java +++ b/core/java/android/widget/RelativeLayout.java @@ -1441,6 +1441,7 @@ public class RelativeLayout extends ViewGroup { ); private Node mNext; + private boolean mIsPooled; public void setNextPoolable(Node element) { mNext = element; @@ -1450,6 +1451,14 @@ public class RelativeLayout extends ViewGroup { return mNext; } + public boolean isPooled() { + return mIsPooled; + } + + public void setPooled(boolean isPooled) { + mIsPooled = isPooled; + } + static Node acquire(View view) { final Node node = sPool.acquire(); node.view = view; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 1d123f6..eba9d37 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -113,6 +113,7 @@ import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AnimationUtils; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; @@ -7647,7 +7648,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener protected int computeVerticalScrollExtent() { return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); } - + + @Override + public void findViewsWithText(ArrayList<View> outViews, CharSequence text) { + CharSequence thisText = getText(); + if (TextUtils.isEmpty(thisText)) { + return; + } + if (thisText.toString().toLowerCase().contains(text)) { + outViews.add(this); + } + } + public enum BufferType { NORMAL, SPANNABLE, EDITABLE, } @@ -7899,6 +7911,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener event.setPassword(isPassword); } + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + + final boolean isPassword = hasPasswordTransformationMethod(); + if (!isPassword) { + info.setText(getText()); + } + info.setPassword(isPassword); + } + void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount) { AccessibilityEvent event = diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index c41b2cb..b9948fe 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -16,8 +16,6 @@ package com.android.internal.view; -import android.content.ClipData; -import android.content.ClipDescription; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; @@ -26,8 +24,6 @@ import android.os.RemoteException; import android.view.DragEvent; import android.view.IWindow; import android.view.IWindowSession; -import android.view.KeyEvent; -import android.view.MotionEvent; public class BaseIWindow extends IWindow.Stub { private IWindowSession mSession; |