Touch exploration state set to clients asynchronously and depended on talking service being enabled.

1. Upon registration of an accessibility client the latter received only
   the accessiiblity state and waiting for the touch exploration state
   to be sent by the system in async manner. This led the very first
   check of touch exploration state is checked a wrong value to be reported.
   Now a state of the accessibility layer is returned to the client
   upon registration.

2. Removing the dependency on talking accessibility service to be enabled
   for getting into touch exploration mode. What if the user wants to use
   an accessibility service that shows a dialog with the text of the touched
   view?

bug:5051546

Change-Id: Ib377babb3f560929ee73bd3d8b0d277341ba23f7
This commit is contained in:
Svetoslav Ganov
2011-07-21 11:35:03 -07:00
parent d24cd90486
commit 00aabf7d18
12 changed files with 121 additions and 100 deletions

View File

@@ -1828,6 +1828,7 @@ package android.accessibilityservice {
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
field public static final int DEFAULT = 1; // 0x1
field public static final int FEEDBACK_ALL_MASK = -1; // 0xffffffff
field public static final int FEEDBACK_AUDIBLE = 4; // 0x4
field public static final int FEEDBACK_GENERIC = 16; // 0x10
field public static final int FEEDBACK_HAPTIC = 2; // 0x2
@@ -16709,7 +16710,7 @@ package android.provider {
field public static final java.lang.String SELECTED_INPUT_METHOD_SUBTYPE = "selected_input_method_subtype";
field public static final java.lang.String SETTINGS_CLASSNAME = "settings_classname";
field public static final java.lang.String SYS_PROP_SETTING_VERSION = "sys.settings_secure_version";
field public static final java.lang.String TOUCH_EXPLORATION_REQUESTED = "touch_exploration_requested";
field public static final java.lang.String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
field public static final java.lang.String TTS_DEFAULT_COUNTRY = "tts_default_country";
field public static final java.lang.String TTS_DEFAULT_LANG = "tts_default_lang";
field public static final java.lang.String TTS_DEFAULT_PITCH = "tts_default_pitch";

View File

@@ -74,6 +74,17 @@ public class AccessibilityServiceInfo implements Parcelable {
*/
public static final int FEEDBACK_GENERIC = 0x0000010;
/**
* Mask for all feedback types.
*
* @see #FEEDBACK_SPOKEN
* @see #FEEDBACK_HAPTIC
* @see #FEEDBACK_AUDIBLE
* @see #FEEDBACK_VISUAL
* @see #FEEDBACK_GENERIC
*/
public static final int FEEDBACK_ALL_MASK = 0xFFFFFFFF;
/**
* 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

View File

@@ -2691,11 +2691,9 @@ public final class Settings {
public static final String ACCESSIBILITY_ENABLED = "accessibility_enabled";
/**
* If touch exploration is requested. Touch exploration is enabled if it is
* requested by this setting, accessibility is enabled and there is at least
* one enabled accessibility serivce that provides spoken feedback.
* If touch exploration is enabled.
*/
public static final String TOUCH_EXPLORATION_REQUESTED = "touch_exploration_requested";
public static final String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
/**
* List of the enabled accessibility providers.

View File

@@ -67,13 +67,17 @@ public final class AccessibilityManager {
private static final String LOG_TAG = "AccessibilityManager";
/** @hide */
public static final int STATE_FLAG_ACCESSIBILITY_ENABLED = 0x00000001;
/** @hide */
public static final int STATE_FLAG_TOUCH_EXPLORATION_ENABLED = 0x00000002;
static final Object sInstanceSync = new Object();
private static AccessibilityManager sInstance;
private static final int DO_SET_ACCESSIBILITY_ENABLED = 10;
private static final int DO_SET_TOUCH_EXPLORATION_ENABLED = 20;
private static final int DO_SET_STATE = 10;
final IAccessibilityManager mService;
@@ -100,13 +104,8 @@ public final class AccessibilityManager {
}
final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() {
public void setEnabled(boolean enabled) {
mHandler.obtainMessage(DO_SET_ACCESSIBILITY_ENABLED, enabled ? 1 : 0, 0).sendToTarget();
}
public void setTouchExplorationEnabled(boolean enabled) {
mHandler.obtainMessage(DO_SET_TOUCH_EXPLORATION_ENABLED,
enabled ? 1 : 0, 0).sendToTarget();
public void setState(int state) {
mHandler.obtainMessage(DO_SET_STATE, state, 0).sendToTarget();
}
};
@@ -119,14 +118,8 @@ public final class AccessibilityManager {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case DO_SET_ACCESSIBILITY_ENABLED :
final boolean isAccessibilityEnabled = (message.arg1 == 1);
setAccessibilityState(isAccessibilityEnabled);
return;
case DO_SET_TOUCH_EXPLORATION_ENABLED :
synchronized (mHandler) {
mIsTouchExplorationEnabled = (message.arg1 == 1);
}
case DO_SET_STATE :
setState(message.arg1);
return;
default :
Log.w(LOG_TAG, "Unknown message type: " + message.what);
@@ -163,8 +156,8 @@ public final class AccessibilityManager {
mService = service;
try {
final boolean isEnabled = mService.addClient(mClient);
setAccessibilityState(isEnabled);
final int stateFlags = mService.addClient(mClient);
setState(stateFlags);
} catch (RemoteException re) {
Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
}
@@ -340,6 +333,17 @@ public final class AccessibilityManager {
return mAccessibilityStateChangeListeners.remove(listener);
}
/**
* Sets the current state.
*
* @param stateFlags The state flags.
*/
private void setState(int stateFlags) {
final boolean accessibilityEnabled = (stateFlags & STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
setAccessibilityState(accessibilityEnabled);
mIsTouchExplorationEnabled = (stateFlags & STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0;
}
/**
* Sets the enabled state.
*

View File

@@ -294,14 +294,14 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
* Gets the unique id identifying this node's parent.
* Gets the parent.
* <p>
* <strong>Note:</strong> It is a client responsibility to recycle the
* received info by calling {@link AccessibilityNodeInfo#recycle()}
* to avoid creating of multiple instances.
* </p>
*
* @return The node's patent id.
* @return The parent.
*/
public AccessibilityNodeInfo getParent() {
enforceSealed();

View File

@@ -34,7 +34,7 @@ import android.view.IWindow;
*/
interface IAccessibilityManager {
boolean addClient(IAccessibilityManagerClient client);
int addClient(IAccessibilityManagerClient client);
boolean sendAccessibilityEvent(in AccessibilityEvent uiEvent);

View File

@@ -24,7 +24,5 @@ package android.view.accessibility;
*/
oneway interface IAccessibilityManagerClient {
void setEnabled(boolean enabled);
void setTouchExplorationEnabled(boolean enabled);
void setState(int stateFlags);
}

View File

@@ -2281,6 +2281,8 @@
<flag name="feedbackVisual" value="0x00000008" />
<!-- Provides {@link android.accessibilityservice.AccessibilityServiceInfo#FEEDBACK_GENERIC} feedback. -->
<flag name="feedbackGeneric" value="0x00000010" />
<!-- Provides {@link android.accessibilityservice.AccessibilityServiceInfo#FEEDBACK_ALL_MASK} feedback. -->
<flag name="feedbackAllMask" value="0xffffffff" />
</attr>
<!-- The minimal period in milliseconds between two accessibility events of the same type
are sent to this serivce. This setting can be changed at runtime by calling

View File

@@ -65,13 +65,13 @@ public class RecycleAccessibilityEventTest extends TestCase {
assertEquals(0, first.getText().size());
assertFalse(first.isChecked());
assertNull(first.getContentDescription());
assertEquals(0, first.getItemCount());
assertEquals(-1, 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());
assertEquals(-1, first.getFromIndex());
assertEquals(-1, first.getAddedCount());
assertEquals(-1, first.getRemovedCount());
// get another event from the pool (this must be the recycled first)
AccessibilityEvent second = AccessibilityEvent.obtain();

View File

@@ -49,6 +49,7 @@ import android.util.SparseArray;
import android.view.IWindow;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
@@ -129,10 +130,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private boolean mIsAccessibilityEnabled;
private boolean mIsTouchExplorationRequested;
private AccessibilityInputFilter mInputFilter;
private boolean mHasInputFilter;
private final List<AccessibilityServiceInfo> mEnabledServicesForFeedbackTempList = new ArrayList<AccessibilityServiceInfo>();
private boolean mIsTouchExplorationEnabled;
@@ -189,7 +190,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
manageServicesLocked();
}
}
@Override
public boolean onHandleForceStop(Intent intent, String[] packages,
int uid, boolean doit) {
@@ -236,17 +237,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mIsAccessibilityEnabled = Settings.Secure.getInt(
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
// if accessibility is enabled inform our clients we are on
if (mIsAccessibilityEnabled) {
sendAccessibilityEnabledToClientsLocked();
}
manageServicesLocked();
// get touch exploration enabled setting on boot
mIsTouchExplorationRequested = Settings.Secure.getInt(
mIsTouchExplorationEnabled = Settings.Secure.getInt(
mContext.getContentResolver(),
Settings.Secure.TOUCH_EXPLORATION_REQUESTED, 0) == 1;
updateTouchExplorationEnabledLocked();
Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1;
updateInputFilterLocked();
sendStateToClientsLocked();
}
return;
@@ -288,13 +288,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
} else {
unbindAllServicesLocked();
}
sendAccessibilityEnabledToClientsLocked();
sendStateToClientsLocked();
}
}
});
Uri touchExplorationRequestedUri = Settings.Secure.getUriFor(
Settings.Secure.TOUCH_EXPLORATION_REQUESTED);
Settings.Secure.TOUCH_EXPLORATION_ENABLED);
contentResolver.registerContentObserver(touchExplorationRequestedUri, false,
new ContentObserver(new Handler()) {
@Override
@@ -302,10 +302,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
super.onChange(selfChange);
synchronized (mLock) {
mIsTouchExplorationRequested = Settings.Secure.getInt(
mIsTouchExplorationEnabled = Settings.Secure.getInt(
mContext.getContentResolver(),
Settings.Secure.TOUCH_EXPLORATION_REQUESTED, 0) == 1;
updateTouchExplorationEnabledLocked();
Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1;
updateInputFilterLocked();
sendStateToClientsLocked();
}
}
});
@@ -325,7 +326,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
});
}
public boolean addClient(IAccessibilityManagerClient client) throws RemoteException {
public int addClient(IAccessibilityManagerClient client) throws RemoteException {
synchronized (mLock) {
final IAccessibilityManagerClient addedClient = client;
mClients.add(addedClient);
@@ -338,7 +339,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
}, 0);
return mIsAccessibilityEnabled;
return getState();
}
}
@@ -628,7 +629,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
service.linkToOwnDeath();
mServices.add(service);
mComponentNameToServiceMap.put(service.mComponentName, service);
updateTouchExplorationEnabledLocked();
updateInputFilterLocked();
} catch (RemoteException e) {
/* do nothing */
}
@@ -648,7 +649,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mComponentNameToServiceMap.remove(service.mComponentName);
mHandler.removeMessages(service.mId);
service.unlinkToOwnDeath();
updateTouchExplorationEnabledLocked();
updateInputFilterLocked();
return removed;
}
@@ -781,12 +782,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
/**
* Updates the state of {@link android.view.accessibility.AccessibilityManager} clients.
* Sends the state to the clients.
*/
private void sendAccessibilityEnabledToClientsLocked() {
private void sendStateToClientsLocked() {
final int state = getState();
for (int i = 0, count = mClients.size(); i < count; i++) {
try {
mClients.get(i).setEnabled(mIsAccessibilityEnabled);
mClients.get(i).setState(state);
} catch (RemoteException re) {
mClients.remove(i);
count--;
@@ -796,48 +798,39 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
/**
* Sends the touch exploration state to clients.
* Gets the current state as a set of flags.
*
* @return The state.
*/
private void sendTouchExplorationEnabledToClientsLocked() {
for (int i = 0, count = mClients.size(); i < count; i++) {
try {
mClients.get(i).setTouchExplorationEnabled(mIsTouchExplorationEnabled);
} catch (RemoteException re) {
mClients.remove(i);
count--;
i--;
}
private int getState() {
int state = 0;
if (mIsAccessibilityEnabled) {
state |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
}
// Touch exploration relies on enabled accessibility.
if (mIsAccessibilityEnabled && mIsTouchExplorationEnabled) {
state |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
}
return state;
}
/**
* Updates the touch exploration state. Touch exploration is enabled if it
* is requested, accessibility is on and there is at least one enabled
* accessibility service providing spoken feedback.
* Updates the touch exploration state.
*/
private void updateTouchExplorationEnabledLocked() {
if (mIsAccessibilityEnabled && mIsTouchExplorationRequested) {
final boolean hasSpeakingServicesEnabled = !getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty();
if (!mIsTouchExplorationEnabled) {
if (!hasSpeakingServicesEnabled) {
return;
}
private void updateInputFilterLocked() {
if (mIsAccessibilityEnabled && mIsTouchExplorationEnabled) {
if (!mHasInputFilter) {
mHasInputFilter = true;
if (mInputFilter == null) {
mInputFilter = new AccessibilityInputFilter(mContext);
}
mWindowManagerService.setInputFilter(mInputFilter);
mIsTouchExplorationEnabled = true;
sendTouchExplorationEnabledToClientsLocked();
return;
} else if (hasSpeakingServicesEnabled) {
return;
}
return;
}
if (mIsTouchExplorationEnabled) {
if (mHasInputFilter) {
mHasInputFilter = false;
mWindowManagerService.setInputFilter(null);
mIsTouchExplorationEnabled = false;
sendTouchExplorationEnabledToClientsLocked();
}
}

View File

@@ -29,12 +29,13 @@ import android.provider.Settings;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
import android.view.accessibility.IAccessibilityManagerClient;
/**
* This test exercises the
* {@link com.android.server.AccessibilityManagerService} by mocking the
* {@link com.android.server.accessibility.AccessibilityManagerService} by mocking the
* {@link android.view.accessibility.AccessibilityManager} which talks to to the
* service. The service itself is interacting with the platform. Note: Testing
* the service in full isolation would require significant amount of work for
@@ -97,7 +98,9 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient();
// invoke the method under test
boolean enabledAccessibilityDisabled = mManagerService.addClient(mockClient);
final int stateFlagsDisabled = mManagerService.addClient(mockClient);
boolean enabledAccessibilityDisabled =
(stateFlagsDisabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
// check expected result
assertFalse("The client must be disabled since accessibility is disabled.",
@@ -107,7 +110,10 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
ensureAccessibilityEnabled(mContext, true);
// invoke the method under test
boolean enabledAccessibilityEnabled = mManagerService.addClient(mockClient);
final int stateFlagsEnabled = mManagerService.addClient(mockClient);
boolean enabledAccessibilityEnabled =
(stateFlagsEnabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
// check expected result
assertTrue("The client must be enabled since accessibility is enabled.",
@@ -123,7 +129,9 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
MyMockAccessibilityManagerClient mockClient = new MyMockAccessibilityManagerClient();
// invoke the method under test
boolean enabledAccessibilityEnabled = mManagerService.addClient(mockClient);
final int stateFlagsEnabled = mManagerService.addClient(mockClient);
boolean enabledAccessibilityEnabled =
(stateFlagsEnabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
// check expected result
assertTrue("The client must be enabled since accessibility is enabled.",
@@ -133,7 +141,9 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
ensureAccessibilityEnabled(mContext, false);
// invoke the method under test
boolean enabledAccessibilityDisabled = mManagerService.addClient(mockClient);
final int stateFlagsDisabled = mManagerService.addClient(mockClient);
boolean enabledAccessibilityDisabled =
(stateFlagsDisabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0;
// check expected result
assertFalse("The client must be disabled since accessibility is disabled.",
@@ -537,10 +547,10 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase {
* This class is a mock {@link IAccessibilityManagerClient}.
*/
public class MyMockAccessibilityManagerClient extends IAccessibilityManagerClient.Stub {
boolean mIsEnabled;
int mState;
public void setEnabled(boolean enabled) {
mIsEnabled = enabled;
public void setState(int state) {
mState = state;
}
public void setTouchExplorationEnabled(boolean enabled) {

View File

@@ -70,7 +70,8 @@ public class AccessibilityManagerTest extends AndroidTestCase {
// configure the mock service behavior
IAccessibilityManager mockServiceInterface = mMockServiceInterface;
expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true);
expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(
AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED);
expect(mockServiceInterface.getInstalledAccessibilityServiceList()).andReturn(
expectedServices);
replay(mockServiceInterface);
@@ -91,7 +92,8 @@ public class AccessibilityManagerTest extends AndroidTestCase {
public void testInterrupt() throws Exception {
// configure the mock service behavior
IAccessibilityManager mockServiceInterface = mMockServiceInterface;
expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true);
expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(
AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED);
mockServiceInterface.interrupt();
replay(mockServiceInterface);
@@ -107,7 +109,8 @@ public class AccessibilityManagerTest extends AndroidTestCase {
public void testIsEnabled() throws Exception {
// configure the mock service behavior
IAccessibilityManager mockServiceInterface = mMockServiceInterface;
expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true);
expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(
AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED);
replay(mockServiceInterface);
// invoke the method under test
@@ -118,7 +121,7 @@ public class AccessibilityManagerTest extends AndroidTestCase {
assertTrue("Must be enabled since the mock service is enabled", isEnabledServiceEnabled);
// disable accessibility
manager.getClient().setEnabled(false);
manager.getClient().setState(0);
// wait for the asynchronous IBinder call to complete
Thread.sleep(TIMEOUT_BINDER_CALL);
@@ -141,7 +144,8 @@ public class AccessibilityManagerTest extends AndroidTestCase {
// configure the mock service behavior
IAccessibilityManager mockServiceInterface = mMockServiceInterface;
expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(true);
expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(
AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED);
expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent)))
.andReturn(true);
expect(mockServiceInterface.sendAccessibilityEvent(eqAccessibilityEvent(sentEvent)))
@@ -176,7 +180,7 @@ public class AccessibilityManagerTest extends AndroidTestCase {
// configure the mock service behavior
IAccessibilityManager mockServiceInterface = mMockServiceInterface;
expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(false);
expect(mockServiceInterface.addClient(anyIAccessibilityManagerClient())).andReturn(0);
replay(mockServiceInterface);
// invoke the method under test (accessibility disabled)