Merge "Add API for IME control by Accessibility Services."
diff --git a/api/current.txt b/api/current.txt
index a501ce7..0f90986 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2625,6 +2625,7 @@
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
+ method public final android.accessibilityservice.AccessibilityService.SoftKeyboardController getSoftKeyboardController();
method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public final android.os.IBinder onBind(android.content.Intent);
@@ -2683,6 +2684,18 @@
method public abstract void onMagnificationChanged(android.accessibilityservice.AccessibilityService.MagnificationController, android.graphics.Region, float, float, float);
}
+ public static final class AccessibilityService.SoftKeyboardController {
+ method public void addOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
+ method public void addOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener, android.os.Handler);
+ method public int getShowMode();
+ method public boolean removeOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
+ method public boolean setShowMode(int);
+ }
+
+ public static abstract interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener {
+ method public abstract void onShowModeChanged(android.accessibilityservice.AccessibilityService.SoftKeyboardController, int);
+ }
+
public class AccessibilityServiceInfo implements android.os.Parcelable {
ctor public AccessibilityServiceInfo();
method public static java.lang.String capabilityToString(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index dc8e5da..1091ff1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2727,6 +2727,7 @@
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
+ method public final android.accessibilityservice.AccessibilityService.SoftKeyboardController getSoftKeyboardController();
method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public final android.os.IBinder onBind(android.content.Intent);
@@ -2785,6 +2786,18 @@
method public abstract void onMagnificationChanged(android.accessibilityservice.AccessibilityService.MagnificationController, android.graphics.Region, float, float, float);
}
+ public static final class AccessibilityService.SoftKeyboardController {
+ method public void addOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
+ method public void addOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener, android.os.Handler);
+ method public int getShowMode();
+ method public boolean removeOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
+ method public boolean setShowMode(int);
+ }
+
+ public static abstract interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener {
+ method public abstract void onShowModeChanged(android.accessibilityservice.AccessibilityService.SoftKeyboardController, int);
+ }
+
public class AccessibilityServiceInfo implements android.os.Parcelable {
ctor public AccessibilityServiceInfo();
method public static java.lang.String capabilityToString(int);
diff --git a/api/test-current.txt b/api/test-current.txt
index d400113..2c5cc3a 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2625,6 +2625,7 @@
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
+ method public final android.accessibilityservice.AccessibilityService.SoftKeyboardController getSoftKeyboardController();
method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public final android.os.IBinder onBind(android.content.Intent);
@@ -2683,6 +2684,18 @@
method public abstract void onMagnificationChanged(android.accessibilityservice.AccessibilityService.MagnificationController, android.graphics.Region, float, float, float);
}
+ public static final class AccessibilityService.SoftKeyboardController {
+ method public void addOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
+ method public void addOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener, android.os.Handler);
+ method public int getShowMode();
+ method public boolean removeOnShowModeChangedListener(android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
+ method public boolean setShowMode(int);
+ }
+
+ public static abstract interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener {
+ method public abstract void onShowModeChanged(android.accessibilityservice.AccessibilityService.SoftKeyboardController, int);
+ }
+
public class AccessibilityServiceInfo implements android.os.Parcelable {
ctor public AccessibilityServiceInfo();
method public static java.lang.String capabilityToString(int);
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index be93926..c96cca2 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -17,6 +17,7 @@
package android.accessibilityservice;
import android.accessibilityservice.GestureDescription.MotionEventGenerator;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Service;
@@ -29,6 +30,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
@@ -46,6 +48,8 @@
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -363,9 +367,26 @@
public boolean onKeyEvent(KeyEvent event);
public void onMagnificationChanged(@NonNull Region region,
float scale, float centerX, float centerY);
+ public void onSoftKeyboardShowModeChanged(int showMode);
public void onPerformGestureResult(int sequence, boolean completedSuccessfully);
}
+ /**
+ * Annotations for Soft Keyboard show modes so tools can catch invalid show modes.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SHOW_MODE_AUTO, SHOW_MODE_HIDDEN})
+ public @interface SoftKeyboardShowMode {};
+ /**
+ * @hide
+ */
+ public static final int SHOW_MODE_AUTO = 0;
+ /**
+ * @hide
+ */
+ public static final int SHOW_MODE_HIDDEN = 1;
+
private int mConnectionId;
private AccessibilityServiceInfo mInfo;
@@ -375,6 +396,7 @@
private WindowManager mWindowManager;
private MagnificationController mMagnificationController;
+ private SoftKeyboardController mSoftKeyboardController;
private int mGestureStatusCallbackSequence;
@@ -563,10 +585,12 @@
*/
@NonNull
public final MagnificationController getMagnificationController() {
- if (mMagnificationController == null) {
- mMagnificationController = new MagnificationController(this);
+ synchronized (mLock) {
+ if (mMagnificationController == null) {
+ mMagnificationController = new MagnificationController(this, mLock);
+ }
+ return mMagnificationController;
}
- return mMagnificationController;
}
/**
@@ -670,17 +694,21 @@
* first magnification listener.
*/
private ArrayMap<OnMagnificationChangedListener, Handler> mListeners;
+ private final Object mLock;
- MagnificationController(@NonNull AccessibilityService service) {
+ MagnificationController(@NonNull AccessibilityService service, @NonNull Object lock) {
mService = service;
+ mLock = lock;
}
/**
* Called when the service is connected.
*/
void onServiceConnected() {
- if (mListeners != null && !mListeners.isEmpty()) {
- setMagnificationCallbackEnabled(true);
+ synchronized (mLock) {
+ if (mListeners != null && !mListeners.isEmpty()) {
+ setMagnificationCallbackEnabled(true);
+ }
}
}
@@ -707,17 +735,19 @@
*/
public void addListener(@NonNull OnMagnificationChangedListener listener,
@Nullable Handler handler) {
- if (mListeners == null) {
- mListeners = new ArrayMap<>();
- }
+ synchronized (mLock) {
+ if (mListeners == null) {
+ mListeners = new ArrayMap<>();
+ }
- final boolean shouldEnableCallback = mListeners.isEmpty();
- mListeners.put(listener, handler);
+ final boolean shouldEnableCallback = mListeners.isEmpty();
+ mListeners.put(listener, handler);
- if (shouldEnableCallback) {
- // This may fail if the service is not connected yet, but if we
- // still have listeners when it connects then we can try again.
- setMagnificationCallbackEnabled(true);
+ if (shouldEnableCallback) {
+ // This may fail if the service is not connected yet, but if we
+ // still have listeners when it connects then we can try again.
+ setMagnificationCallbackEnabled(true);
+ }
}
}
@@ -734,19 +764,21 @@
return false;
}
- final int keyIndex = mListeners.indexOfKey(listener);
- final boolean hasKey = keyIndex >= 0;
- if (hasKey) {
- mListeners.removeAt(keyIndex);
- }
+ synchronized (mLock) {
+ final int keyIndex = mListeners.indexOfKey(listener);
+ final boolean hasKey = keyIndex >= 0;
+ if (hasKey) {
+ mListeners.removeAt(keyIndex);
+ }
- if (hasKey && mListeners.isEmpty()) {
- // We just removed the last listener, so we don't need
- // callbacks from the service anymore.
- setMagnificationCallbackEnabled(false);
- }
+ if (hasKey && mListeners.isEmpty()) {
+ // We just removed the last listener, so we don't need
+ // callbacks from the service anymore.
+ setMagnificationCallbackEnabled(false);
+ }
- return hasKey;
+ return hasKey;
+ }
}
private void setMagnificationCallbackEnabled(boolean enabled) {
@@ -768,17 +800,19 @@
*/
void dispatchMagnificationChanged(final @NonNull Region region, final float scale,
final float centerX, final float centerY) {
- if (mListeners == null || mListeners.isEmpty()) {
- Slog.d(LOG_TAG, "Received magnification changed "
- + "callback with no listeners registered!");
- setMagnificationCallbackEnabled(false);
- return;
- }
+ final ArrayMap<OnMagnificationChangedListener, Handler> entries;
+ synchronized (mLock) {
+ if (mListeners == null || mListeners.isEmpty()) {
+ Slog.d(LOG_TAG, "Received magnification changed "
+ + "callback with no listeners registered!");
+ setMagnificationCallbackEnabled(false);
+ return;
+ }
- // Listeners may remove themselves. Perform a shallow copy to avoid
- // concurrent modification.
- final ArrayMap<OnMagnificationChangedListener, Handler> entries =
- new ArrayMap<>(mListeners);
+ // Listeners may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mListeners);
+ }
for (int i = 0, count = entries.size(); i < count; i++) {
final OnMagnificationChangedListener listener = entries.keyAt(i);
@@ -1011,6 +1045,243 @@
}
/**
+ * Returns the soft keyboard controller, which may be used to query and modify the soft keyboard
+ * show mode.
+ *
+ * @return the soft keyboard controller
+ */
+ @NonNull
+ public final SoftKeyboardController getSoftKeyboardController() {
+ synchronized (mLock) {
+ if (mSoftKeyboardController == null) {
+ mSoftKeyboardController = new SoftKeyboardController(this, mLock);
+ }
+ return mSoftKeyboardController;
+ }
+ }
+
+ private void onSoftKeyboardShowModeChanged(int showMode) {
+ if (mSoftKeyboardController != null) {
+ mSoftKeyboardController.dispatchSoftKeyboardShowModeChanged(showMode);
+ }
+ }
+
+ /**
+ * Used to control and query the soft keyboard show mode.
+ */
+ public static final class SoftKeyboardController {
+ private final AccessibilityService mService;
+
+ /**
+ * Map of listeners to their handlers. Lazily created when adding the first
+ * soft keyboard change listener.
+ */
+ private ArrayMap<OnShowModeChangedListener, Handler> mListeners;
+ private final Object mLock;
+
+ SoftKeyboardController(@NonNull AccessibilityService service, @NonNull Object lock) {
+ mService = service;
+ mLock = lock;
+ }
+
+ /**
+ * Called when the service is connected.
+ */
+ void onServiceConnected() {
+ synchronized(mLock) {
+ if (mListeners != null && !mListeners.isEmpty()) {
+ setSoftKeyboardCallbackEnabled(true);
+ }
+ }
+ }
+
+ /**
+ * Adds the specified change listener to the list of show mode change listeners. The
+ * callback will occur on the service's main thread. Listener is not called on registration.
+ */
+ public void addOnShowModeChangedListener(@NonNull OnShowModeChangedListener listener) {
+ addOnShowModeChangedListener(listener, null);
+ }
+
+ /**
+ * Adds the specified change listener to the list of soft keyboard show mode change
+ * listeners. The callback will occur on the specified {@link Handler}'s thread, or on the
+ * services's main thread if the handler is {@code null}.
+ *
+ * @param listener the listener to add, must be non-null
+ * @param handler the handler on which to callback should execute, or {@code null} to
+ * execute on the service's main thread
+ */
+ public void addOnShowModeChangedListener(@NonNull OnShowModeChangedListener listener,
+ @Nullable Handler handler) {
+ synchronized (mLock) {
+ if (mListeners == null) {
+ mListeners = new ArrayMap<>();
+ }
+
+ final boolean shouldEnableCallback = mListeners.isEmpty();
+ mListeners.put(listener, handler);
+
+ if (shouldEnableCallback) {
+ // This may fail if the service is not connected yet, but if we still have
+ // listeners when it connects, we can try again.
+ setSoftKeyboardCallbackEnabled(true);
+ }
+ }
+ }
+
+ /**
+ * Removes all instances of the specified change listener from teh list of magnification
+ * change listeners.
+ *
+ * @param listener the listener to remove, must be non-null
+ * @return {@code true} if at least one instance of the listener was removed
+ */
+ public boolean removeOnShowModeChangedListener(@NonNull OnShowModeChangedListener listener) {
+ if (mListeners == null) {
+ return false;
+ }
+
+ synchronized (mLock) {
+ final int keyIndex = mListeners.indexOfKey(listener);
+ final boolean hasKey = keyIndex >= 0;
+ if (hasKey) {
+ mListeners.removeAt(keyIndex);
+ }
+
+ if (hasKey && mListeners.isEmpty()) {
+ // We just removed the last listener, so we don't need callbacks from the
+ // service anymore.
+ setSoftKeyboardCallbackEnabled(false);
+ }
+
+ return hasKey;
+ }
+ }
+
+ private void setSoftKeyboardCallbackEnabled(boolean enabled) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ connection.setSoftKeyboardCallbackEnabled(enabled);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
+ /**
+ * Dispatches the soft keyboard show mode change to any registered listeners. This should
+ * be called on the service's main thread.
+ */
+ void dispatchSoftKeyboardShowModeChanged(final int showMode) {
+ final ArrayMap<OnShowModeChangedListener, Handler> entries;
+ synchronized (mLock) {
+ if (mListeners == null || mListeners.isEmpty()) {
+ Slog.d(LOG_TAG, "Received soft keyboard show mode changed callback"
+ + " with no listeners registered!");
+ setSoftKeyboardCallbackEnabled(false);
+ return;
+ }
+
+ // Listeners may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mListeners);
+ }
+
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final OnShowModeChangedListener listener = entries.keyAt(i);
+ final Handler handler = entries.valueAt(i);
+ if (handler != null) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onShowModeChanged(SoftKeyboardController.this, showMode);
+ }
+ });
+ } else {
+ // We're already on the main thread, just run the listener.
+ listener.onShowModeChanged(this, showMode);
+ }
+ }
+ }
+
+ /**
+ * Returns the show mode of the soft keyboard. The default show mode is
+ * {@code Settings.Secure.SHOW_MODE_AUTO}, where the soft keyboard is shown when a text
+ * input field is focused. An AccessibilityService can also request the show mode
+ * {@code Settings.Secure.SHOW_MODE_HIDDEN}, where the soft keyboard is never shown.
+ *
+ * @return the current soft keyboard show mode
+ *
+ * @see Settings#Secure#SHOW_MODE_AUTO
+ * @see Settings#Secure#SHOW_MODE_HIDDEN
+ */
+ @SoftKeyboardShowMode
+ public int getShowMode() {
+ try {
+ return Settings.Secure.getInt(mService.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
+ } catch (Settings.SettingNotFoundException e) {
+ Log.v(LOG_TAG, "Failed to obtain the soft keyboard mode", e);
+ // The settings hasn't been changed yet, so it's value is null. Return the default.
+ return 0;
+ }
+ }
+
+ /**
+ * Sets the soft keyboard show mode. The default show mode is
+ * {@code Settings.Secure.SHOW_MODE_AUTO}, where the soft keyboard is shown when a text
+ * input field is focused. An AccessibilityService can also request the show mode
+ * {@code Settings.Secure.SHOW_MODE_HIDDEN}, where the soft keyboard is never shown. The
+ * The lastto this method will be honored, regardless of any previous calls (including those
+ * made by other AccessibilityServices).
+ * <p>
+ * <strong>Note:</strong> If the service is not yet conected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been called) or the
+ * service has been disconnected, this method will hav no effect and return {@code false}.
+ *
+ * @param showMode the new show mode for the soft keyboard
+ * @return {@code true} on success
+ *
+ * @see Settings#Secure#SHOW_MODE_AUTO
+ * @see Settings#Secure#SHOW_MODE_HIDDEN
+ */
+ public boolean setShowMode(@SoftKeyboardShowMode int showMode) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.setSoftKeyboardShowMode(showMode);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Falied to set soft keyboard behavior", re);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Listener for changes in the soft keyboard show mode.
+ */
+ public interface OnShowModeChangedListener {
+ /**
+ * Called when the soft keyboard behavior changes. The default show mode is
+ * {@code Settings.Secure.SHOW_MODE_AUTO}, where the soft keyboard is shown when a text
+ * input field is focused. An AccessibilityService can also request the show mode
+ * {@code Settings.Secure.SHOW_MODE_HIDDEN}, where the soft keyboard is never shown.
+ *
+ * @param controller the soft keyboard controller
+ * @param showMode the current soft keyboard show mode
+ */
+ void onShowModeChanged(@NonNull SoftKeyboardController controller,
+ @SoftKeyboardShowMode int showMode);
+ }
+ }
+
+ /**
* Performs a global action. Such an action can be performed
* at any moment regardless of the current application or user
* location in that application. For example going back, going
@@ -1184,6 +1455,11 @@
}
@Override
+ public void onSoftKeyboardShowModeChanged(int showMode) {
+ AccessibilityService.this.onSoftKeyboardShowModeChanged(showMode);
+ }
+
+ @Override
public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
AccessibilityService.this.onPerformGestureResult(sequence, completedSuccessfully);
}
@@ -1205,7 +1481,8 @@
private static final int DO_CLEAR_ACCESSIBILITY_CACHE = 5;
private static final int DO_ON_KEY_EVENT = 6;
private static final int DO_ON_MAGNIFICATION_CHANGED = 7;
- private static final int DO_GESTURE_COMPLETE = 8;
+ private static final int DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED = 8;
+ private static final int DO_GESTURE_COMPLETE = 9;
private final HandlerCaller mCaller;
@@ -1264,6 +1541,12 @@
mCaller.sendMessage(message);
}
+ public void onSoftKeyboardShowModeChanged(int showMode) {
+ final Message message =
+ mCaller.obtainMessageI(DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED, showMode);
+ mCaller.sendMessage(message);
+ }
+
public void onPerformGestureResult(int sequence, boolean successfully) {
Message message = mCaller.obtainMessageII(DO_GESTURE_COMPLETE, sequence,
successfully ? 1 : 0);
@@ -1354,6 +1637,11 @@
mCallback.onMagnificationChanged(region, scale, centerX, centerY);
} return;
+ case DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED: {
+ final int showMode = (int) message.arg1;
+ mCallback.onSoftKeyboardShowModeChanged(showMode);
+ } return;
+
case DO_GESTURE_COMPLETE: {
final boolean successfully = message.arg2 == 1;
mCallback.onPerformGestureResult(message.arg1, successfully);
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 6280542..ef05d6f 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -43,5 +43,7 @@
void onMagnificationChanged(in Region region, float scale, float centerX, float centerY);
+ void onSoftKeyboardShowModeChanged(int showMode);
+
void onPerformGestureResult(int sequence, boolean completedSuccessfully);
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index e58ef2f..d5e8a11 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -84,5 +84,9 @@
void setMagnificationCallbackEnabled(boolean enabled);
+ boolean setSoftKeyboardShowMode(int showMode);
+
+ void setSoftKeyboardCallbackEnabled(boolean enabled);
+
void sendMotionEvents(int sequence, in ParceledListSlice events);
}
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 79d383c..5ec59b4 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -1088,6 +1088,11 @@
}
@Override
+ public void onSoftKeyboardShowModeChanged(int showMode) {
+ /* do nothing */
+ }
+
+ @Override
public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
/* do nothing */
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c9c0cde..6e92ec1 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4856,6 +4856,29 @@
"accessibility_display_magnification_auto_update";
/**
+ * Setting that specifies what mode the soft keyboard is in (default or hidden). Can be
+ * modified from an AccessibilityService using the SoftKeyboardController.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SOFT_KEYBOARD_MODE =
+ "accessibility_soft_keyboard_mode";
+
+ /**
+ * Default soft keyboard behavior.
+ *
+ * @hide
+ */
+ public static final int SHOW_MODE_AUTO = 0;
+
+ /**
+ * Soft keyboard is never shown.
+ *
+ * @hide
+ */
+ public static final int SHOW_MODE_HIDDEN = 1;
+
+ /**
* Setting that specifies whether timed text (captions) should be
* displayed in video content. Text display properties are controlled by
* the following settings:
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 2a8672d..53504cc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -958,7 +958,15 @@
final UserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final Service service = state.mBoundServices.get(i);
- service.notifyMagnificationChanged(region, scale, centerX, centerY);
+ service.notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+ }
+ }
+
+ private void notifySoftKeyboardShowModeChangedLocked(int showMode) {
+ final UserState state = getCurrentUserStateLocked();
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ final Service service = state.mBoundServices.get(i);
+ service.notifySoftKeyboardShowModeChangedLocked(showMode);
}
}
@@ -1407,6 +1415,7 @@
updateEnhancedWebAccessibilityLocked(userState);
updateDisplayColorAdjustmentSettingsLocked(userState);
updateMagnificationLocked(userState);
+ updateSoftKeyboardShowModeLocked(userState);
scheduleUpdateInputFilter(userState);
scheduleUpdateClientsIfNeededLocked(userState);
}
@@ -1623,6 +1632,18 @@
return false;
}
+ private boolean readSoftKeyboardShowModeChangedLocked(UserState userState) {
+ final int softKeyboardShowMode = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0,
+ userState.mUserId);
+ if (softKeyboardShowMode != userState.mSoftKeyboardShowMode) {
+ userState.mSoftKeyboardShowMode = softKeyboardShowMode;
+ return true;
+ }
+ return false;
+ }
+
private void updateTouchExplorationLocked(UserState userState) {
boolean enabled = false;
final int serviceCount = userState.mBoundServices.size();
@@ -1757,6 +1778,25 @@
return false;
}
+ private void updateSoftKeyboardShowModeLocked(UserState userState) {
+ final int userId = userState.mUserId;
+ if (userId == mCurrentUserId) {
+ // Check whether any Accessibility Services are still enabled and, if not, remove flag
+ // requesting no soft keyboard
+ final boolean accessibilityRequestingNoIme = userState.mSoftKeyboardShowMode == 1;
+ if (accessibilityRequestingNoIme && !userState.mIsAccessibilityEnabled) {
+ // No active Accessibility Services can be requesting the soft keyboard to be hidden
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ 0,
+ userState.mUserId);
+ userState.mSoftKeyboardShowMode = 0;
+ }
+
+ notifySoftKeyboardShowModeChangedLocked(userState.mSoftKeyboardShowMode);
+ }
+ }
+
private MagnificationSpec getCompatibleMagnificationSpecLocked(int windowId) {
IBinder windowToken = mGlobalWindowTokens.get(windowId);
if (windowToken == null) {
@@ -2102,7 +2142,7 @@
};
// Handler for scheduling method invocations on the main thread.
- public InvocationHandler mInvocationHandler = new InvocationHandler(
+ public final InvocationHandler mInvocationHandler = new InvocationHandler(
mMainHandler.getLooper());
public Service(int userId, ComponentName componentName,
@@ -2297,17 +2337,20 @@
}
}
+ private boolean isCalledForCurrentUserLocked() {
+ // We treat calls from a profile as if made by its parent as profiles
+ // share the accessibility state of the parent. The call below
+ // performs the current profile parent resolution.
+ final int resolvedUserId = mSecurityPolicy
+ .resolveCallingUserIdEnforcingPermissionsLocked(UserHandle.USER_CURRENT);
+ return resolvedUserId == mCurrentUserId;
+ }
+
@Override
public List<AccessibilityWindowInfo> getWindows() {
ensureWindowsAvailableTimed();
synchronized (mLock) {
- // We treat calls from a profile as if made by its perent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return null;
}
final boolean permissionGranted =
@@ -2335,13 +2378,7 @@
public AccessibilityWindowInfo getWindow(int windowId) {
ensureWindowsAvailableTimed();
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return null;
}
final boolean permissionGranted =
@@ -2368,13 +2405,7 @@
IAccessibilityInteractionConnection connection = null;
Region partialInteractiveRegion = Region.obtain();
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
@@ -2425,13 +2456,7 @@
IAccessibilityInteractionConnection connection = null;
Region partialInteractiveRegion = Region.obtain();
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
@@ -2482,13 +2507,7 @@
IAccessibilityInteractionConnection connection = null;
Region partialInteractiveRegion = Region.obtain();
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
@@ -2539,13 +2558,7 @@
IAccessibilityInteractionConnection connection = null;
Region partialInteractiveRegion = Region.obtain();
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
resolvedWindowId = resolveAccessibilityWindowIdForFindFocusLocked(
@@ -2597,13 +2610,7 @@
IAccessibilityInteractionConnection connection = null;
Region partialInteractiveRegion = Region.obtain();
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
@@ -2670,13 +2677,7 @@
final int resolvedWindowId;
IAccessibilityInteractionConnection connection = null;
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
@@ -2709,13 +2710,7 @@
@Override
public boolean performGlobalAction(int action) {
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
}
@@ -2750,13 +2745,7 @@
@Override
public float getMagnificationScale() {
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return 1.0f;
}
}
@@ -2771,13 +2760,7 @@
@Override
public Region getMagnifiedRegion() {
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return Region.obtain();
}
}
@@ -2794,13 +2777,7 @@
@Override
public float getMagnificationCenterX() {
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return 0.0f;
}
}
@@ -2815,13 +2792,7 @@
@Override
public float getMagnificationCenterY() {
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return 0.0f;
}
}
@@ -2836,13 +2807,7 @@
@Override
public boolean resetMagnification(boolean animate) {
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
final boolean permissionGranted = mSecurityPolicy.canControlMagnification(this);
@@ -2862,13 +2827,7 @@
public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
boolean animate) {
synchronized (mLock) {
- // We treat calls from a profile as if made by its parent as profiles
- // share the accessibility state of the parent. The call below
- // performs the current profile parent resolution.
- final int resolvedUserId = mSecurityPolicy
- .resolveCallingUserIdEnforcingPermissionsLocked(
- UserHandle.USER_CURRENT);
- if (resolvedUserId != mCurrentUserId) {
+ if (!isCalledForCurrentUserLocked()) {
return false;
}
final boolean permissionGranted = mSecurityPolicy.canControlMagnification(this);
@@ -2891,6 +2850,33 @@
}
@Override
+ public boolean setSoftKeyboardShowMode(int showMode) {
+ final UserState userState;
+ synchronized (mLock) {
+ if (!isCalledForCurrentUserLocked()) {
+ return false;
+ }
+
+ userState = getCurrentUserStateLocked();
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, showMode,
+ userState.mUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return true;
+ }
+
+ @Override
+ public void setSoftKeyboardCallbackEnabled(boolean enabled) {
+ mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
+ }
+
+ @Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP);
synchronized (mLock) {
@@ -3080,9 +3066,14 @@
InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
}
- public void notifyMagnificationChanged(@NonNull Region region,
+ public void notifyMagnificationChangedLocked(@NonNull Region region,
float scale, float centerX, float centerY) {
- mInvocationHandler.notifyMagnificationChanged(region, scale, centerX, centerY);
+ mInvocationHandler
+ .notifyMagnificationChangedLocked(region, scale, centerX, centerY);
+ }
+
+ public void notifySoftKeyboardShowModeChangedLocked(int showState) {
+ mInvocationHandler.notifySoftKeyboardShowModeChangedLocked(showState);
}
/**
@@ -3104,6 +3095,25 @@
}
}
+ /**
+ * Called by the invocation handler to notify the service that the state of the soft
+ * keyboard show mode has changed.
+ */
+ private void notifySoftKeyboardShowModeChangedInternal(int showState) {
+ final IAccessibilityServiceClient listener;
+ synchronized (mLock) {
+ listener = mServiceInterface;
+ }
+ if (listener != null) {
+ try {
+ listener.onSoftKeyboardShowModeChanged(showState);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error sending soft keyboard show mode changes to " + mService,
+ re);
+ }
+ }
+ }
+
private void notifyGestureInternal(int gestureId) {
final IAccessibilityServiceClient listener;
synchronized (mLock) {
@@ -3239,8 +3249,10 @@
public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 2;
private static final int MSG_ON_MAGNIFICATION_CHANGED = 5;
+ private static final int MSG_ON_SOFT_KEYBOARD_STATE_CHANGED = 6;
private boolean mIsMagnificationCallbackEnabled = false;
+ private boolean mIsSoftKeyboardCallbackEnabled = false;
public InvocationHandler(Looper looper) {
super(looper, null, true);
@@ -3268,13 +3280,18 @@
notifyMagnificationChangedInternal(region, scale, centerX, centerY);
} break;
+ case MSG_ON_SOFT_KEYBOARD_STATE_CHANGED: {
+ final int showState = (int) message.arg1;
+ notifySoftKeyboardShowModeChangedInternal(showState);
+ } break;
+
default: {
throw new IllegalArgumentException("Unknown message: " + type);
}
}
}
- public void notifyMagnificationChanged(@NonNull Region region, float scale,
+ public void notifyMagnificationChangedLocked(@NonNull Region region, float scale,
float centerX, float centerY) {
if (!mIsMagnificationCallbackEnabled) {
// Callback is disabled, don't bother packing args.
@@ -3294,8 +3311,20 @@
public void setMagnificationCallbackEnabled(boolean enabled) {
mIsMagnificationCallbackEnabled = enabled;
}
- }
+ public void notifySoftKeyboardShowModeChangedLocked(int showState) {
+ if (!mIsSoftKeyboardCallbackEnabled) {
+ return;
+ }
+
+ final Message msg = obtainMessage(MSG_ON_SOFT_KEYBOARD_STATE_CHANGED, showState, 0);
+ msg.sendToTarget();
+ }
+
+ public void setSoftKeyboardCallbackEnabled(boolean enabled) {
+ mIsSoftKeyboardCallbackEnabled = enabled;
+ }
+ }
}
final class WindowsForAccessibilityCallback implements
@@ -3976,6 +4005,8 @@
public int mLastSentClientState = -1;
+ public int mSoftKeyboardShowMode = 0;
+
public boolean mIsAccessibilityEnabled;
public boolean mIsTouchExplorationEnabled;
public boolean mIsTextHighContrastEnabled;
@@ -4048,6 +4079,7 @@
mIsEnhancedWebAccessibilityEnabled = false;
mIsDisplayMagnificationEnabled = false;
mIsAutoclickEnabled = false;
+ mSoftKeyboardShowMode = 0;
}
public void destroyUiAutomationService() {
@@ -4105,6 +4137,9 @@
private final Uri mHighTextContrastUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED);
+ private final Uri mAccessibilitySoftKeyboardModeUri = Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
+
public AccessibilityContentObserver(Handler handler) {
super(handler);
}
@@ -4135,6 +4170,8 @@
mDisplayColorMatrixUri, false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(
mHighTextContrastUri, false, this, UserHandle.USER_ALL);
+ contentResolver.registerContentObserver(
+ mAccessibilitySoftKeyboardModeUri, false, this, UserHandle.USER_ALL);
}
@Override
@@ -4189,6 +4226,10 @@
if (readHighTextContrastEnabledSettingLocked(userState)) {
onUserStateChangedLocked(userState);
}
+ } else if (mAccessibilitySoftKeyboardModeUri.equals(uri)) {
+ if (readSoftKeyboardShowModeChangedLocked(userState)) {
+ onUserStateChangedLocked(userState);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 4a123df..d77def6 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -448,6 +448,7 @@
private int[] mSubtypeIds;
private Locale mLastSystemLocale;
private boolean mShowImeWithHardKeyboard;
+ private boolean mAccessibilityRequestingNoSoftKeyboard;
private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
private final IPackageManager mIPackageManager;
private final String mSlotIme;
@@ -485,15 +486,31 @@
Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this, userId);
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this, userId);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE), false, this, userId);
mRegistered = true;
}
@Override public void onChange(boolean selfChange, Uri uri) {
- final Uri showImeUri =
- Settings.Secure.getUriFor(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
+ final Uri showImeUri = Settings.Secure.getUriFor(
+ Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
+ final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
synchronized (mMethodMap) {
if (showImeUri.equals(uri)) {
updateKeyboardFromSettingsLocked();
+ } else if (accessibilityRequestingNoImeUri.equals(uri)) {
+ mAccessibilityRequestingNoSoftKeyboard = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ 0, mUserId) == 1;
+ if (mAccessibilityRequestingNoSoftKeyboard) {
+ final boolean showRequested = mShowRequested;
+ hideCurrentInputLocked(0, null);
+ mShowRequested = showRequested;
+ } else if (mShowRequested) {
+ showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
+ }
} else {
boolean enabledChanged = false;
String newEnabled = mSettings.getEnabledInputMethodsStr();
@@ -655,7 +672,7 @@
boolean changed = false;
if (curIm != null) {
- int change = isPackageDisappearing(curIm.getPackageName());
+ int change = isPackageDisappearing(curIm.getPackageName());
if (change == PACKAGE_TEMPORARY_CHANGE
|| change == PACKAGE_PERMANENT_CHANGE) {
ServiceInfo si = null;
@@ -2073,12 +2090,15 @@
boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
mShowRequested = true;
- if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
- mShowExplicitlyRequested = true;
+ if (mAccessibilityRequestingNoSoftKeyboard) {
+ return false;
}
+
if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
mShowExplicitlyRequested = true;
mShowForced = true;
+ } else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
+ mShowExplicitlyRequested = true;
}
if (!mSystemReady) {
@@ -3265,7 +3285,7 @@
"Requires permission "
+ android.Manifest.permission.WRITE_SECURE_SETTINGS);
}
-
+
long ident = Binder.clearCallingIdentity();
try {
return setInputMethodEnabledLocked(id, enabled);