diff options
| author | svetoslavganov <svetoslavganov@google.com> | 2009-05-14 22:28:01 -0700 |
|---|---|---|
| committer | svetoslavganov <svetoslavganov@google.com> | 2009-05-14 23:47:05 -0700 |
| commit | 75986cf9bc57ef11ad70f36fb77fbbf5d63af6ec (patch) | |
| tree | 84e1843368037d24f83749d152f818d537267bfa /core/java/android/view | |
| parent | 669ec3a6e47248fee0a3a0f4877b46875eb42140 (diff) | |
| download | frameworks_base-75986cf9bc57ef11ad70f36fb77fbbf5d63af6ec.zip frameworks_base-75986cf9bc57ef11ad70f36fb77fbbf5d63af6ec.tar.gz frameworks_base-75986cf9bc57ef11ad70f36fb77fbbf5d63af6ec.tar.bz2 | |
Accessibility feature - framework changes (replacing 698, 699, 700, 701 and merging with the latest Donut)
Diffstat (limited to 'core/java/android/view')
| -rw-r--r-- | core/java/android/view/View.java | 169 | ||||
| -rw-r--r-- | core/java/android/view/ViewGroup.java | 26 | ||||
| -rw-r--r-- | core/java/android/view/ViewRoot.java | 24 | ||||
| -rw-r--r-- | core/java/android/view/Window.java | 13 | ||||
| -rw-r--r-- | core/java/android/view/WindowManagerImpl.java | 1 | ||||
| -rw-r--r-- | core/java/android/view/accessibility/AccessibilityEvent.aidl | 19 | ||||
| -rw-r--r-- | core/java/android/view/accessibility/AccessibilityEvent.java | 734 | ||||
| -rw-r--r-- | core/java/android/view/accessibility/AccessibilityEventSource.java | 52 | ||||
| -rw-r--r-- | core/java/android/view/accessibility/AccessibilityManager.java | 198 | ||||
| -rw-r--r-- | core/java/android/view/accessibility/IAccessibilityManager.aidl | 39 | ||||
| -rw-r--r-- | core/java/android/view/accessibility/IAccessibilityManagerClient.aidl | 29 |
11 files changed, 1279 insertions, 25 deletions
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 16b70ed..1564fd0 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -16,6 +16,9 @@ package android.view; +import com.android.internal.R; +import com.android.internal.view.menu.MenuBuilder; + import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; @@ -25,12 +28,12 @@ import android.graphics.LinearGradient; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; +import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.Region; import android.graphics.Shader; -import android.graphics.Point; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; @@ -42,30 +45,30 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.util.AttributeSet; +import android.util.Config; import android.util.EventLog; import android.util.Log; -import android.util.SparseArray; -import android.util.Poolable; import android.util.Pool; -import android.util.Pools; +import android.util.Poolable; import android.util.PoolableManager; -import android.util.Config; +import android.util.Pools; +import android.util.SparseArray; import android.view.ContextMenu.ContextMenuInfo; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityEventSource; +import android.view.accessibility.AccessibilityManager; import android.view.animation.Animation; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.EditorInfo; import android.widget.ScrollBarDrawable; -import com.android.internal.R; -import com.android.internal.view.menu.MenuBuilder; - +import java.lang.ref.SoftReference; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.WeakHashMap; -import java.lang.ref.SoftReference; -import java.lang.reflect.Method; -import java.lang.reflect.InvocationTargetException; /** * <p> @@ -553,7 +556,7 @@ import java.lang.reflect.InvocationTargetException; * * @see android.view.ViewGroup */ -public class View implements Drawable.Callback, KeyEvent.Callback { +public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { private static final boolean DBG = false; /** @@ -851,6 +854,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback { public static final int HAPTIC_FEEDBACK_ENABLED = 0x10000000; /** + * View flag indicating whether {@link #addFocusables(ArrayList, int, int)} + * should add all focusable Views regardless if they are focusable in touch mode. + */ + public static final int FOCUSABLES_ALL = 0x00000000; + + /** + * View flag indicating whether {@link #addFocusables(ArrayList, int, int)} + * should add only Views focusable in touch mode. + */ + public static final int FOCUSABLES_TOUCH_MODE = 0x00000001; + + /** * Use with {@link #focusSearch}. Move focus to the previous selectable * item. */ @@ -1551,6 +1566,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback { protected int mPaddingBottom; /** + * Briefly describes the view and is primarily used for accessibility support. + */ + private CharSequence mContentDescription; + + /** * Cache the paddingRight set by the user to append to the scrollbar's size. */ @ViewDebug.ExportedProperty @@ -1858,6 +1878,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback { viewFlagMasks |= DRAWING_CACHE_QUALITY_MASK; } break; + case com.android.internal.R.styleable.View_contentDescription: + mContentDescription = a.getString(attr); + break; case com.android.internal.R.styleable.View_soundEffectsEnabled: if (!a.getBoolean(attr, true)) { viewFlagValues &= ~SOUND_EFFECTS_ENABLED; @@ -2255,6 +2278,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback { * otherwise is returned. */ public boolean performClick() { + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); + if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); @@ -2272,6 +2297,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback { * otherwise is returned. */ public boolean performLongClick() { + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); + boolean handled = false; if (mOnLongClickListener != null) { handled = mOnLongClickListener.onLongClick(View.this); @@ -2492,6 +2519,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback { * from (in addition to direction). Will be <code>null</code> otherwise. */ protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { + if (gainFocus) { + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + } + InputMethodManager imm = InputMethodManager.peekInstance(); if (!gainFocus) { if (isPressed()) { @@ -2514,6 +2545,79 @@ public class View implements Drawable.Callback, KeyEvent.Callback { } /** + * {@inheritDoc} + */ + public void sendAccessibilityEvent(int eventType) { + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + sendAccessibilityEventUnchecked(AccessibilityEvent.obtain(eventType)); + } + } + + /** + * {@inheritDoc} + */ + public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { + event.setClassName(getClass().getName()); + event.setPackageName(getContext().getPackageName()); + event.setEnabled(isEnabled()); + event.setContentDescription(mContentDescription); + + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && mAttachInfo != null) { + ArrayList<View> focusablesTempList = mAttachInfo.mFocusablesTempList; + getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD, FOCUSABLES_ALL); + event.setItemCount(focusablesTempList.size()); + event.setCurrentItemIndex(focusablesTempList.indexOf(this)); + focusablesTempList.clear(); + } + + dispatchPopulateAccessibilityEvent(event); + + AccessibilityManager.getInstance(mContext).sendAccessibilityEvent(event); + } + + /** + * Dispatches an {@link AccessibilityEvent} to the {@link View} children + * to be populated. + * + * @param event The event. + * + * @return True if the event population was completed. + */ + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + return false; + } + + /** + * 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 + * true for views that do not have textual representation (For example, + * ImageButton). + * + * @return The content descriptiopn. + * + * @attr ref android.R.styleable#View_contentDescription + */ + public CharSequence getContentDescription() { + return mContentDescription; + } + + /** + * Sets 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 + * true for views that do not have textual representation (For example, + * ImageButton). + * + * @param contentDescription The content description. + * + * @attr ref android.R.styleable#View_contentDescription + */ + public void setContentDescription(CharSequence contentDescription) { + mContentDescription = contentDescription; + } + + /** * Invoked whenever this view loses focus, either by losing window focus or by losing * focus within its window. This method can be used to clear any state tied to the * focus. For instance, if a button is held pressed with the trackball and the window @@ -3222,11 +3326,37 @@ public class View implements Drawable.Callback, KeyEvent.Callback { * @param direction The direction of the focus */ public void addFocusables(ArrayList<View> views, int direction) { - if (!isFocusable()) return; + addFocusables(views, direction, FOCUSABLES_TOUCH_MODE); + } - if (isInTouchMode() && !isFocusableInTouchMode()) return; + /** + * Adds any focusable views that are descendants of this view (possibly + * including this view if it is focusable itself) to views. This method + * adds all focusable views regardless if we are in touch mode or + * only views focusable in touch mode if we are in touch mode depending on + * the focusable mode paramater. + * + * @param views Focusable views found so far or null if all we are interested is + * the number of focusables. + * @param direction The direction of the focus. + * @param focusableMode The type of focusables to be added. + * + * @see #FOCUSABLES_ALL + * @see #FOCUSABLES_TOUCH_MODE + */ + public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { + if (!isFocusable()) { + return; + } - views.add(this); + if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE && + isInTouchMode() && !isFocusableInTouchMode()) { + return; + } + + if (views != null) { + views.add(this); + } } /** @@ -8378,7 +8508,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback { * calling up the hierarchy. */ final Rect mTmpInvalRect = new Rect(); - + + /** + * Temporary list for use in collecting focusable descendents of a view. + */ + final ArrayList<View> mFocusablesTempList = new ArrayList<View>(24); + /** * Creates a new set of attachment information with the specified * events handler and thread. diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index db5177f..bf04dcd 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -24,15 +24,16 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; -import android.graphics.Region; import android.graphics.RectF; +import android.graphics.Region; import android.os.Parcelable; import android.os.SystemClock; import android.util.AttributeSet; +import android.util.Config; import android.util.EventLog; import android.util.Log; import android.util.SparseArray; -import android.util.Config; +import android.view.accessibility.AccessibilityEvent; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.LayoutAnimationController; @@ -601,6 +602,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ @Override public void addFocusables(ArrayList<View> views, int direction) { + addFocusables(views, direction, FOCUSABLES_TOUCH_MODE); + } + + /** + * {@inheritDoc} + */ + @Override + public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { final int focusableCount = views.size(); final int descendantFocusability = getDescendantFocusability(); @@ -612,7 +621,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager for (int i = 0; i < count; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { - child.addFocusables(views, direction); + child.addFocusables(views, direction, focusableMode); } } } @@ -625,7 +634,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager descendantFocusability != FOCUS_AFTER_DESCENDANTS || // No focusable descendants (focusableCount == views.size())) { - super.addFocusables(views, direction); + super.addFocusables(views, direction, focusableMode); } } @@ -1020,6 +1029,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + boolean populated = false; + for (int i = 0, count = getChildCount(); i < count; i++) { + populated |= getChildAt(i).dispatchPopulateAccessibilityEvent(event); + } + return populated; + } + /** * {@inheritDoc} */ diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index dbfb194..7cd65e2 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -34,6 +34,8 @@ import android.util.Log; import android.util.EventLog; import android.util.SparseArray; import android.view.View.MeasureSpec; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.Scroller; @@ -640,6 +642,7 @@ public final class ViewRoot extends Handler implements ViewParent, host.dispatchAttachedToWindow(attachInfo, 0); getRunQueue().executeActions(attachInfo.mHandler); //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn); + } else { desiredWindowWidth = mWinFrame.width(); desiredWindowHeight = mWinFrame.height(); @@ -1723,7 +1726,7 @@ public final class ViewRoot extends Handler implements ViewParent, } mView.dispatchWindowFocusChanged(hasWindowFocus); } - + // Note: must be done after the focus change callbacks, // so all of the view state is set up correctly. if (hasWindowFocus) { @@ -1741,6 +1744,10 @@ public final class ViewRoot extends Handler implements ViewParent, ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; mHasHadWindowFocus = true; } + + if (hasWindowFocus && mView != null) { + sendAccessibilityEvents(); + } } } break; case DIE: @@ -2526,6 +2533,21 @@ public final class ViewRoot extends Handler implements ViewParent, sendMessage(msg); } + /** + * The window is getting focus so if there is anything focused/selected + * send an {@link AccessibilityEvent} to announce that. + */ + private void sendAccessibilityEvents() { + if (!AccessibilityManager.getInstance(mView.getContext()).isEnabled()) { + return; + } + mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); + View focusedView = mView.findFocus(); + if (focusedView != null && focusedView != mView) { + focusedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + } + } + public boolean showContextMenuForChild(View originalView) { return false; } diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 428de67..b0e738c 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -24,7 +24,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; -import android.util.Log; +import android.view.accessibility.AccessibilityEvent; /** * Abstract base class for a top-level window look and behavior policy. An @@ -153,7 +153,16 @@ public abstract class Window { * @return boolean Return true if this event was consumed. */ public boolean dispatchTrackballEvent(MotionEvent event); - + + /** + * Called to process population of {@link AccessibilityEvent}s. + * + * @param event The event. + * + * @return boolean Return true if event population was completed. + */ + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event); + /** * Instantiate the view to display in the panel for 'featureId'. * You can return null, in which case the default content (typically diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 755d7b8..0973599 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -173,7 +173,6 @@ public class WindowManagerImpl implements WindowManager { mRoots[index] = root; mParams[index] = wparams; } - // do this last because it fires off messages to start doing things root.setView(view, wparams, panelParentView); } diff --git a/core/java/android/view/accessibility/AccessibilityEvent.aidl b/core/java/android/view/accessibility/AccessibilityEvent.aidl new file mode 100644 index 0000000..cee3604 --- /dev/null +++ b/core/java/android/view/accessibility/AccessibilityEvent.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2009, 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 AccessibilityEvent; diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java new file mode 100644 index 0000000..c22f991 --- /dev/null +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -0,0 +1,734 @@ +/* + * Copyright (C) 2009 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.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class represents accessibility events that are sent by the system when + * something notable happens in the user interface. For example, when a + * {@link android.widget.Button} is clicked, a {@link android.view.View} is focused, etc. + * <p> + * This class represents various semantically different accessibility event + * types. Each event type has associated a set of related properties. In other + * words, each event type is characterized via a subset of the properties exposed + * by this class. For each event type there is a corresponding constant defined + * in this class. Since some event types are semantically close there are mask + * constants that group them together. Follows a specification of the event + * types and their associated properties: + * <p> + * <b>VIEW TYPES</b> <br> + * <p> + * <b>View clicked</b> - represents the event of clicking on a {@link android.view.View} + * like {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc. <br> + * Type:{@link #TYPE_VIEW_CLICKED} <br> + * Properties: + * {@link #getClassName()}, + * {@link #getPackageName()}, + * {@link #getEventTime()}, + * {@link #getText()}, + * {@link #isChecked()}, + * {@link #isEnabled()}, + * {@link #isPassword()}, + * {@link #getItemCount()}, + * {@link #getCurrentItemIndex()} + * <p> + * <b>View long clicked</b> - represents the event of long clicking on a {@link android.view.View} + * like {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc. <br> + * Type:{@link #TYPE_VIEW_LONG_CLICKED} <br> + * Properties: + * {@link #getClassName()}, + * {@link #getPackageName()}, + * {@link #getEventTime()}, + * {@link #getText()}, + * {@link #isChecked()}, + * {@link #isEnabled()}, + * {@link #isPassword()}, + * {@link #getItemCount()}, + * {@link #getCurrentItemIndex()} + * <p> + * <b>View selected</b> - represents the event of selecting an item usually in + * the context of an {@link android.widget.AdapterView}. <br> + * Type: {@link #TYPE_VIEW_SELECTED} <br> + * Properties: + * {@link #getClassName()}, + * {@link #getPackageName()}, + * {@link #getEventTime()}, + * {@link #getText()}, + * {@link #isChecked()}, + * {@link #isEnabled()}, + * {@link #isPassword()}, + * {@link #getItemCount()}, + * {@link #getCurrentItemIndex()} + * <p> + * <b>View focused</b> - represents the event of focusing a + * {@link android.view.View}. <br> + * Type: {@link #TYPE_VIEW_FOCUSED} <br> + * Properties: + * {@link #getClassName()}, + * {@link #getPackageName()}, + * {@link #getEventTime()}, + * {@link #getText()}, + * {@link #isChecked()}, + * {@link #isEnabled()}, + * {@link #isPassword()}, + * {@link #getItemCount()}, + * {@link #getCurrentItemIndex()} + * <p> + * <b>View text changed</b> - represents the event of changing the text of an + * {@link android.widget.EditText}. <br> + * Type: {@link #TYPE_VIEW_TEXT_CHANGED} <br> + * Properties: + * {@link #getClassName()}, + * {@link #getPackageName()}, + * {@link #getEventTime()}, + * {@link #getText()}, + * {@link #isChecked()}, + * {@link #isEnabled()}, + * {@link #isPassword()}, + * {@link #getItemCount()}, + * {@link #getCurrentItemIndex()}, + * {@link #getFromIndex()}, + * {@link #getAddedCount()}, + * {@link #getRemovedCount()}, + * {@link #getBeforeText()} + * <p> + * <b>TRANSITION TYPES</b> <br> + * <p> + * <b>Window state changed</b> - represents the event of opening/closing a + * {@link android.widget.PopupWindow}, {@link android.view.Menu}, + * {@link android.app.Dialog}, etc. <br> + * Type: {@link #TYPE_WINDOW_STATE_CHANGED} <br> + * Properties: + * {@link #getClassName()}, + * {@link #getPackageName()}, + * {@link #getEventTime()}, + * {@link #getText()} + * <p> + * <b>NOTIFICATION TYPES</b> <br> + * <p> + * <b>Notification state changed</b> - represents the event showing/hiding + * {@link android.app.Notification}. + * Type: {@link #TYPE_NOTIFICATION_STATE_CHANGED} <br> + * Properties: + * {@link #getClassName()}, + * {@link #getPackageName()}, + * {@link #getEventTime()}, + * {@link #getText()} + * {@link #getParcelableData()} + * <p> + * <b>Security note</b> + * <p> + * Since an event contains the text of its source privacy can be compromised by leaking of + * sensitive information such as passwords. To address this issue any event fired in response + * to manipulation of a PASSWORD field does NOT CONTAIN the text of the password. + * + * @see android.view.accessibility.AccessibilityManager + * @see android.accessibilityservice.AccessibilityService + */ +public final class AccessibilityEvent implements Parcelable { + + /** + * Invalid selection/focus position. + * + * @see #getCurrentItemIndex() + */ + public static final int INVALID_POSITION = -1; + + /** + * Maximum length of the text fields. + * + * @see #getBeforeText() + * @see #getText() + */ + public static final int MAX_TEXT_LENGTH = 500; + + /** + * Represents the event of clicking on a {@link android.view.View} like + * {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc. + */ + public static final int TYPE_VIEW_CLICKED = 0x00000001; + + /** + * Represents the event of long clicking on a {@link android.view.View} like + * {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc. + */ + public static final int TYPE_VIEW_LONG_CLICKED = 0x00000002; + + /** + * Represents the event of selecting an item usually in the context of an + * {@link android.widget.AdapterView}. + */ + public static final int TYPE_VIEW_SELECTED = 0x00000004; + + /** + * Represents the event of focusing a {@link android.view.View}. + */ + public static final int TYPE_VIEW_FOCUSED = 0x00000008; + + /** + * Represents the event of changing the text of an {@link android.widget.EditText}. + */ + public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010; + + /** + * Represents the event of opening/closing a {@link android.widget.PopupWindow}, + * {@link android.view.Menu}, {@link android.app.Dialog}, etc. + */ + public static final int TYPE_WINDOW_STATE_CHANGED = 0x00000020; + + /** + * Represents the event showing/hiding a {@link android.app.Notification}. + */ + public static final int TYPE_NOTIFICATION_STATE_CHANGED = 0x00000040; + + /** + * Mask for {@link AccessibilityEvent} all types. + * + * @see #TYPE_VIEW_CLICKED + * @see #TYPE_VIEW_LONG_CLICKED + * @see #TYPE_VIEW_SELECTED + * @see #TYPE_VIEW_FOCUSED + * @see #TYPE_VIEW_TEXT_CHANGED + * @see #TYPE_WINDOW_STATE_CHANGED + * @see #TYPE_NOTIFICATION_STATE_CHANGED + */ + public static final int TYPES_ALL_MASK = 0xFFFFFFFF; + + private static final int MAX_POOL_SIZE = 2; + private static final Object mPoolLock = new Object(); + private static AccessibilityEvent sPool; + private static int sPoolSize; + + private static final int CHECKED = 0x00000001; + private static final int ENABLED = 0x00000002; + private static final int PASSWORD = 0x00000004; + private static final int FULL_SCREEN = 0x00000080; + + private AccessibilityEvent mNext; + + private int mEventType; + private int mBooleanProperties; + private int mCurrentItemIndex; + private int mItemCount; + private int mFromIndex; + private int mAddedCount; + private int mRemovedCount; + + private long mEventTime; + + private CharSequence mClassName; + private CharSequence mPackageName; + private CharSequence mContentDescription; + private CharSequence mBeforeText; + + private Parcelable mParcelableData; + + private final List<CharSequence> mText = new ArrayList<CharSequence>(); + + private boolean mIsInPool; + + /* + * Hide constructor from clients. + */ + private AccessibilityEvent() { + mCurrentItemIndex = INVALID_POSITION; + } + + /** + * Gets if the source is checked. + * + * @return True if the view is checked, false otherwise. + */ + public boolean isChecked() { + return getBooleanProperty(CHECKED); + } + + /** + * Sets if the source is checked. + * + * @param isChecked True if the view is checked, false otherwise. + */ + public void setChecked(boolean isChecked) { + setBooleanProperty(CHECKED, isChecked); + } + + /** + * Gets if the source is enabled. + * + * @return True if the view is enabled, false otherwise. + */ + public boolean isEnabled() { + return getBooleanProperty(ENABLED); + } + + /** + * Sets if the source is enabled. + * + * @param isEnabled True if the view is enabled, false otherwise. + */ + public void setEnabled(boolean isEnabled) { + setBooleanProperty(ENABLED, isEnabled); + } + + /** + * Gets if the source is a password field. + * + * @return True if the view is a password field, false otherwise. + */ + public boolean isPassword() { + return getBooleanProperty(PASSWORD); + } + + /** + * Sets if the source is a password field. + * + * @param isPassword True if the view is a password field, false otherwise. + */ + public void setPassword(boolean isPassword) { + setBooleanProperty(PASSWORD, isPassword); + } + + /** + * Sets if the source is taking the entire screen. + * + * @param isFullScreen True if the source is full screen, false otherwise. + */ + public void setFullScreen(boolean isFullScreen) { + setBooleanProperty(FULL_SCREEN, isFullScreen); + } + + /** + * Gets if the source is taking the entire screen. + * + * @return True if the source is full screen, false otherwise. + */ + public boolean isFullScreen() { + return getBooleanProperty(FULL_SCREEN); + } + + /** + * Gets the event type. + * + * @return The event type. + */ + public int getEventType() { + return mEventType; + } + + /** + * Sets the event type. + * + * @param eventType The event type. + */ + public void setEventType(int eventType) { + mEventType = eventType; + } + + /** + * Gets the number of items that can be visited. + * + * @return The number of items. + */ + public int getItemCount() { + return mItemCount; + } + + /** + * Sets the number of items that can be visited. + * + * @param itemCount The number of items. + */ + public void setItemCount(int itemCount) { + mItemCount = itemCount; + } + + /** + * Gets the index of the source in the list of items the can be visited. + * + * @return The current item index. + */ + public int getCurrentItemIndex() { + return mCurrentItemIndex; + } + + /** + * Sets the index of the source in the list of items that can be visited. + * + * @param currentItemIndex The current item index. + */ + public void setCurrentItemIndex(int currentItemIndex) { + mCurrentItemIndex = currentItemIndex; + } + + /** + * Gets the index of the first character of the changed sequence. + * + * @return The index of the first character. + */ + public int getFromIndex() { + return mFromIndex; + } + + /** + * Sets the index of the first character of the changed sequence. + * + * @param fromIndex The index of the first character. + */ + public void setFromIndex(int fromIndex) { + mFromIndex = fromIndex; + } + + /** + * Gets the number of added characters. + * + * @return The number of added characters. + */ + public int getAddedCount() { + return mAddedCount; + } + + /** + * Sets the number of added characters. + * + * @param addedCount The number of added characters. + */ + public void setAddedCount(int addedCount) { + mAddedCount = addedCount; + } + + /** + * Gets the number of removed characters. + * + * @return The number of removed characters. + */ + public int getRemovedCount() { + return mRemovedCount; + } + + /** + * Sets the number of removed characters. + * + * @param removedCount The number of removed characters. + */ + public void setRemovedCount(int removedCount) { + mRemovedCount = removedCount; + } + + /** + * Gets the time in which this event was sent. + * + * @return The event time. + */ + public long getEventTime() { + return mEventTime; + } + + /** + * Sets the time in which this event was sent. + * + * @param eventTime The event time. + */ + public void setEventTime(long eventTime) { + mEventTime = eventTime; + } + + /** + * Gets the class name of the source. + * + * @return The class name. + */ + public CharSequence getClassName() { + return mClassName; + } + + /** + * Sets the class name of the source. + * + * @param className The lass name. + */ + public void setClassName(CharSequence className) { + mClassName = className; + } + + /** + * Gets the package name of the source. + * + * @return The package name. + */ + public CharSequence getPackageName() { + return mPackageName; + } + + /** + * Sets the package name of the source. + * + * @param packageName The package name. + */ + public void setPackageName(CharSequence packageName) { + mPackageName = packageName; + } + + /** + * Gets the text of the event. The index in the list represents the priority + * of the text. Specifically, the lower the index the higher the priority. + * + * @return The text. + */ + public List<CharSequence> getText() { + return mText; + } + + /** + * Sets the text before a change. + * + * @return The text before the change. + */ + public CharSequence getBeforeText() { + return mBeforeText; + } + + /** + * Sets the text before a change. + * + * @param beforeText The text before the change. + */ + public void setBeforeText(CharSequence beforeText) { + mBeforeText = beforeText; + } + + /** + * Gets the description of the source. + * + * @return The description. + */ + public CharSequence getContentDescription() { + return mContentDescription; + } + + /** + * Sets the description of the source. + * + * @param contentDescription The description. + */ + public void setContentDescription(CharSequence contentDescription) { + mContentDescription = contentDescription; + } + + /** + * Gets the {@link Parcelable} data. + * + * @return The parcelable data. + */ + public Parcelable getParcelableData() { + return mParcelableData; + } + + /** + * Sets the {@link Parcelable} data of the event. + * + * @param parcelableData The parcelable data. + */ + public void setParcelableData(Parcelable parcelableData) { + mParcelableData = parcelableData; + } + + /** + * Returns a cached instance if such is available or a new one is + * instantiated with type property set. + * + * @param eventType The event type. + * @return An instance. + */ + public static AccessibilityEvent obtain(int eventType) { + AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(eventType); + return event; + } + + /** + * Returns a cached instance if such is available or a new one is + * instantiated. + * + * @return An instance. + */ + public static AccessibilityEvent obtain() { + synchronized (mPoolLock) { + if (sPool != null) { + AccessibilityEvent event = sPool; + sPool = sPool.mNext; + sPoolSize--; + event.mNext = null; + event.mIsInPool = false; + return event; + } + return new AccessibilityEvent(); + } + } + + /** + * Return an instance back to be reused. + * <p> + * <b>Note: You must not touch the object after calling this function.</b> + */ + public void recycle() { + if (mIsInPool) { + return; + } + + clear(); + synchronized (mPoolLock) { + if (sPoolSize <= MAX_POOL_SIZE) { + mNext = sPool; + sPool = this; + mIsInPool = true; + sPoolSize++; + } + } + } + + /** + * Clears the state of this instance. + */ + private void clear() { + mEventType = 0; + mBooleanProperties = 0; + mCurrentItemIndex = INVALID_POSITION; + mItemCount = 0; + mFromIndex = 0; + mAddedCount = 0; + mRemovedCount = 0; + mEventTime = 0; + mClassName = null; + mPackageName = null; + mContentDescription = null; + mBeforeText = null; + mText.clear(); + } + + /** + * Gets the value of a boolean property. + * + * @param property The property. + * @return The value. + */ + private boolean getBooleanProperty(int property) { + return (mBooleanProperties & property) == property; + } + + /** + * Sets a boolean property. + * + * @param property The property. + * @param value The value. + */ + private void setBooleanProperty(int property, boolean value) { + if (value) { + mBooleanProperties |= property; + } else { + mBooleanProperties &= ~property; + } + } + + /** + * Creates a new instance from a {@link Parcel}. + * + * @param parcel A parcel containing the state of a {@link AccessibilityEvent}. + */ + public void initFromParcel(Parcel parcel) { + mEventType = parcel.readInt(); + mBooleanProperties = parcel.readInt(); + mCurrentItemIndex = parcel.readInt(); + mItemCount = parcel.readInt(); + mFromIndex = parcel.readInt(); + mAddedCount = parcel.readInt(); + mRemovedCount = parcel.readInt(); + mEventTime = parcel.readLong(); + mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); + mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); + mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); + mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); + mParcelableData = parcel.readParcelable(null); + parcel.readList(mText, null); + } + + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(mEventType); + parcel.writeInt(mBooleanProperties); + parcel.writeInt(mCurrentItemIndex); + parcel.writeInt(mItemCount); + parcel.writeInt(mFromIndex); + parcel.writeInt(mAddedCount); + parcel.writeInt(mRemovedCount); + parcel.writeLong(mEventTime); + TextUtils.writeToParcel(mClassName, parcel, 0); + TextUtils.writeToParcel(mPackageName, parcel, 0); + TextUtils.writeToParcel(mContentDescription, parcel, 0); + TextUtils.writeToParcel(mBeforeText, parcel, 0); + parcel.writeParcelable(mParcelableData, flags); + parcel.writeList(mText); + } + + public int describeContents() { + return 0; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(super.toString()); + builder.append("; EventType: " + mEventType); + builder.append("; EventTime: " + mEventTime); + builder.append("; ClassName: " + mClassName); + builder.append("; PackageName: " + mPackageName); + builder.append("; Text: " + mText); + builder.append("; ContentDescription: " + mContentDescription); + builder.append("; ItemCount: " + mItemCount); + builder.append("; CurrentItemIndex: " + mCurrentItemIndex); + builder.append("; IsEnabled: " + isEnabled()); + builder.append("; IsPassword: " + isPassword()); + builder.append("; IsChecked: " + isChecked()); + builder.append("; IsFullScreen: " + isFullScreen()); + builder.append("; BeforeText: " + mBeforeText); + builder.append("; FromIndex: " + mFromIndex); + builder.append("; AddedCount: " + mAddedCount); + builder.append("; RemovedCount: " + mRemovedCount); + builder.append("; ParcelableData: " + mParcelableData); + return builder.toString(); + } + + /** + * @see Parcelable.Creator + */ + public static final Parcelable.Creator<AccessibilityEvent> CREATOR = + new Parcelable.Creator<AccessibilityEvent>() { + public AccessibilityEvent createFromParcel(Parcel parcel) { + AccessibilityEvent event = AccessibilityEvent.obtain(); + event.initFromParcel(parcel); + return event; + } + + public AccessibilityEvent[] newArray(int size) { + return new AccessibilityEvent[size]; + } + }; +} diff --git a/core/java/android/view/accessibility/AccessibilityEventSource.java b/core/java/android/view/accessibility/AccessibilityEventSource.java new file mode 100644 index 0000000..3d70959 --- /dev/null +++ b/core/java/android/view/accessibility/AccessibilityEventSource.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2009 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; + +/** + * This interface is implemented by classes source of {@link AccessibilityEvent}s. + */ +public interface AccessibilityEventSource { + + /** + * Handles the request for sending an {@link AccessibilityEvent} given + * the event type. The method must first check if accessibility is on + * via calling {@link AccessibilityManager#isEnabled()}, obtain + * an {@link AccessibilityEvent} from the event pool through calling + * {@link AccessibilityEvent#obtain(int)}, populate the event, and + * send it for dispatch via calling + * {@link AccessibilityManager#sendAccessibilityEvent(AccessibilityEvent)}. + * + * @see AccessibilityEvent + * @see AccessibilityManager + * + * @param eventType The event type. + */ + public void sendAccessibilityEvent(int eventType); + + /** + * Handles the request for sending an {@link AccessibilityEvent}. The + * method does not guarantee to check if accessibility is on before + * sending the event for dispatch. It is responsibility of the caller + * to do the check via calling {@link AccessibilityManager#isEnabled()}. + * + * @see AccessibilityEvent + * @see AccessibilityManager + * + * @param event The event. + */ + public void sendAccessibilityEventUnchecked(AccessibilityEvent event); +} diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java new file mode 100644 index 0000000..0186270 --- /dev/null +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2009 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 static android.util.Config.LOGV; + +import android.content.Context; +import android.content.pm.ServiceInfo; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.util.Log; + +import java.util.Collections; +import java.util.List; + +/** + * System level service that serves as an event dispatch for {@link AccessibilityEvent}s. + * Such events are generated when something notable happens in the user interface, + * for example an {@link android.app.Activity} starts, the focus or selection of a + * {@link android.view.View} changes etc. Parties interested in handling accessibility + * events implement and register an accessibility service which extends + * {@link android.accessibilityservice.AccessibilityService}. + * + * @see AccessibilityEvent + * @see android.accessibilityservice.AccessibilityService + * @see android.content.Context#getSystemService + */ +public final class AccessibilityManager { + private static final String LOG_TAG = "AccessibilityManager"; + + static final Object sInstanceSync = new Object(); + + private static AccessibilityManager sInstance; + + private static final int DO_SET_ENABLED = 10; + + final IAccessibilityManager mService; + + final Handler mHandler; + + boolean mIsEnabled; + + final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() { + public void setEnabled(boolean enabled) { + mHandler.obtainMessage(DO_SET_ENABLED, enabled ? 1 : 0, 0).sendToTarget(); + } + }; + + class MyHandler extends Handler { + + MyHandler(Looper mainLooper) { + super(mainLooper); + } + + @Override + public void handleMessage(Message message) { + switch (message.what) { + case DO_SET_ENABLED : + synchronized (mHandler) { + mIsEnabled = (message.arg1 == 1); + } + return; + default : + Log.w(LOG_TAG, "Unknown message type: " + message.what); + } + } + } + + /** + * Get an AccessibilityManager instance (create one if necessary). + * + * @hide + */ + public static AccessibilityManager getInstance(Context context) { + synchronized (sInstanceSync) { + if (sInstance == null) { + sInstance = new AccessibilityManager(context); + } + } + return sInstance; + } + + /** + * Create an instance. + * + * @param context A {@link Context}. + */ + private AccessibilityManager(Context context) { + mHandler = new MyHandler(context.getMainLooper()); + IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE); + mService = IAccessibilityManager.Stub.asInterface(iBinder); + try { + mService.addClient(mClient); + } catch (RemoteException re) { + Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); + } + } + + /** + * Returns if the {@link AccessibilityManager} is enabled. + * + * @return True if this {@link AccessibilityManager} is enabled, false otherwise. + */ + public boolean isEnabled() { + synchronized (mHandler) { + return mIsEnabled; + } + } + + /** + * Sends an {@link AccessibilityEvent}. If this {@link AccessibilityManager} is not + * enabled the call is a NOOP. + * + * @param event The {@link AccessibilityEvent}. + * + * @throws IllegalStateException if a client tries to send an {@link AccessibilityEvent} + * while accessibility is not enabled. + */ + public void sendAccessibilityEvent(AccessibilityEvent event) { + if (!mIsEnabled) { + throw new IllegalStateException("Accessibility off. Did you forget to check that?"); + } + boolean doRecycle = false; + try { + event.setEventTime(SystemClock.uptimeMillis()); + // it is possible that this manager is in the same process as the service but + // client using it is called through Binder from another process. Example: MMS + // app adds a SMS notification and the NotificationManagerService calls this method + long identityToken = Binder.clearCallingIdentity(); + doRecycle = mService.sendAccessibilityEvent(event); + Binder.restoreCallingIdentity(identityToken); + if (LOGV) { + Log.i(LOG_TAG, event + " sent"); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error during sending " + event + " ", re); + } finally { + if (doRecycle) { + event.recycle(); + } + } + } + + /** + * Requests interruption of the accessibility feedback from all accessibility services. + */ + public void interrupt() { + if (!mIsEnabled) { + throw new IllegalStateException("Accessibility off. Did you forget to check that?"); + } + try { + mService.interrupt(); + if (LOGV) { + Log.i(LOG_TAG, "Requested interrupt from all services"); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re); + } + } + + /** + * Returns the {@link ServiceInfo}s of the installed accessibility services. + * + * @return An unmodifiable list with {@link ServiceInfo}s. + */ + public List<ServiceInfo> getAccessibilityServiceList() { + List<ServiceInfo> services = null; + try { + services = mService.getAccessibilityServiceList(); + if (LOGV) { + Log.i(LOG_TAG, "Installed AccessibilityServices " + services); + } + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); + } + return Collections.unmodifiableList(services); + } +} diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl new file mode 100644 index 0000000..32788be --- /dev/null +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -0,0 +1,39 @@ +/* //device/java/android/android/app/INotificationManager.aidl +** +** Copyright 2009, 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.AccessibilityEvent; +import android.view.accessibility.IAccessibilityManagerClient; +import android.content.pm.ServiceInfo; + +/** + * Interface implemented by the AccessibilityManagerService called by + * the AccessibilityMasngers. + * + * @hide + */ +interface IAccessibilityManager { + + void addClient(IAccessibilityManagerClient client); + + boolean sendAccessibilityEvent(in AccessibilityEvent uiEvent); + + List<ServiceInfo> getAccessibilityServiceList(); + + void interrupt(); +} diff --git a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl new file mode 100644 index 0000000..1eb60fc --- /dev/null +++ b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2009 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; + +/** + * Interface a client of the IAccessibilityManager implements to + * receive information about changes in the manager state. + * + * @hide + */ +oneway interface IAccessibilityManagerClient { + + void setEnabled(boolean enabled); + +} |
