Accessibility feature - framework changes (replacing 698, 699, 700, 701 and merging with the latest Donut)

This commit is contained in:
svetoslavganov
2009-05-14 22:28:01 -07:00
parent 669ec3a6e4
commit 75986cf9bc
43 changed files with 4241 additions and 113 deletions

View File

@@ -64,6 +64,8 @@ endif
##
## READ ME: ########################################################
LOCAL_SRC_FILES += \
core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl \
core/java/android/accessibilityservice/IEventListener.aidl \
core/java/android/accounts/IAccountsService.aidl \
core/java/android/app/IActivityPendingResult.aidl \
core/java/android/app/IActivityWatcher.aidl \
@@ -106,6 +108,8 @@ LOCAL_SRC_FILES += \
core/java/android/os/IPermissionController.aidl \
core/java/android/os/IPowerManager.aidl \
core/java/android/text/IClipboard.aidl \
core/java/android/view/accessibility/IAccessibilityManager.aidl \
core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \
core/java/android/view/IApplicationToken.aidl \
core/java/android/view/IOnKeyguardExitResult.aidl \
core/java/android/view/IRotationWatcher.aidl \

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,225 @@
/*
* 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.accessibilityservice;
import com.android.internal.os.HandlerCaller;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
/**
* An accessibility service runs in the background and receives callbacks by the system
* when {@link AccessibilityEvent}s are fired. Such events denote some state transition
* in the user interface, for example, the focus has changed, a button has been clicked,
* etc.
* <p>
* An accessibility service extends this class and implements its abstract methods. Such
* a service is declared as any other service in an AndroidManifest.xml but it must also
* specify that it handles the "android.accessibilityservice.AccessibilityService"
* {@link android.content.Intent}. Following is an example of such a declaration:
* <p>
* <code>
* &lt;service android:name=".MyAccessibilityService"&gt;<br>
* &lt;intent-filter&gt;<br>
* &lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;<br>
* &lt;/intent-filter&gt;<br>
* &lt;/service&gt;<br>
* </code>
* <p>
* The lifecycle of an accessibility service is managed exclusively by the system. Starting
* or stopping an accessibility service is triggered by an explicit user action through
* 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. An accessibility service
* is configured though setting an {@link AccessibilityServiceInfo} by calling
* {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}. You can call this
* method any time to change the service configuration but it is good practice to do that
* in the overriden {@link AccessibilityService#onServiceConnected()}.
* <p>
* An accessibility service can be registered for events in specific packages to provide a
* specific type of feedback and is notified with a certain timeout after the last event
* of interest has been fired.
* <p>
* <b>Notification strategy</b>
* <p>
* For each feedback type only one accessibility service is notified. Services are notified
* in the order of registration. Hence, if two services are registered for the same
* feedback type in the same package the first one wins. It is possible however, to
* register a service as the default one for a given feedback type. In such a case this
* service is invoked if no other service was interested in the event. In other words, default
* services do not compete with other services and are notified last regardless of the
* registration order. This enables "generic" accessibility services that work reasonably
* well with most applications to coexist with "polished" ones that are targeted for
* specific applications.
* <p>
* <b>Event types</b>
* <p>
* {@link AccessibilityEvent#TYPE_VIEW_CLICKED}
* {@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED}
* {@link AccessibilityEvent#TYPE_VIEW_FOCUSED}
* {@link AccessibilityEvent#TYPE_VIEW_SELECTED}
* {@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED}
* {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}
* {@link AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED}
* <p>
* <b>Feedback types</b>
* <p>
* {@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}
* {@link AccessibilityServiceInfo#FEEDBACK_HAPTIC}
* {@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}
* {@link AccessibilityServiceInfo#FEEDBACK_VISUAL}
* {@link AccessibilityServiceInfo#FEEDBACK_GENERIC}
*
* @see AccessibilityEvent
* @see AccessibilityServiceInfo
* @see android.view.accessibility.AccessibilityManager
*
* Note: The event notification timeout is useful to avoid propagating events to the client
* too frequently since this is accomplished via an expensive interprocess call.
* One can think of the timeout as a criteria to determine when event generation has
* settled down.
*/
public abstract class AccessibilityService extends Service {
/**
* The {@link Intent} that must be declared as handled by the service.
*/
public static final String SERVICE_INTERFACE =
"android.accessibilityservice.AccessibilityService";
private static final String LOG_TAG = "AccessibilityService";
private AccessibilityServiceInfo mInfo;
IAccessibilityServiceConnection mConnection;
/**
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
*
* @param event An event.
*/
public abstract void onAccessibilityEvent(AccessibilityEvent event);
/**
* Callback for interrupting the accessibility feedback.
*/
public abstract void onInterrupt();
/**
* This method is a part of the {@link AccessibilityService} lifecycle and is
* called after the system has successfully bound to the service. If is
* convenient to use this method for setting the {@link AccessibilityServiceInfo}.
*
* @see AccessibilityServiceInfo
* @see #setServiceInfo(AccessibilityServiceInfo)
*/
protected void onServiceConnected() {
}
/**
* Sets the {@link AccessibilityServiceInfo} that describes this service.
* <p>
* Note: You can call this method any time but the info will be picked up after
* the system has bound to this service and when this method is called thereafter.
*
* @param info The info.
*/
public final void setServiceInfo(AccessibilityServiceInfo info) {
mInfo = info;
sendServiceInfo();
}
/**
* Sets the {@link AccessibilityServiceInfo} for this service if the latter is
* properly set and there is an {@link IAccessibilityServiceConnection} to the
* AccessibilityManagerService.
*/
private void sendServiceInfo() {
if (mInfo != null && mConnection != null) {
try {
mConnection.setServiceInfo(mInfo);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
}
}
}
@Override
public final IBinder onBind(Intent intent) {
return new IEventListenerWrapper(this);
}
/**
* Implements the internal {@link IEventListener} interface to convert
* incoming calls to it back to calls on an {@link AccessibilityService}.
*/
class IEventListenerWrapper extends IEventListener.Stub
implements HandlerCaller.Callback {
private static final int DO_SET_SET_CONNECTION = 10;
private static final int DO_ON_INTERRUPT = 20;
private static final int DO_ON_ACCESSIBILITY_EVENT = 30;
private final HandlerCaller mCaller;
private AccessibilityService mTarget;
public IEventListenerWrapper(AccessibilityService context) {
mTarget = context;
mCaller = new HandlerCaller(context, this);
}
public void setConnection(IAccessibilityServiceConnection connection) {
Message message = mCaller.obtainMessageO(DO_SET_SET_CONNECTION, connection);
mCaller.sendMessage(message);
}
public void onInterrupt() {
Message message = mCaller.obtainMessage(DO_ON_INTERRUPT);
mCaller.sendMessage(message);
}
public void onAccessibilityEvent(AccessibilityEvent event) {
Message message = mCaller.obtainMessageO(DO_ON_ACCESSIBILITY_EVENT, event);
mCaller.sendMessage(message);
}
public void executeMessage(Message message) {
switch (message.what) {
case DO_ON_ACCESSIBILITY_EVENT :
AccessibilityEvent event = (AccessibilityEvent) message.obj;
mTarget.onAccessibilityEvent(event);
event.recycle();
return;
case DO_ON_INTERRUPT :
mTarget.onInterrupt();
return;
case DO_SET_SET_CONNECTION :
mConnection = ((IAccessibilityServiceConnection) message.obj);
mTarget.onServiceConnected();
return;
default :
Log.w(LOG_TAG, "Unknown message type " + message.what);
}
}
}
}

View File

@@ -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.accessibilityservice;
parcelable AccessibilityServiceInfo;

View File

@@ -0,0 +1,145 @@
/*
* 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.accessibilityservice;
import android.os.Parcel;
import android.os.Parcelable;
/**
* This class describes an {@link AccessibilityService}. The system
* notifies an {@link AccessibilityService} for
* {@link android.view.accessibility.AccessibilityEvent}s
* according to the information encapsulated in this class.
*
* @see AccessibilityService
* @see android.view.accessibility.AccessibilityEvent
*/
public class AccessibilityServiceInfo implements Parcelable {
/**
* Denotes spoken feedback.
*/
public static final int FEEDBACK_SPOKEN = 0x0000001;
/**
* Denotes haptic feedback.
*/
public static final int FEEDBACK_HAPTIC = 0x0000002;
/**
* Denotes audible (not spoken) feedback.
*/
public static final int FEEDBACK_AUDIBLE = 0x0000004;
/**
* Denotes visual feedback.
*/
public static final int FEEDBACK_VISUAL = 0x0000008;
/**
* Denotes generic feedback.
*/
public static final int FEEDBACK_GENERIC = 0x0000010;
/**
* If an {@link AccessibilityService} is the default for a given type.
* Default service is invoked only if no package specific one exists. In case of
* more than one package specific service only the earlier registered is notified.
*/
public static final int DEFAULT = 0x0000001;
/**
* The event types an {@link AccessibilityService} is interested in.
*
* @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED
* @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_FOCUSED
* @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SELECTED
* @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED
* @see android.view.accessibility.AccessibilityEvent#TYPE_ACTIVITY_STARTED
* @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
* @see android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED
*/
public int eventTypes;
/**
* The package names an {@link AccessibilityService} is interested in. Setting
* to null is equivalent to all packages.
*/
public String[] packageNames;
/**
* The feedback type an {@link AccessibilityService} provides.
*
* @see #FEEDBACK_AUDIBLE
* @see #FEEDBACK_GENERIC
* @see #FEEDBACK_HAPTIC
* @see #FEEDBACK_SPOKEN
* @see #FEEDBACK_VISUAL
*/
public int feedbackType;
/**
* The timeout after the most recent event of a given type before an
* {@link AccessibilityService} is notified.
* <p>
* Note: The event notification timeout is useful to avoid propagating events to the client
* too frequently since this is accomplished via an expensive interprocess call.
* One can think of the timeout as a criteria to determine when event generation has
* settled down
*/
public long notificationTimeout;
/**
* This field represents a set of flags used for configuring an
* {@link AccessibilityService}.
*
* @see #DEFAULT
*/
public int flags;
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(eventTypes);
parcel.writeStringArray(packageNames);
parcel.writeInt(feedbackType);
parcel.writeLong(notificationTimeout);
parcel.writeInt(flags);
}
/**
* @see Parcelable.Creator
*/
public static final Parcelable.Creator<AccessibilityServiceInfo> CREATOR =
new Parcelable.Creator<AccessibilityServiceInfo>() {
public AccessibilityServiceInfo createFromParcel(Parcel parcel) {
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
info.eventTypes = parcel.readInt();
info.packageNames = parcel.readStringArray();
info.feedbackType = parcel.readInt();
info.notificationTimeout = parcel.readLong();
info.flags = parcel.readInt();
return info;
}
public AccessibilityServiceInfo[] newArray(int size) {
return new AccessibilityServiceInfo[size];
}
};
}

View File

@@ -0,0 +1,30 @@
/*
* 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.accessibilityservice;
import android.accessibilityservice.AccessibilityServiceInfo;
/**
* Interface AccessibilityManagerService#Service implements, and passes to an
* AccessibilityService so it can dynamically configure how the system handles it.
*
* @hide
*/
oneway interface IAccessibilityServiceConnection {
void setServiceInfo(in AccessibilityServiceInfo info);
}

View File

@@ -0,0 +1,34 @@
/*
** 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.accessibilityservice;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.view.accessibility.AccessibilityEvent;
/**
* Top-level interface to accessibility service component (implemented in Service).
*
* @hide
*/
oneway interface IEventListener {
void setConnection(in IAccessibilityServiceConnection connection);
void onAccessibilityEvent(in AccessibilityEvent event);
void onInterrupt();
}

View File

@@ -16,6 +16,8 @@
package android.app;
import com.android.internal.policy.PolicyManager;
import android.content.ComponentCallbacks;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -32,11 +34,12 @@ import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
import android.util.Config;
@@ -58,10 +61,10 @@ import android.view.Window;
import android.view.WindowManager;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnCreateContextMenuListener;
import android.view.ViewGroup.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView;
import com.android.internal.policy.PolicyManager;
import java.util.ArrayList;
import java.util.HashMap;
@@ -2013,7 +2016,24 @@ public class Activity extends ContextThemeWrapper
}
return onTrackballEvent(ev);
}
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
event.setClassName(getClass().getName());
event.setPackageName(getPackageName());
LayoutParams params = getWindow().getAttributes();
boolean isFullScreen = (params.width == LayoutParams.FILL_PARENT) &&
(params.height == LayoutParams.FILL_PARENT);
event.setFullScreen(isFullScreen);
CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
event.getText().add(title);
}
return true;
}
/**
* Default implementation of
* {@link android.view.Window.Callback#onCreatePanelView}

View File

@@ -16,8 +16,11 @@
package android.app;
import com.google.android.collect.Maps;
import com.android.internal.policy.PolicyManager;
import com.android.internal.util.XmlUtils;
import com.google.android.collect.Maps;
import org.xmlpull.v1.XmlPullParserException;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.IBluetoothDevice;
@@ -37,9 +40,9 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -68,15 +71,15 @@ import android.net.wifi.IWifiManager;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.Looper;
import android.os.RemoteException;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.IPowerManager;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Vibrator;
import android.os.FileUtils.FileStatus;
@@ -87,10 +90,9 @@ import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.policy.PolicyManager;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -100,16 +102,14 @@ import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.WeakHashMap;
import java.util.Map.Entry;
import org.xmlpull.v1.XmlPullParserException;
class ReceiverRestrictedContext extends ContextWrapper {
ReceiverRestrictedContext(Context base) {
super(base);
@@ -172,6 +172,7 @@ class ApplicationContext extends Context {
private Resources.Theme mTheme = null;
private PackageManager mPackageManager;
private NotificationManager mNotificationManager = null;
private AccessibilityManager mAccessibilityManager = null;
private ActivityManager mActivityManager = null;
private Context mReceiverRestrictedContext = null;
private SearchManager mSearchManager = null;
@@ -904,6 +905,8 @@ class ApplicationContext extends Context {
return getNotificationManager();
} else if (KEYGUARD_SERVICE.equals(name)) {
return new KeyguardManager();
} else if (ACCESSIBILITY_SERVICE.equals(name)) {
return AccessibilityManager.getInstance(this);
} else if (LOCATION_SERVICE.equals(name)) {
return getLocationManager();
} else if (SEARCH_SERVICE.equals(name)) {

View File

@@ -16,32 +16,34 @@
package android.app;
import com.android.internal.policy.PolicyManager;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Bundle;
import android.util.Config;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.view.Window;
import android.view.WindowManager;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnCreateContextMenuListener;
import com.android.internal.policy.PolicyManager;
import android.view.ViewGroup.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import java.lang.ref.WeakReference;
@@ -81,6 +83,7 @@ public class Dialog implements DialogInterface, Window.Callback,
* {@hide}
*/
protected boolean mCancelable = true;
private Message mCancelMessage;
private Message mDismissMessage;
@@ -209,7 +212,9 @@ public class Dialog implements DialogInterface, Window.Callback,
if (mShowing) {
if (Config.LOGV) Log.v(LOG_TAG,
"[Dialog] start: already showing, ignore");
if (mDecor != null) mDecor.setVisibility(View.VISIBLE);
if (mDecor != null) {
mDecor.setVisibility(View.VISIBLE);
}
return;
}
@@ -236,7 +241,9 @@ public class Dialog implements DialogInterface, Window.Callback,
* Hide the dialog, but do not dismiss it.
*/
public void hide() {
if (mDecor != null) mDecor.setVisibility(View.GONE);
if (mDecor != null) {
mDecor.setVisibility(View.GONE);
}
}
/**
@@ -266,6 +273,7 @@ public class Dialog implements DialogInterface, Window.Callback,
}
mWindowManager.removeView(mDecor);
mDecor = null;
mWindow.closeAllPanels();
onStop();
@@ -280,7 +288,7 @@ public class Dialog implements DialogInterface, Window.Callback,
Message.obtain(mDismissMessage).sendToTarget();
}
}
// internal method to make sure mcreated is set properly without requiring
// users to call through to super in onCreate
void dispatchOnCreate(Bundle savedInstanceState) {
@@ -608,6 +616,18 @@ public class Dialog implements DialogInterface, Window.Callback,
return onTrackballEvent(ev);
}
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
event.setClassName(getClass().getName());
event.setPackageName(mContext.getPackageName());
LayoutParams params = getWindow().getAttributes();
boolean isFullScreen = (params.width == LayoutParams.FILL_PARENT) &&
(params.height == LayoutParams.FILL_PARENT);
event.setFullScreen(isFullScreen);
return false;
}
/**
* @see Activity#onCreatePanelView(int)
*/

View File

@@ -1133,6 +1133,15 @@ public abstract class Context {
* @see android.app.NotificationManager
*/
public static final String NOTIFICATION_SERVICE = "notification";
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.view.accessibility.AccessibilityManager} for giving the user
* feedback for UI events through the registered event listeners.
*
* @see #getSystemService
* @see android.view.accessibility.AccessibilityManager
*/
public static final String ACCESSIBILITY_SERVICE = "accessibility";
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.app.NotificationManager} for controlling keyguard.

View File

@@ -16,6 +16,7 @@
package android.preference;
import android.app.Service;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
@@ -23,6 +24,8 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Checkable;
import android.widget.TextView;
@@ -42,6 +45,9 @@ public class CheckBoxPreference extends Preference {
private CharSequence mSummaryOff;
private boolean mChecked;
private boolean mSendAccessibilityEventViewClickedType;
private AccessibilityManager mAccessibilityManager;
private boolean mDisableDependentsState;
@@ -55,6 +61,9 @@ public class CheckBoxPreference extends Preference {
mDisableDependentsState = a.getBoolean(
com.android.internal.R.styleable.CheckBoxPreference_disableDependentsState, false);
a.recycle();
mAccessibilityManager =
(AccessibilityManager) getContext().getSystemService(Service.ACCESSIBILITY_SERVICE);
}
public CheckBoxPreference(Context context, AttributeSet attrs) {
@@ -64,14 +73,26 @@ public class CheckBoxPreference extends Preference {
public CheckBoxPreference(Context context) {
this(context, null);
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
View checkboxView = view.findViewById(com.android.internal.R.id.checkbox);
if (checkboxView != null && checkboxView instanceof Checkable) {
((Checkable) checkboxView).setChecked(mChecked);
// send an event to announce the value change of the CheckBox and is done here
// because clicking a preference does not immediately change the checked state
// for example when enabling the WiFi
if (mSendAccessibilityEventViewClickedType &&
mAccessibilityManager.isEnabled() &&
checkboxView.isEnabled()) {
mSendAccessibilityEventViewClickedType = false;
int eventType = AccessibilityEvent.TYPE_VIEW_CLICKED;
checkboxView.sendAccessibilityEventUnchecked(AccessibilityEvent.obtain(eventType));
}
}
// Sync the summary view
@@ -85,7 +106,7 @@ public class CheckBoxPreference extends Preference {
summaryView.setText(mSummaryOff);
useDefaultSummary = false;
}
if (useDefaultSummary) {
final CharSequence summary = getSummary();
if (summary != null) {
@@ -111,6 +132,10 @@ public class CheckBoxPreference extends Preference {
boolean newValue = !isChecked();
// in onBindView() an AccessibilityEventViewClickedType is sent to announce the change
// not sending
mSendAccessibilityEventViewClickedType = true;
if (!callChangeListener(newValue)) {
return;
}
@@ -124,10 +149,11 @@ public class CheckBoxPreference extends Preference {
* @param checked The checked state.
*/
public void setChecked(boolean checked) {
mChecked = checked;
persistBoolean(checked);
notifyDependencyChange(shouldDisableDependents());
notifyChanged();

View File

@@ -1904,6 +1904,17 @@ public final class Settings {
*/
public static final String USE_GOOGLE_MAIL = "use_google_mail";
/**
* If accessibility is enabled.
*/
public static final String ACCESSIBILITY_ENABLED = "accessibility_enabled";
/**
* List of the enabled accessibility providers.
*/
public static final String ENABLED_ACCESSIBILITY_SERVICES =
"enabled_accessibility_services";
/**
* Whether to notify the user of open networks.
* <p>

View File

@@ -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;
/**
@@ -850,6 +853,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.
@@ -1550,6 +1565,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
@ViewDebug.ExportedProperty
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.
*/
@@ -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()) {
@@ -2513,6 +2544,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
@@ -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.

View File

@@ -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}
*/

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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];
}
};
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -24,11 +24,12 @@ import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.ContextMenu;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewDebug;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.accessibility.AccessibilityEvent;
/**
@@ -618,7 +619,9 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
/**
* Sets the currently selected item
* Sets the currently selected item. To support accessibility subclasses that
* override this method must invoke the overriden super method first.
*
* @param position Index (starting at 0) of the data item to be selected.
*/
public abstract void setSelection(int position);
@@ -844,6 +847,11 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
fireOnSelected();
}
}
// we fire selection events here not in View
if (mSelectedPosition != ListView.INVALID_POSITION && isShown() && !isInTouchMode()) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
}
private void fireOnSelected() {
@@ -860,6 +868,35 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
boolean populated = false;
// This is an exceptional case which occurs when a window gets the
// focus and sends a focus event via its focused child to announce
// current focus/selection. AdapterView fires selection but not focus
// events so we change the event type here.
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
// we send selection events only from AdapterView to avoid
// generation of such event for each child
View selectedView = getSelectedView();
if (selectedView != null) {
populated = selectedView.dispatchPopulateAccessibilityEvent(event);
}
if (!populated) {
if (selectedView != null) {
event.setEnabled(selectedView.isEnabled());
}
event.setItemCount(getCount());
event.setCurrentItemIndex(getSelectedItemPosition());
}
return populated;
}
@Override
protected boolean canAnimate() {
return super.canAnimate() && mItemCount > 0;

View File

@@ -16,14 +16,15 @@
package android.widget;
import com.android.internal.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import com.android.internal.R;
import android.view.accessibility.AccessibilityEvent;
/**
@@ -194,5 +195,13 @@ public class CheckedTextView extends TextView implements Checkable {
invalidate();
}
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
boolean populated = super.dispatchPopulateAccessibilityEvent(event);
if (!populated) {
event.setChecked(mChecked);
}
return populated;
}
}

View File

@@ -26,7 +26,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.accessibility.AccessibilityEvent;
/**
* <p>
@@ -124,6 +124,7 @@ public abstract class CompoundButton extends Button implements Checkable {
if (mOnCheckedChangeWidgetListener != null) {
mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
}
mBroadcasting = false;
}
}
@@ -204,6 +205,25 @@ public abstract class CompoundButton extends Button implements Checkable {
refreshDrawableState();
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
boolean populated = super.dispatchPopulateAccessibilityEvent(event);
if (!populated) {
int resourceId = 0;
if (mChecked) {
resourceId = R.string.accessibility_compound_button_selected;
} else {
resourceId = R.string.accessibility_compound_button_unselected;
}
String state = getResources().getString(resourceId);
event.getText().add(state);
event.setChecked(mChecked);
}
return populated;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

View File

@@ -32,6 +32,8 @@ import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.RemoteViews.RemoteView;
@@ -848,7 +850,7 @@ public class ImageView extends View {
public int getBaseline() {
return mBaselineAligned ? getMeasuredHeight() : -1;
}
/**
* Set a tinting option for the image.
*
@@ -878,7 +880,7 @@ public class ImageView extends View {
invalidate();
}
}
public void setAlpha(int alpha) {
alpha &= 0xFF; // keep it legal
if (mAlpha != alpha) {

View File

@@ -35,6 +35,7 @@ import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.SoundEffectConstants;
import android.view.accessibility.AccessibilityEvent;
import com.google.android.collect.Lists;
import com.android.internal.R;
@@ -1845,6 +1846,32 @@ public class ListView extends AbsListView {
}
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
boolean populated = super.dispatchPopulateAccessibilityEvent(event);
if (!populated) {
int itemCount = 0;
int currentItemIndex = getSelectedItemPosition();
ListAdapter adapter = getAdapter();
if (adapter != null) {
for (int i = 0, count = adapter.getCount(); i < count; i++) {
if (adapter.isEnabled(i)) {
itemCount++;
} else if (i <= currentItemIndex) {
currentItemIndex--;
}
}
}
event.setItemCount(itemCount);
event.setCurrentItemIndex(currentItemIndex);
}
return populated;
}
/**
* setSelectionAfterHeaderView set the selection to be the first list item
* after the header views.

View File

@@ -18,6 +18,8 @@ package android.widget;
import com.android.internal.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -33,8 +35,6 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.os.IBinder;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import java.lang.ref.WeakReference;
@@ -1017,6 +1017,7 @@ public class PopupWindow {
unregisterForScrollChanged();
mWindowManager.removeView(mPopupView);
if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
((ViewGroup) mPopupView).removeView(mContentView);
}
@@ -1316,7 +1317,16 @@ public class PopupWindow {
return super.onTouchEvent(event);
}
}
@Override
public void sendAccessibilityEvent(int eventType) {
// clinets are interested in the content not the container, make it event source
if (mContentView != null) {
mContentView.sendAccessibilityEvent(eventType);
} else {
super.sendAccessibilityEvent(eventType);
}
}
}
}

View File

@@ -16,17 +16,22 @@
package android.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.Gravity;
import android.view.ViewDebug;
import android.widget.RemoteViews.RemoteView;
import android.graphics.Rect;
import com.android.internal.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.RemoteViews.RemoteView;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* A Layout where the positions of the children can be described in relation to each other or to the
@@ -137,6 +142,8 @@ public class RelativeLayout extends ViewGroup {
private final Rect mSelfBounds = new Rect();
private int mIgnoreGravity;
private static SortedSet<View> mTopToBottomLeftToRightSet = null;
public RelativeLayout(Context context) {
super(context);
}
@@ -782,6 +789,57 @@ public class RelativeLayout extends ViewGroup {
return new LayoutParams(p);
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
if (mTopToBottomLeftToRightSet == null) {
mTopToBottomLeftToRightSet = new TreeSet<View>(new TopToBottomLeftToRightComparator());
}
// sort children top-to-bottom and left-to-right
for (int i = 0, count = getChildCount(); i < count; i++) {
mTopToBottomLeftToRightSet.add(getChildAt(i));
}
for (View view : mTopToBottomLeftToRightSet) {
if (view.dispatchPopulateAccessibilityEvent(event)) {
mTopToBottomLeftToRightSet.clear();
return true;
}
}
mTopToBottomLeftToRightSet.clear();
return false;
}
/**
* Compares two views in left-to-right and top-to-bottom fashion.
*/
private class TopToBottomLeftToRightComparator implements Comparator<View> {
public int compare(View first, View second) {
// top - bottom
int topDifference = first.getTop() - second.getTop();
if (topDifference != 0) {
return topDifference;
}
// left - right
int leftDifference = first.getLeft() - second.getLeft();
if (leftDifference != 0) {
return leftDifference;
}
// break tie by height
int heightDiference = first.getHeight() - second.getHeight();
if (heightDiference != 0) {
return heightDiference;
}
// break tie by width
int widthDiference = first.getWidth() - second.getWidth();
if (widthDiference != 0) {
return widthDiference;
}
return 0;
}
}
/**
* Per-child layout information associated with RelativeLayout.
*

View File

@@ -16,21 +16,22 @@
package android.widget;
import android.view.ViewGroup;
import android.view.View;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.SoundEffectConstants;
import android.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.Bitmap;
import android.os.SystemClock;
import android.os.Handler;
import android.os.Message;
import android.R;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
/**
* SlidingDrawer hides content out of the screen and allows the user to drag a handle
@@ -746,6 +747,8 @@ public class SlidingDrawer extends ViewGroup {
openDrawer();
invalidate();
requestLayout();
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
/**
@@ -777,6 +780,7 @@ public class SlidingDrawer extends ViewGroup {
scrollListener.onScrollStarted();
}
animateClose(mVertical ? mHandle.getTop() : mHandle.getLeft());
if (scrollListener != null) {
scrollListener.onScrollEnded();
}
@@ -798,6 +802,9 @@ public class SlidingDrawer extends ViewGroup {
scrollListener.onScrollStarted();
}
animateOpen(mVertical ? mHandle.getTop() : mHandle.getLeft());
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
if (scrollListener != null) {
scrollListener.onScrollEnded();
}
@@ -827,6 +834,7 @@ public class SlidingDrawer extends ViewGroup {
}
mExpanded = true;
if (mOnDrawerOpenListener != null) {
mOnDrawerOpenListener.onDrawerOpened();
}

View File

@@ -16,6 +16,11 @@
package android.widget;
import com.android.internal.util.FastMath;
import com.android.internal.widget.EditableInputConnection;
import org.xmlpull.v1.XmlPullParserException;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
@@ -31,17 +36,17 @@ import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.os.Message;
import android.text.BoringLayout;
import android.text.ClipboardManager;
import android.text.DynamicLayout;
import android.text.Editable;
import android.text.GetChars;
import android.text.GraphicsOperations;
import android.text.ClipboardManager;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Layout;
@@ -49,9 +54,9 @@ import android.text.ParcelableSpan;
import android.text.Selection;
import android.text.SpanWatcher;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.SpannableString;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
@@ -64,19 +69,18 @@ import android.text.method.KeyListener;
import android.text.method.LinkMovementMethod;
import android.text.method.MetaKeyKeyListener;
import android.text.method.MovementMethod;
import android.text.method.TimeKeyListener;
import android.text.method.PasswordTransformationMethod;
import android.text.method.SingleLineTransformationMethod;
import android.text.method.TextKeyListener;
import android.text.method.TimeKeyListener;
import android.text.method.TransformationMethod;
import android.text.style.ParagraphStyle;
import android.text.style.URLSpan;
import android.text.style.UpdateAppearance;
import android.text.util.Linkify;
import android.util.AttributeSet;
import android.util.Log;
import android.util.FloatMath;
import android.util.Log;
import android.util.TypedValue;
import android.view.ContextMenu;
import android.view.Gravity;
@@ -89,25 +93,22 @@ import android.view.ViewDebug;
import android.view.ViewRoot;
import android.view.ViewTreeObserver;
import android.view.ViewGroup.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.EditorInfo;
import android.widget.RemoteViews.RemoteView;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import com.android.internal.util.FastMath;
import com.android.internal.widget.EditableInputConnection;
import org.xmlpull.v1.XmlPullParserException;
/**
* Displays text to the user and optionally allows them to edit it. A TextView
* is a complete text editor, however the basic class is configured to not
@@ -6129,10 +6130,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private class ChangeWatcher
implements TextWatcher, SpanWatcher {
private CharSequence mBeforeText;
public void beforeTextChanged(CharSequence buffer, int start,
int before, int after) {
if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start
+ " before=" + before + " after=" + after + ": " + buffer);
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
mBeforeText = buffer.toString();
}
TextView.this.sendBeforeTextChanged(buffer, start, before, after);
}
@@ -6141,6 +6150,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start
+ " before=" + before + " after=" + after + ": " + buffer);
TextView.this.handleTextChanged(buffer, start, before, after);
if (AccessibilityManager.getInstance(mContext).isEnabled() &&
(isFocused() || isSelected() &&
isShown())) {
sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
mBeforeText = null;
}
}
public void afterTextChanged(Editable buffer) {
@@ -6775,6 +6791,40 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return TextUtils.substring(mTransformed, start, end);
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
boolean isPassword =
(mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) ==
(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
if (!isPassword) {
CharSequence text = getText();
if (TextUtils.isEmpty(text)) {
text = getHint();
}
if (!TextUtils.isEmpty(text)) {
if (text.length() > AccessibilityEvent.MAX_TEXT_LENGTH) {
text = text.subSequence(0, AccessibilityEvent.MAX_TEXT_LENGTH + 1);
}
event.getText().add(text);
}
} else {
event.setPassword(isPassword);
}
return false;
}
void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
int fromIndex, int removedCount, int addedCount) {
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
event.setFromIndex(fromIndex);
event.setRemovedCount(removedCount);
event.setAddedCount(addedCount);
event.setBeforeText(beforeText);
sendAccessibilityEventUnchecked(event);
}
@Override
protected void onCreateContextMenu(ContextMenu menu) {
super.onCreateContextMenu(menu);

View File

@@ -21,8 +21,8 @@ import android.app.ITransientNotification;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.os.RemoteException;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.view.Gravity;
@@ -278,7 +278,7 @@ public class Toast {
}
tv.setText(s);
}
// =======================================================================================
// All the gunk below is the interaction with the Notification Service, which handles
// the proper ordering of these system-wide.
@@ -373,6 +373,7 @@ public class Toast {
TAG, "REMOVE! " + mView + " in " + this);
mWM.removeView(mView);
}
mView = null;
}
}

View File

@@ -1165,6 +1165,11 @@
enabled for events such as long presses. -->
<attr name="hapticFeedbackEnabled" format="boolean" />
<!-- Defines text that briefly describes content of the view. This property is used
primarily for accessibility. Since some views do not have textual
representation this attribute can be used for providing such. -->
<attr name="contentDescription" format="string" localization="suggested" />
<!-- Name of the method in this View's context to invoke when the view is
clicked. This name must correspond to a public method that takes
exactly one parameter of type View. For instance, if you specify

View File

@@ -1100,6 +1100,7 @@
<public type="attr" name="targetSdkVersion" id="0x01010270" />
<public type="attr" name="maxSdkVersion" id="0x01010271" />
<public type="attr" name="testOnly" id="0x01010272" />
<public type="attr" name="contentDescription" id="0x01010273" />
<public type="anim" name="anticipate_interpolator" id="0x010a0007" />
<public type="anim" name="overshoot_interpolator" id="0x010a0008" />

View File

@@ -887,7 +887,6 @@
properties uploaded by the checkin service. Not for use by normal
applications.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_bindGadget">choose widgets</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -2366,10 +2365,12 @@
<!-- This string array should be overridden by the manufacture to present a list of carrier-id,locale pairs. This is used at startup to set a default locale by checking the system property ro.carrier for the carrier-id and searching through this array -->
<string-array translatable="false" name="carrier_locales">
</string-array>
</string-array>
<!-- Title for the selected state of a CompoundButton. -->
<string name="accessibility_compound_button_selected">checked</string>
<!-- Title for the unselected state of a CompoundButton. -->
<string name="accessibility_compound_button_unselected">not checked</string>
</resources>

View File

@@ -0,0 +1,668 @@
/*
** 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 com.android.server;
import static android.util.Config.LOGV;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.HandlerCaller.SomeArgs;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.accessibilityservice.IEventListener;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
import android.util.Log;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.IAccessibilityManager;
import android.view.accessibility.IAccessibilityManagerClient;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This class is instantiated by the system as a system level service and can be
* accessed only by the system. The task of this service is to be a centralized
* event dispatch for {@link AccessibilityEvent}s generated across all processes
* on the device. Events are dispatched to {@link AccessibilityService}s.
*
* @hide
*/
public class AccessibilityManagerService extends IAccessibilityManager.Stub
implements HandlerCaller.Callback {
private static final String LOG_TAG = "AccessibilityManagerService";
private static int sIdCounter = 0;
private static final int OWN_PROCESS_ID = android.os.Process.myPid();
private static final int DO_SET_SERVICE_INFO = 10;
final HandlerCaller mCaller;
final Context mContext;
final Object mLock = new Object();
final List<Service> mServices = new ArrayList<Service>();
final List<IAccessibilityManagerClient> mClients =
new ArrayList<IAccessibilityManagerClient>();
final Map<ComponentName, Service> mComponentNameToServiceMap =
new HashMap<ComponentName, Service>();
private final List<ServiceInfo> mInstalledServices = new ArrayList<ServiceInfo>();
private final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>();
private final SimpleStringSplitter mStringColonSplitter = new SimpleStringSplitter(':');
private PackageManager mPackageManager;
private int mHandledFeedbackTypes = 0;
private boolean mIsEnabled;
/**
* Handler for delayed event dispatch.
*/
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message message) {
Service service = (Service) message.obj;
int eventType = message.arg1;
synchronized (mLock) {
notifyEventListenerLocked(service, eventType);
AccessibilityEvent oldEvent = service.mPendingEvents.get(eventType);
service.mPendingEvents.remove(eventType);
tryRecycleLocked(oldEvent);
}
}
};
/**
* Creates a new instance.
*
* @param context A {@link Context} instance.
*/
AccessibilityManagerService(Context context) {
mContext = context;
mPackageManager = mContext.getPackageManager();
mCaller = new HandlerCaller(context, this);
registerPackageChangeAndBootCompletedBroadcastReceiver();
registerSettingsContentObservers();
synchronized (mLock) {
populateAccessibilityServiceListLocked();
}
}
/**
* Registers a {@link BroadcastReceiver} for the events of
* adding/changing/removing/restarting a package and boot completion.
*/
private void registerPackageChangeAndBootCompletedBroadcastReceiver() {
Context context = mContext;
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
synchronized (mLock) {
populateAccessibilityServiceListLocked();
manageServicesLocked();
if (intent.getAction() == Intent.ACTION_BOOT_COMPLETED) {
mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
updateClientsLocked();
}
}
}
};
// package changes
IntentFilter packageFilter = new IntentFilter();
packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
packageFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
packageFilter.addDataScheme("package");
context.registerReceiver(broadcastReceiver, packageFilter);
// boot completed
IntentFilter bootFiler = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
mContext.registerReceiver(broadcastReceiver, bootFiler);
}
/**
* {@link ContentObserver}s for {@link Settings.Secure#ACCESSIBILITY_ENABLED}
* and {@link Settings.Secure#ENABLED_ACCESSIBILITY_SERVICES} settings.
*/
private void registerSettingsContentObservers() {
ContentResolver contentResolver = mContext.getContentResolver();
Uri enabledUri = Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_ENABLED);
contentResolver.registerContentObserver(enabledUri, false,
new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
mIsEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
synchronized (mLock) {
if (mIsEnabled) {
manageServicesLocked();
} else {
unbindAllServicesLocked();
}
updateClientsLocked();
}
}
});
Uri providersUri =
Settings.Secure.getUriFor(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
contentResolver.registerContentObserver(providersUri, false,
new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
synchronized (mLock) {
manageServicesLocked();
}
}
});
}
public void addClient(IAccessibilityManagerClient client) {
synchronized (mLock) {
try {
client.setEnabled(mIsEnabled);
mClients.add(client);
} catch (RemoteException re) {
Log.w(LOG_TAG, "Dead AccessibilityManagerClient: " + client, re);
}
}
}
public boolean sendAccessibilityEvent(AccessibilityEvent event) {
synchronized (mLock) {
notifyAccessibilityServicesDelayedLocked(event, false);
notifyAccessibilityServicesDelayedLocked(event, true);
}
// event not scheduled for dispatch => recycle
if (mHandledFeedbackTypes == 0) {
event.recycle();
} else {
mHandledFeedbackTypes = 0;
}
return (OWN_PROCESS_ID != Binder.getCallingPid());
}
public List<ServiceInfo> getAccessibilityServiceList() {
synchronized (mLock) {
return mInstalledServices;
}
}
public void interrupt() {
synchronized (mLock) {
for (int i = 0, count = mServices.size(); i < count; i++) {
Service service = mServices.get(i);
try {
service.mServiceInterface.onInterrupt();
} catch (RemoteException re) {
if (re instanceof DeadObjectException) {
Log.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up.");
if (removeDeadServiceLocked(service)) {
count--;
i--;
}
} else {
Log.e(LOG_TAG, "Error during sending interrupt request to "
+ service.mService, re);
}
}
}
}
}
public void executeMessage(Message message) {
switch (message.what) {
case DO_SET_SERVICE_INFO:
SomeArgs arguments = ((SomeArgs) message.obj);
AccessibilityServiceInfo info = (AccessibilityServiceInfo) arguments.arg1;
Service service = (Service) arguments.arg2;
synchronized (mLock) {
service.mEventTypes = info.eventTypes;
service.mFeedbackType = info.feedbackType;
String[] packageNames = info.packageNames;
if (packageNames != null) {
service.mPackageNames.addAll(Arrays.asList(packageNames));
}
service.mNotificationTimeout = info.notificationTimeout;
service.mIsDefault = (info.flags & AccessibilityServiceInfo.DEFAULT) != 0;
}
return;
default:
Log.w(LOG_TAG, "Unknown message type: " + message.what);
}
}
/**
* Populates the cached list of installed {@link AccessibilityService}s.
*/
private void populateAccessibilityServiceListLocked() {
mInstalledServices.clear();
List<ResolveInfo> installedServices = mPackageManager.queryIntentServices(
new Intent(AccessibilityService.SERVICE_INTERFACE), PackageManager.GET_SERVICES);
for (int i = 0, count = installedServices.size(); i < count; i++) {
mInstalledServices.add(installedServices.get(i).serviceInfo);
}
}
/**
* Performs {@link AccessibilityService}s delayed notification. The delay is configurable
* and denotes the period after the last event before notifying the service.
*
* @param event The event.
* @param isDefault True to notify default listeners, not default services.
*/
private void notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event,
boolean isDefault) {
for (int i = 0, count = mServices.size(); i < count; i++) {
Service service = mServices.get(i);
if (service.mIsDefault == isDefault) {
if (canDispathEventLocked(service, event, mHandledFeedbackTypes)) {
mHandledFeedbackTypes |= service.mFeedbackType;
notifyAccessibilityServiceDelayedLocked(service, event);
}
}
}
}
/**
* Performs an {@link AccessibilityService} delayed notification. The delay is configurable
* and denotes the period after the last event before notifying the service.
*
* @param service The service.
* @param event The event.
*/
private void notifyAccessibilityServiceDelayedLocked(Service service,
AccessibilityEvent event) {
synchronized (mLock) {
int eventType = event.getEventType();
AccessibilityEvent oldEvent = service.mPendingEvents.get(eventType);
service.mPendingEvents.put(eventType, event);
int what = eventType | (service.mId << 16);
if (oldEvent != null) {
mHandler.removeMessages(what);
tryRecycleLocked(oldEvent);
}
Message message = mHandler.obtainMessage(what, service);
message.arg1 = event.getEventType();
mHandler.sendMessageDelayed(message, service.mNotificationTimeout);
}
}
/**
* Recycles an event if it can be safely recycled. The condition is that no
* not notified service is interested in the event.
*
* @param event The event.
*/
private void tryRecycleLocked(AccessibilityEvent event) {
int eventType = event.getEventType();
List<Service> services = mServices;
// linear in the number of service which is not large
for (int i = 0, count = services.size(); i < count; i++) {
Service service = services.get(i);
if (service.mPendingEvents.get(eventType) == event) {
return;
}
}
event.recycle();
}
/**
* Notifies a service for a scheduled event given the event type.
*
* @param service The service.
* @param eventType The type of the event to dispatch.
*/
private void notifyEventListenerLocked(Service service, int eventType) {
IEventListener listener = service.mServiceInterface;
AccessibilityEvent event = service.mPendingEvents.get(eventType);
try {
listener.onAccessibilityEvent(event);
if (LOGV) {
Log.i(LOG_TAG, "Event " + event + " sent to " + listener);
}
} catch (RemoteException re) {
if (re instanceof DeadObjectException) {
Log.w(LOG_TAG, "Dead " + service.mService + ". Cleaning up.");
synchronized (mLock) {
removeDeadServiceLocked(service);
}
} else {
Log.e(LOG_TAG, "Error during sending " + event + " to " + service.mService, re);
}
}
}
/**
* Removes a dead service.
*
* @param service The service.
* @return True if the service was removed, false otherwise.
*/
private boolean removeDeadServiceLocked(Service service) {
mServices.remove(service);
mHandler.removeMessages(service.mId);
if (LOGV) {
Log.i(LOG_TAG, "Dead service " + service.mService + " removed");
}
if (mServices.isEmpty()) {
mIsEnabled = false;
updateClientsLocked();
}
return true;
}
/**
* Determines if given event can be dispatched to a service based on the package of the
* event source and already notified services for that event type. Specifically, a
* service is notified if it is interested in events from the package and no other service
* providing the same feedback type has been notified. Exception are services the
* provide generic feedback (feedback type left as a safety net for unforeseen feedback
* types) which are always notified.
*
* @param service The potential receiver.
* @param event The event.
* @param handledFeedbackTypes The feedback types for which services have been notified.
* @return True if the listener should be notified, false otherwise.
*/
private boolean canDispathEventLocked(Service service, AccessibilityEvent event,
int handledFeedbackTypes) {
if (!service.isConfigured()) {
return false;
}
if (!service.mService.isBinderAlive()) {
removeDeadServiceLocked(service);
return false;
}
int eventType = event.getEventType();
if ((service.mEventTypes & eventType) != eventType) {
return false;
}
Set<String> packageNames = service.mPackageNames;
CharSequence packageName = event.getPackageName();
if (packageNames.isEmpty() || packageNames.contains(packageName)) {
int feedbackType = service.mFeedbackType;
if ((handledFeedbackTypes & feedbackType) != feedbackType
|| feedbackType == AccessibilityServiceInfo.FEEDBACK_GENERIC) {
return true;
}
}
return false;
}
/**
* Manages services by starting enabled ones and stopping disabled ones.
*/
private void manageServicesLocked() {
populateEnabledServicesLocked(mEnabledServices);
updateServicesStateLocked(mInstalledServices, mEnabledServices);
}
/**
* Unbinds all bound services.
*/
private void unbindAllServicesLocked() {
List<Service> services = mServices;
for (int i = 0, count = services.size(); i < count; i++) {
Service service = services.get(i);
service.unbind();
mComponentNameToServiceMap.remove(service.mComponentName);
}
services.clear();
}
/**
* Populates a list with the {@link ComponentName}s of all enabled
* {@link AccessibilityService}s.
*
* @param enabledServices The list.
*/
private void populateEnabledServicesLocked(Set<ComponentName> enabledServices) {
enabledServices.clear();
String servicesValue = Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (servicesValue != null) {
TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
splitter.setString(servicesValue);
while (splitter.hasNext()) {
ComponentName enabledService = ComponentName.unflattenFromString(splitter.next());
enabledServices.add(enabledService);
}
}
}
/**
* Updates the state of each service by starting (or keeping running) enabled ones and
* stopping the rest.
*
* @param installedServices All installed {@link AccessibilityService}s.
* @param enabledServices The {@link ComponentName}s of the enabled services.
*/
private void updateServicesStateLocked(List<ServiceInfo> installedServices,
Set<ComponentName> enabledServices) {
Map<ComponentName, Service> componentNameToServiceMap = mComponentNameToServiceMap;
List<Service> services = mServices;
for (int i = 0, count = installedServices.size(); i < count; i++) {
ServiceInfo intalledService = installedServices.get(i);
ComponentName componentName = new ComponentName(intalledService.packageName,
intalledService.name);
Service service = componentNameToServiceMap.get(componentName);
if (enabledServices.contains(componentName)) {
if (service == null) {
new Service(componentName).bind();
}
} else {
if (service != null) {
service.unbind();
componentNameToServiceMap.remove(componentName);
services.remove(service);
}
}
}
}
/**
* Updates the state of {@link android.view.accessibility.AccessibilityManager} clients.
*/
private void updateClientsLocked() {
for (int i = 0, count = mClients.size(); i < count; i++) {
try {
mClients.get(i).setEnabled(mIsEnabled);
} catch (RemoteException re) {
mClients.remove(i);
count--;
}
}
}
/**
* This class represents an accessibility service. It stores all per service
* data required for the service management, provides API for starting/stopping the
* service and is responsible for adding/removing the service in the data structures
* for service management. The class also exposes configuration interface that is
* passed to the service it represents as soon it is bound. It also serves as the
* connection for the service.
*/
class Service extends IAccessibilityServiceConnection.Stub implements ServiceConnection {
int mId = 0;
IBinder mService;
IEventListener mServiceInterface;
int mEventTypes;
int mFeedbackType;
Set<String> mPackageNames = new HashSet<String>();
boolean mIsDefault;
long mNotificationTimeout;
boolean mIsActive;
ComponentName mComponentName;
Intent mIntent;
// the events pending events to be dispatched to this service
final SparseArray<AccessibilityEvent> mPendingEvents =
new SparseArray<AccessibilityEvent>();
Service(ComponentName componentName) {
mId = sIdCounter++;
mComponentName = componentName;
mIntent = new Intent().setComponent(mComponentName);
}
/**
* Binds to the accessibility service.
*/
public void bind() {
if (mService == null) {
mContext.bindService(mIntent, this, Context.BIND_AUTO_CREATE);
}
}
/**
* Unbinds form the accessibility service and removes it from the data
* structures for service management.
*/
public void unbind() {
if (mService != null) {
mContext.unbindService(this);
}
}
/**
* Returns if the service is configured i.e. at least event types of interest
* and feedback type must be set.
*
* @return True if the service is configured, false otherwise.
*/
public boolean isConfigured() {
return (mEventTypes != 0 && mFeedbackType != 0);
}
public void setServiceInfo(AccessibilityServiceInfo info) {
mCaller.obtainMessageOO(DO_SET_SERVICE_INFO, info, this).sendToTarget();
}
public void onServiceConnected(ComponentName componentName, IBinder service) {
mService = service;
mServiceInterface = IEventListener.Stub.asInterface(service);
try {
mServiceInterface.setConnection(this);
synchronized (mLock) {
if (!mServices.contains(this)) {
mServices.add(this);
mComponentNameToServiceMap.put(componentName, this);
}
}
} catch (RemoteException re) {
Log.w(LOG_TAG, "Error while setting Controller for service: " + service, re);
}
}
public void onServiceDisconnected(ComponentName componentName) {
synchronized (mLock) {
Service service = mComponentNameToServiceMap.remove(componentName);
mServices.remove(service);
}
}
}
}

View File

@@ -16,6 +16,10 @@
package com.android.server;
import com.android.server.status.IconData;
import com.android.server.status.NotificationData;
import com.android.server.status.StatusBarService;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.INotificationManager;
@@ -30,33 +34,29 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.media.AudioManager;
import android.media.AsyncPlayer;
import android.media.RingtoneManager;
import android.media.AudioManager;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Binder;
import android.os.RemoteException;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Power;
import android.os.RemoteException;
import android.os.Vibrator;
import android.provider.Settings;
import android.util.Config;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
import com.android.server.status.IconData;
import com.android.server.status.NotificationData;
import com.android.server.status.StatusBarService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.io.IOException;
class NotificationManagerService extends INotificationManager.Stub
{
@@ -98,7 +98,7 @@ class NotificationManagerService extends INotificationManager.Stub
private boolean mBatteryLow;
private boolean mBatteryFull;
private NotificationRecord mLedNotification;
private static final int BATTERY_LOW_ARGB = 0xFFFF0000; // Charging Low - red solid on
private static final int BATTERY_MEDIUM_ARGB = 0xFFFFFF00; // Charging - orange solid on
private static final int BATTERY_FULL_ARGB = 0xFF00FF00; // Charging Full - green solid on
@@ -594,6 +594,9 @@ class NotificationManagerService extends INotificationManager.Stub
Binder.restoreCallingIdentity(identity);
}
}
sendAccessibilityEventTypeNotificationChangedDoCheck(notification, pkg);
} else {
if (old != null && old.statusBarKey != null) {
long identity = Binder.clearCallingIdentity();
@@ -676,6 +679,26 @@ class NotificationManagerService extends INotificationManager.Stub
idOut[0] = id;
}
private void sendAccessibilityEventTypeNotificationChangedDoCheck(Notification notification,
CharSequence packageName) {
AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
if (!manager.isEnabled()) {
return;
}
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
event.setPackageName(packageName);
event.setClassName(Notification.class.getName());
event.setParcelableData(notification);
CharSequence tickerText = notification.tickerText;
if (!TextUtils.isEmpty(tickerText)) {
event.getText().add(tickerText);
}
manager.sendAccessibilityEvent(event);
}
private void cancelNotificationLocked(NotificationRecord r) {
// status bar
if (r.notification.icon != 0) {

View File

@@ -227,6 +227,14 @@ class ServerThread extends Thread {
Log.e(TAG, "Failure starting Connectivity Service", e);
}
try {
Log.i(TAG, "Starting Accessibility Manager.");
ServiceManager.addService(Context.ACCESSIBILITY_SERVICE,
new AccessibilityManagerService(context));
} catch (Throwable e) {
Log.e(TAG, "Failure starting Accessibility Manager", e);
}
try {
Log.i(TAG, "Starting Notification Manager.");
ServiceManager.addService(Context.NOTIFICATION_SERVICE,

View File

@@ -20,6 +20,9 @@
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.HARDWARE_TEST" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.ACCESSIBILITY_EVENT_VIEW_TYPES" />
<uses-permission android:name="android.permission.ACCESSIBILITY_EVENT_TRANSITION_TYPES" />
<uses-permission android:name="android.permission.ACCESSIBILITY_EVENT_NOTIFICATION_TYPES" />
<application android:theme="@style/Theme">
<uses-library android:name="android.test.runner" />
@@ -939,7 +942,7 @@
</intent-filter>
</activity>
<activity android:name="android.widget.AutoCompleteTextViewSimple"
<activity android:name="android.widget.AutoCompleteTextViewSimple"
android:label="AutoCompleteTextViewSimple">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -947,6 +950,12 @@
</intent-filter>
</activity>
<service android:name=".accessibility.AccessibilityTestService">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
</service>
</application>
</manifest>

View File

@@ -0,0 +1,167 @@
/*
* 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 com.android.frameworktest.accessibility;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Notification;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import java.util.Timer;
import java.util.TimerTask;
/**
* This class text the accessibility framework end to end.
* <p>
* Note: Since accessibility is provided by {@link AccessibilityService}s we create one,
* and it generates an event and an interruption dispatching them through the
* {@link AccessibilityManager}. We verify the received result. To trigger the test
* go to Settings->Accessibility and select the enable accessibility check and then
* select the check for this service (same name as the class).
*/
public class AccessibilityTestService extends AccessibilityService {
private static final String LOG_TAG = "AccessibilityTestService";
private static final String CLASS_NAME = "foo.bar.baz.Test";
private static final String PACKAGE_NAME = "foo.bar.baz";
private static final String TEXT = "Some stuff";
private static final String BEFORE_TEXT = "Some other stuff";
private static final String CONTENT_DESCRIPTION = "Content description";
private static final int ITEM_COUNT = 10;
private static final int CURRENT_ITEM_INDEX = 1;
private static final int INTERRUPT_INVOCATION_TYPE = 0x00000200;
private static final int FROM_INDEX = 1;
private static final int ADDED_COUNT = 2;
private static final int REMOVED_COUNT = 1;
private static final int NOTIFICATION_TIMEOUT_MILLIS = 80;
private int mReceivedResult;
private Timer mTimer = new Timer();
@Override
public void onServiceConnected() {
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
info.feedbackType = AccessibilityServiceInfo.FEEDBACK_AUDIBLE;
info.notificationTimeout = NOTIFICATION_TIMEOUT_MILLIS;
info.flags &= AccessibilityServiceInfo.DEFAULT;
setServiceInfo(info);
// we need to wait until the system picks our configuration
// otherwise it will not notify us
mTimer.schedule(new TimerTask() {
@Override
public void run() {
try {
testAccessibilityEventDispatching();
testInterrupt();
} catch (Exception e) {
Log.e(LOG_TAG, "Error in testing Accessibility feature", e);
}
}
}, 1000);
}
/**
* Check here if the event we received is actually the one we sent.
*/
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
assert(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED == event.getEventType());
assert(event != null);
assert(event.getEventTime() > 0);
assert(CLASS_NAME.equals(event.getClassName()));
assert(PACKAGE_NAME.equals(event.getPackageName()));
assert(1 == event.getText().size());
assert(TEXT.equals(event.getText().get(0)));
assert(BEFORE_TEXT.equals(event.getBeforeText()));
assert(event.isChecked());
assert(CONTENT_DESCRIPTION.equals(event.getContentDescription()));
assert(ITEM_COUNT == event.getItemCount());
assert(CURRENT_ITEM_INDEX == event.getCurrentItemIndex());
assert(event.isEnabled());
assert(event.isPassword());
assert(FROM_INDEX == event.getFromIndex());
assert(ADDED_COUNT == event.getAddedCount());
assert(REMOVED_COUNT == event.getRemovedCount());
assert(event.getParcelableData() != null);
assert(1 == ((Notification) event.getParcelableData()).icon);
// set the type of the receved request
mReceivedResult = AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED;
}
/**
* Set a flag that we received the interrupt request.
*/
@Override
public void onInterrupt() {
// set the type of the receved request
mReceivedResult = INTERRUPT_INVOCATION_TYPE;
}
/**
* If an {@link AccessibilityEvent} is sent and received correctly.
*/
public void testAccessibilityEventDispatching() throws Exception {
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
assert(event != null);
event.setClassName(CLASS_NAME);
event.setPackageName(PACKAGE_NAME);
event.getText().add(TEXT);
event.setBeforeText(BEFORE_TEXT);
event.setChecked(true);
event.setContentDescription(CONTENT_DESCRIPTION);
event.setItemCount(ITEM_COUNT);
event.setCurrentItemIndex(CURRENT_ITEM_INDEX);
event.setEnabled(true);
event.setPassword(true);
event.setFromIndex(FROM_INDEX);
event.setAddedCount(ADDED_COUNT);
event.setRemovedCount(REMOVED_COUNT);
event.setParcelableData(new Notification(1, "Foo", 1234));
AccessibilityManager.getInstance(this).sendAccessibilityEvent(event);
assert(mReceivedResult == event.getEventType());
Log.i(LOG_TAG, "AccessibilityTestService#testAccessibilityEventDispatching: Success");
}
/**
* If accessibility feedback interruption is triggered and received correctly.
*/
public void testInterrupt() throws Exception {
AccessibilityManager.getInstance(this).interrupt();
assert(INTERRUPT_INVOCATION_TYPE == mReceivedResult);
Log.i(LOG_TAG, "AccessibilityTestService#testInterrupt: Success");
}
}

View File

@@ -0,0 +1,81 @@
/**
* 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 com.android.frameworktest.accessibility;
import android.test.suitebuilder.annotation.MediumTest;
import android.view.accessibility.AccessibilityEvent;
import junit.framework.TestCase;
/**
* This class exercises the caching and recycling of {@link AccessibilityEvent}s.
*/
public class RecycleAccessibilityEventTest extends TestCase {
private static final String CLASS_NAME = "foo.bar.baz.Test";
private static final String PACKAGE_NAME = "foo.bar.baz";
private static final String TEXT = "Some stuff";
private static final String CONTENT_DESCRIPTION = "Content description";
private static final int ITEM_COUNT = 10;
private static final int CURRENT_ITEM_INDEX = 1;
private static final int FROM_INDEX = 1;
private static final int ADDED_COUNT = 2;
private static final int REMOVED_COUNT = 1;
/**
* If an {@link AccessibilityEvent} is marshaled/unmarshaled correctly
*/
@MediumTest
public void testAccessibilityEventViewTextChangedType() {
AccessibilityEvent first =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
assertNotNull(first);
first.setClassName(CLASS_NAME);
first.setPackageName(PACKAGE_NAME);
first.getText().add(TEXT);
first.setFromIndex(FROM_INDEX);
first.setAddedCount(ADDED_COUNT);
first.setRemovedCount(REMOVED_COUNT);
first.setChecked(true);
first.setContentDescription(CONTENT_DESCRIPTION);
first.setItemCount(ITEM_COUNT);
first.setCurrentItemIndex(CURRENT_ITEM_INDEX);
first.setEnabled(true);
first.setPassword(true);
first.recycle();
assertNotNull(first);
assertNull(first.getClassName());
assertNull(first.getPackageName());
assertEquals(0, first.getText().size());
assertFalse(first.isChecked());
assertNull(first.getContentDescription());
assertEquals(0, first.getItemCount());
assertEquals(AccessibilityEvent.INVALID_POSITION, first.getCurrentItemIndex());
assertFalse(first.isEnabled());
assertFalse(first.isPassword());
assertEquals(0, first.getFromIndex());
assertEquals(0, first.getAddedCount());
assertEquals(0, first.getRemovedCount());
// get another event from the pool (this must be the recycled first)
AccessibilityEvent second = AccessibilityEvent.obtain();
assertEquals(first, second);
}
}