aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java133
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java129
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibleKeyboardActionListener.java37
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java201
-rw-r--r--java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java226
-rw-r--r--java/src/com/android/inputmethod/compat/AccessibilityEventCompatUtils.java39
-rw-r--r--java/src/com/android/inputmethod/compat/AccessibilityManagerCompatWrapper.java42
-rw-r--r--java/src/com/android/inputmethod/compat/MotionEventCompatUtils.java23
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardId.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java36
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java2
12 files changed, 879 insertions, 1 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
new file mode 100644
index 000000000..ae614b7e0
--- /dev/null
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.accessibility;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.inputmethodservice.InputMethodService;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.inputmethod.compat.AccessibilityEventCompatUtils;
+import com.android.inputmethod.compat.AccessibilityManagerCompatWrapper;
+import com.android.inputmethod.compat.MotionEventCompatUtils;
+
+public class AccessibilityUtils {
+ private static final String TAG = AccessibilityUtils.class.getSimpleName();
+ private static final String CLASS = AccessibilityUtils.class.getClass().getName();
+ private static final String PACKAGE = AccessibilityUtils.class.getClass().getPackage()
+ .getName();
+
+ private static final AccessibilityUtils sInstance = new AccessibilityUtils();
+
+ private AccessibilityManager mAccessibilityManager;
+ private AccessibilityManagerCompatWrapper mCompatManager;
+
+ /*
+ * Setting this constant to {@code false} will disable all keyboard
+ * accessibility code, regardless of whether Accessibility is turned on in
+ * the system settings. It should ONLY be used in the event of an emergency.
+ */
+ private static final boolean ENABLE_ACCESSIBILITY = true;
+
+ public static void init(InputMethodService inputMethod, SharedPreferences prefs) {
+ if (!ENABLE_ACCESSIBILITY)
+ return;
+
+ // These only need to be initialized if the kill switch is off.
+ sInstance.initInternal(inputMethod, prefs);
+ KeyCodeDescriptionMapper.init(inputMethod, prefs);
+ AccessibleInputMethodServiceProxy.init(inputMethod, prefs);
+ AccessibleKeyboardViewProxy.init(inputMethod, prefs);
+ }
+
+ public static AccessibilityUtils getInstance() {
+ return sInstance;
+ }
+
+ private AccessibilityUtils() {
+ // This class is not publicly instantiable.
+ }
+
+ private void initInternal(Context context, SharedPreferences prefs) {
+ mAccessibilityManager = (AccessibilityManager) context
+ .getSystemService(Context.ACCESSIBILITY_SERVICE);
+ mCompatManager = new AccessibilityManagerCompatWrapper(mAccessibilityManager);
+ }
+
+ /**
+ * Returns {@code true} if touch exploration is enabled. Currently, this
+ * means that the kill switch is off, the device supports touch exploration,
+ * and a spoken feedback service is turned on.
+ *
+ * @return {@code true} if touch exploration is enabled.
+ */
+ public boolean isTouchExplorationEnabled() {
+ return ENABLE_ACCESSIBILITY
+ && AccessibilityEventCompatUtils.supportsTouchExploration()
+ && mAccessibilityManager.isEnabled()
+ && !mCompatManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty();
+ }
+
+ /**
+ * Returns {@true} if the provided event is a touch exploration (e.g. hover)
+ * event. This is used to determine whether the event should be processed by
+ * the touch exploration code within the keyboard.
+ *
+ * @param event The event to check.
+ * @return {@true} is the event is a touch exploration event
+ */
+ public boolean isTouchExplorationEvent(MotionEvent event) {
+ final int action = event.getAction();
+
+ return action == MotionEventCompatUtils.ACTION_HOVER_ENTER
+ || action == MotionEventCompatUtils.ACTION_HOVER_EXIT
+ || action == MotionEventCompatUtils.ACTION_HOVER_MOVE;
+ }
+
+ /**
+ * Sends the specified text to the {@link AccessibilityManager} to be
+ * spoken.
+ *
+ * @param text the text to speak
+ */
+ public void speak(CharSequence text) {
+ if (!mAccessibilityManager.isEnabled()) {
+ Log.e(TAG, "Attempted to speak when accessibility was disabled!");
+ return;
+ }
+
+ // The following is a hack to avoid using the heavy-weight TextToSpeech
+ // class. Instead, we're just forcing a fake AccessibilityEvent into
+ // the screen reader to make it speak.
+ final AccessibilityEvent event = AccessibilityEvent
+ .obtain(AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_ENTER);
+
+ event.setPackageName(PACKAGE);
+ event.setClassName(CLASS);
+ event.setEventTime(SystemClock.uptimeMillis());
+ event.setEnabled(true);
+ event.getText().add(text);
+
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+}
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java
new file mode 100644
index 000000000..043266c70
--- /dev/null
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.accessibility;
+
+import android.content.SharedPreferences;
+import android.inputmethodservice.InputMethodService;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.text.TextUtils;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+
+import com.android.inputmethod.latin.R;
+
+public class AccessibleInputMethodServiceProxy implements AccessibleKeyboardActionListener {
+ private static final AccessibleInputMethodServiceProxy sInstance =
+ new AccessibleInputMethodServiceProxy();
+
+ /*
+ * Delay for the handler event that's fired when Accessibility is on and the
+ * user hovers outside of any valid keys. This is used to let the user know
+ * that if they lift their finger, nothing will be typed.
+ */
+ private static final long DELAY_NO_HOVER_SELECTION = 250;
+
+ private InputMethodService mInputMethod;
+
+ private AccessibilityHandler mAccessibilityHandler;
+
+ private class AccessibilityHandler extends Handler {
+ private static final int MSG_NO_HOVER_SELECTION = 0;
+
+ public AccessibilityHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_NO_HOVER_SELECTION:
+ notifyNoHoverSelection();
+ break;
+ }
+ }
+
+ public void postNoHoverSelection() {
+ removeMessages(MSG_NO_HOVER_SELECTION);
+ sendEmptyMessageDelayed(MSG_NO_HOVER_SELECTION, DELAY_NO_HOVER_SELECTION);
+ }
+
+ public void cancelNoHoverSelection() {
+ removeMessages(MSG_NO_HOVER_SELECTION);
+ }
+ }
+
+ public static void init(InputMethodService inputMethod, SharedPreferences prefs) {
+ sInstance.initInternal(inputMethod, prefs);
+ }
+
+ public static AccessibleInputMethodServiceProxy getInstance() {
+ return sInstance;
+ }
+
+ private AccessibleInputMethodServiceProxy() {
+ // Not publicly instantiable.
+ }
+
+ private void initInternal(InputMethodService inputMethod, SharedPreferences prefs) {
+ mInputMethod = inputMethod;
+ mAccessibilityHandler = new AccessibilityHandler(inputMethod.getMainLooper());
+ }
+
+ /**
+ * If touch exploration is enabled, cancels the event sent by
+ * {@link AccessibleInputMethodServiceProxy#onHoverExit(int)} because the
+ * user is currently hovering above a key.
+ */
+ @Override
+ public void onHoverEnter(int primaryCode) {
+ mAccessibilityHandler.cancelNoHoverSelection();
+ }
+
+ /**
+ * If touch exploration is enabled, sends a delayed event to notify the user
+ * that they are not currently hovering above a key.
+ */
+ @Override
+ public void onHoverExit(int primaryCode) {
+ mAccessibilityHandler.postNoHoverSelection();
+ }
+
+ /**
+ * When Accessibility is turned on, notifies the user that they are not
+ * currently hovering above a key. By default this will speak the currently
+ * entered text.
+ */
+ private void notifyNoHoverSelection() {
+ final ExtractedText extracted = mInputMethod.getCurrentInputConnection().getExtractedText(
+ new ExtractedTextRequest(), 0);
+
+ if (extracted == null)
+ return;
+
+ final CharSequence text;
+
+ if (TextUtils.isEmpty(extracted.text)) {
+ text = mInputMethod.getString(R.string.spoken_no_text_entered);
+ } else {
+ text = mInputMethod.getString(R.string.spoken_current_text_is, extracted.text);
+ }
+
+ AccessibilityUtils.getInstance().speak(text);
+ }
+}
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardActionListener.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardActionListener.java
new file mode 100644
index 000000000..12c59d0fc
--- /dev/null
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardActionListener.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.accessibility;
+
+public interface AccessibleKeyboardActionListener {
+ /**
+ * Called when the user hovers inside a key. This is sent only when
+ * Accessibility is turned on. For keys that repeat, this is only called
+ * once.
+ *
+ * @param primaryCode the code of the key that was hovered over
+ */
+ public void onHoverEnter(int primaryCode);
+
+ /**
+ * Called when the user hovers outside a key. This is sent only when
+ * Accessibility is turned on. For keys that repeat, this is only called
+ * once.
+ *
+ * @param primaryCode the code of the key that was hovered over
+ */
+ public void onHoverExit(int primaryCode);
+}
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
new file mode 100644
index 000000000..96f7fc9f2
--- /dev/null
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.accessibility;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.inputmethod.compat.AccessibilityEventCompatUtils;
+import com.android.inputmethod.compat.MotionEventCompatUtils;
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.keyboard.PointerTracker;
+
+public class AccessibleKeyboardViewProxy {
+ private static final String TAG = AccessibleKeyboardViewProxy.class.getSimpleName();
+ private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy();
+
+ // Delay in milliseconds between key press DOWN and UP events
+ private static final long DELAY_KEY_PRESS = 10;
+
+ private int mScaledEdgeSlop;
+ private KeyboardView mView;
+ private AccessibleKeyboardActionListener mListener;
+
+ private int mLastHoverKeyIndex = KeyDetector.NOT_A_KEY;
+ private int mLastX = -1;
+ private int mLastY = -1;
+
+ public static void init(Context context, SharedPreferences prefs) {
+ sInstance.initInternal(context, prefs);
+ sInstance.mListener = AccessibleInputMethodServiceProxy.getInstance();
+ }
+
+ public static AccessibleKeyboardViewProxy getInstance() {
+ return sInstance;
+ }
+
+ public static void setView(KeyboardView view) {
+ sInstance.mView = view;
+ }
+
+ private AccessibleKeyboardViewProxy() {
+ // Not publicly instantiable.
+ }
+
+ private void initInternal(Context context, SharedPreferences prefs) {
+ final Paint paint = new Paint();
+ paint.setTextAlign(Paint.Align.LEFT);
+ paint.setTextSize(14.0f);
+ paint.setAntiAlias(true);
+ paint.setColor(Color.YELLOW);
+
+ mScaledEdgeSlop = ViewConfiguration.get(context).getScaledEdgeSlop();
+ }
+
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event,
+ PointerTracker tracker) {
+ if (mView == null) {
+ Log.e(TAG, "No keyboard view set!");
+ return false;
+ }
+
+ switch (event.getEventType()) {
+ case AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_ENTER:
+ final Key key = tracker.getKey(mLastHoverKeyIndex);
+
+ if (key == null)
+ break;
+
+ final CharSequence description = KeyCodeDescriptionMapper.getInstance()
+ .getDescriptionForKey(mView.getContext(), mView.getKeyboard(), key);
+
+ if (description == null)
+ return false;
+
+ event.getText().add(description);
+
+ break;
+ }
+
+ return true;
+ }
+
+ /**
+ * Receives hover events when accessibility is turned on in API > 11. In
+ * earlier API levels, events are manually routed from onTouchEvent.
+ *
+ * @param event The hover event.
+ * @return {@code true} if the event is handled
+ */
+ public boolean onHoverEvent(MotionEvent event, PointerTracker tracker) {
+ return onTouchExplorationEvent(event, tracker);
+ }
+
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ // Since touch exploration translates hover double-tap to a regular
+ // single-tap, we're going to drop non-touch exploration events.
+ if (!AccessibilityUtils.getInstance().isTouchExplorationEvent(event))
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Handles touch exploration events when Accessibility is turned on.
+ *
+ * @param event The touch exploration hover event.
+ * @return {@code true} if the event was handled
+ */
+ private boolean onTouchExplorationEvent(MotionEvent event, PointerTracker tracker) {
+ final int x = (int) event.getX();
+ final int y = (int) event.getY();
+
+ switch (event.getAction()) {
+ case MotionEventCompatUtils.ACTION_HOVER_ENTER:
+ case MotionEventCompatUtils.ACTION_HOVER_MOVE:
+ final int keyIndex = tracker.getKeyIndexOn(x, y);
+
+ if (keyIndex != mLastHoverKeyIndex) {
+ fireKeyHoverEvent(tracker, mLastHoverKeyIndex, false);
+ mLastHoverKeyIndex = keyIndex;
+ mLastX = x;
+ mLastY = y;
+ fireKeyHoverEvent(tracker, mLastHoverKeyIndex, true);
+ }
+
+ return true;
+ case MotionEventCompatUtils.ACTION_HOVER_EXIT:
+ final int width = mView.getWidth();
+ final int height = mView.getHeight();
+
+ if (x < mScaledEdgeSlop || y < mScaledEdgeSlop || x >= (width - mScaledEdgeSlop)
+ || y >= (height - mScaledEdgeSlop)) {
+ fireKeyHoverEvent(tracker, mLastHoverKeyIndex, false);
+ mLastHoverKeyIndex = KeyDetector.NOT_A_KEY;
+ mLastX = -1;
+ mLastY = -1;
+ } else if (mLastHoverKeyIndex != KeyDetector.NOT_A_KEY) {
+ fireKeyPressEvent(tracker, mLastX, mLastY, event.getEventTime());
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private void fireKeyHoverEvent(PointerTracker tracker, int keyIndex, boolean entering) {
+ if (mListener == null) {
+ Log.e(TAG, "No accessible keyboard action listener set!");
+ return;
+ }
+
+ if (mView == null) {
+ Log.e(TAG, "No keyboard view set!");
+ return;
+ }
+
+ if (keyIndex == KeyDetector.NOT_A_KEY)
+ return;
+
+ final Key key = tracker.getKey(keyIndex);
+
+ if (key == null)
+ return;
+
+ if (entering) {
+ mListener.onHoverEnter(key.mCode);
+ mView.sendAccessibilityEvent(AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_ENTER);
+ } else {
+ mListener.onHoverExit(key.mCode);
+ mView.sendAccessibilityEvent(AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_EXIT);
+ }
+ }
+
+ private void fireKeyPressEvent(PointerTracker tracker, int x, int y, long eventTime) {
+ tracker.onDownEvent(x, y, eventTime, null);
+ tracker.onUpEvent(x, y, eventTime + DELAY_KEY_PRESS, null);
+ }
+}
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
new file mode 100644
index 000000000..154f4af91
--- /dev/null
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.accessibility;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.text.TextUtils;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.latin.R;
+
+import java.util.HashMap;
+
+public class KeyCodeDescriptionMapper {
+ private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper();
+
+ // Map of key labels to spoken description resource IDs
+ private final HashMap<CharSequence, Integer> mKeyLabelMap;
+
+ // Map of key codes to spoken description resource IDs
+ private final HashMap<Integer, Integer> mKeyCodeMap;
+
+ // Map of shifted key codes to spoken description resource IDs
+ private final HashMap<Integer, Integer> mShiftedKeyCodeMap;
+
+ // Map of shift-locked key codes to spoken description resource IDs
+ private final HashMap<Integer, Integer> mShiftLockedKeyCodeMap;
+
+ public static void init(Context context, SharedPreferences prefs) {
+ sInstance.initInternal(context, prefs);
+ }
+
+ public static KeyCodeDescriptionMapper getInstance() {
+ return sInstance;
+ }
+
+ private KeyCodeDescriptionMapper() {
+ mKeyLabelMap = new HashMap<CharSequence, Integer>();
+ mKeyCodeMap = new HashMap<Integer, Integer>();
+ mShiftedKeyCodeMap = new HashMap<Integer, Integer>();
+ mShiftLockedKeyCodeMap = new HashMap<Integer, Integer>();
+ }
+
+ private void initInternal(Context context, SharedPreferences prefs) {
+ // Manual label substitutions for key labels with no string resource
+ mKeyLabelMap.put(":-)", R.string.spoken_description_smiley);
+
+ // Symbols that most TTS engines can't speak
+ mKeyCodeMap.put((int) '.', R.string.spoken_description_period);
+ mKeyCodeMap.put((int) ',', R.string.spoken_description_comma);
+ mKeyCodeMap.put((int) '(', R.string.spoken_description_left_parenthesis);
+ mKeyCodeMap.put((int) ')', R.string.spoken_description_right_parenthesis);
+ mKeyCodeMap.put((int) ':', R.string.spoken_description_colon);
+ mKeyCodeMap.put((int) ';', R.string.spoken_description_semicolon);
+ mKeyCodeMap.put((int) '!', R.string.spoken_description_exclamation_mark);
+ mKeyCodeMap.put((int) '?', R.string.spoken_description_question_mark);
+ mKeyCodeMap.put((int) '\"', R.string.spoken_description_double_quote);
+ mKeyCodeMap.put((int) '\'', R.string.spoken_description_single_quote);
+ mKeyCodeMap.put((int) '*', R.string.spoken_description_star);
+ mKeyCodeMap.put((int) '#', R.string.spoken_description_pound);
+ mKeyCodeMap.put((int) ' ', R.string.spoken_description_space);
+
+ // Non-ASCII symbols (must use escape codes!)
+ mKeyCodeMap.put((int) '\u2022', R.string.spoken_description_dot);
+ mKeyCodeMap.put((int) '\u221A', R.string.spoken_description_square_root);
+ mKeyCodeMap.put((int) '\u03C0', R.string.spoken_description_pi);
+ mKeyCodeMap.put((int) '\u0394', R.string.spoken_description_delta);
+ mKeyCodeMap.put((int) '\u2122', R.string.spoken_description_trademark);
+ mKeyCodeMap.put((int) '\u2105', R.string.spoken_description_care_of);
+ mKeyCodeMap.put((int) '\u2026', R.string.spoken_description_ellipsis);
+ mKeyCodeMap.put((int) '\u201E', R.string.spoken_description_low_double_quote);
+
+ // Special non-character codes defined in Keyboard
+ mKeyCodeMap.put(Keyboard.CODE_DELETE, R.string.spoken_description_delete);
+ mKeyCodeMap.put(Keyboard.CODE_ENTER, R.string.spoken_description_return);
+ mKeyCodeMap.put(Keyboard.CODE_SETTINGS, R.string.spoken_description_settings);
+ mKeyCodeMap.put(Keyboard.CODE_SHIFT, R.string.spoken_description_shift);
+ mKeyCodeMap.put(Keyboard.CODE_SHORTCUT, R.string.spoken_description_mic);
+ mKeyCodeMap.put(Keyboard.CODE_SWITCH_ALPHA_SYMBOL, R.string.spoken_description_to_symbol);
+ mKeyCodeMap.put(Keyboard.CODE_TAB, R.string.spoken_description_tab);
+
+ // Shifted versions of non-character codes defined in Keyboard
+ mShiftedKeyCodeMap.put(Keyboard.CODE_SHIFT, R.string.spoken_description_shift_shifted);
+
+ // Shift-locked versions of non-character codes defined in Keyboard
+ mShiftLockedKeyCodeMap.put(Keyboard.CODE_SHIFT, R.string.spoken_description_caps_lock);
+ }
+
+ /**
+ * Returns the localized description of the action performed by a specified
+ * key based on the current keyboard state.
+ * <p>
+ * The order of precedence for key descriptions is:
+ * <ol>
+ * <li>Manually-defined based on the key label</li>
+ * <li>Automatic or manually-defined based on the key code</li>
+ * <li>Automatically based on the key label</li>
+ * <li>{code null} for keys with no label or key code defined</li>
+ * </p>
+ *
+ * @param context The package's context.
+ * @param keyboard The keyboard on which the key resides.
+ * @param key The key from which to obtain a description.
+ * @return a character sequence describing the action performed by pressing
+ * the key
+ */
+ public CharSequence getDescriptionForKey(Context context, Keyboard keyboard, Key key) {
+ if (key.mCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
+ final CharSequence description = getDescriptionForSwitchAlphaSymbol(context, keyboard);
+ if (description != null)
+ return description;
+ }
+
+ if (!TextUtils.isEmpty(key.mLabel)) {
+ final String label = key.mLabel.toString().trim();
+
+ if (mKeyLabelMap.containsKey(label)) {
+ return context.getString(mKeyLabelMap.get(label));
+ } else if (label.length() == 1
+ || (keyboard.isManualTemporaryUpperCase() && !TextUtils
+ .isEmpty(key.mHintLetter))) {
+ return getDescriptionForKeyCode(context, keyboard, key);
+ } else {
+ return label;
+ }
+ } else if (key.mCode != Keyboard.CODE_DUMMY) {
+ return getDescriptionForKeyCode(context, keyboard, key);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a context-specific description for the CODE_SWITCH_ALPHA_SYMBOL
+ * key or {@code null} if there is not a description provided for the
+ * current keyboard context.
+ *
+ * @param context The package's context.
+ * @param keyboard The keyboard on which the key resides.
+ * @return a character sequence describing the action performed by pressing
+ * the key
+ */
+ private CharSequence getDescriptionForSwitchAlphaSymbol(Context context, Keyboard keyboard) {
+ final KeyboardId id = keyboard.mId;
+
+ if (id.isAlphabetKeyboard()) {
+ return context.getString(R.string.spoken_description_to_symbol);
+ } else if (id.isSymbolsKeyboard()) {
+ return context.getString(R.string.spoken_description_to_alpha);
+ } else if (id.isPhoneSymbolsKeyboard()) {
+ return context.getString(R.string.spoken_description_to_numeric);
+ } else if (id.isPhoneKeyboard()) {
+ return context.getString(R.string.spoken_description_to_symbol);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the keycode for the specified key given the current keyboard
+ * state.
+ *
+ * @param keyboard The keyboard on which the key resides.
+ * @param key The key from which to obtain a key code.
+ * @return the key code for the specified key
+ */
+ private int getCorrectKeyCode(Keyboard keyboard, Key key) {
+ if (keyboard.isManualTemporaryUpperCase() && !TextUtils.isEmpty(key.mHintLetter)) {
+ return key.mHintLetter.charAt(0);
+ } else {
+ return key.mCode;
+ }
+ }
+
+ /**
+ * Returns a localized character sequence describing what will happen when
+ * the specified key is pressed based on its key code.
+ * <p>
+ * The order of precedence for key code descriptions is:
+ * <ol>
+ * <li>Manually-defined shift-locked description</li>
+ * <li>Manually-defined shifted description</li>
+ * <li>Manually-defined normal description</li>
+ * <li>Automatic based on the character represented by the key code</li>
+ * <li>Fall-back for undefined or control characters</li>
+ * </ol>
+ * </p>
+ *
+ * @param context The package's context.
+ * @param keyboard The keyboard on which the key resides.
+ * @param key The key from which to obtain a description.
+ * @return a character sequence describing the action performed by pressing
+ * the key
+ */
+ private CharSequence getDescriptionForKeyCode(Context context, Keyboard keyboard, Key key) {
+ final int code = getCorrectKeyCode(keyboard, key);
+
+ if (keyboard.isShiftLocked() && mShiftLockedKeyCodeMap.containsKey(code)) {
+ return context.getString(mShiftLockedKeyCodeMap.get(code));
+ } else if (keyboard.isShiftedOrShiftLocked() && mShiftedKeyCodeMap.containsKey(code)) {
+ return context.getString(mShiftedKeyCodeMap.get(code));
+ } else if (mKeyCodeMap.containsKey(code)) {
+ return context.getString(mKeyCodeMap.get(code));
+ } else if (Character.isDefined(code) && !Character.isISOControl(code)) {
+ return Character.toString((char) code);
+ } else {
+ return context.getString(R.string.spoken_description_unknown, code);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/compat/AccessibilityEventCompatUtils.java b/java/src/com/android/inputmethod/compat/AccessibilityEventCompatUtils.java
new file mode 100644
index 000000000..50057727a
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/AccessibilityEventCompatUtils.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.view.accessibility.AccessibilityEvent;
+
+import java.lang.reflect.Field;
+
+public class AccessibilityEventCompatUtils {
+ public static final int TYPE_VIEW_HOVER_ENTER = 0x80;
+ public static final int TYPE_VIEW_HOVER_EXIT = 0x100;
+
+ private static final Field FIELD_TYPE_VIEW_HOVER_ENTER = CompatUtils.getField(
+ AccessibilityEvent.class, "TYPE_VIEW_HOVER_ENTER");
+ private static final Field FIELD_TYPE_VIEW_HOVER_EXIT = CompatUtils.getField(
+ AccessibilityEvent.class, "TYPE_VIEW_HOVER_EXIT");
+ private static final Integer OBJ_TYPE_VIEW_HOVER_ENTER = (Integer) CompatUtils
+ .getFieldValue(null, null, FIELD_TYPE_VIEW_HOVER_ENTER);
+ private static final Integer OBJ_TYPE_VIEW_HOVER_EXIT = (Integer) CompatUtils
+ .getFieldValue(null, null, FIELD_TYPE_VIEW_HOVER_EXIT);
+
+ public static boolean supportsTouchExploration() {
+ return OBJ_TYPE_VIEW_HOVER_ENTER != null && OBJ_TYPE_VIEW_HOVER_EXIT != null;
+ }
+}
diff --git a/java/src/com/android/inputmethod/compat/AccessibilityManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/AccessibilityManagerCompatWrapper.java
new file mode 100644
index 000000000..4db1c7a24
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/AccessibilityManagerCompatWrapper.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.view.accessibility.AccessibilityManager;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+
+public class AccessibilityManagerCompatWrapper {
+ private static final Method METHOD_getEnabledAccessibilityServiceList = CompatUtils.getMethod(
+ AccessibilityManager.class, "getEnabledAccessibilityServiceList", int.class);
+
+ private final AccessibilityManager mManager;
+
+ public AccessibilityManagerCompatWrapper(AccessibilityManager manager) {
+ mManager = manager;
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType) {
+ return (List<AccessibilityServiceInfo>) CompatUtils.invoke(mManager,
+ Collections.<AccessibilityServiceInfo>emptyList(),
+ METHOD_getEnabledAccessibilityServiceList, feedbackType);
+ }
+}
diff --git a/java/src/com/android/inputmethod/compat/MotionEventCompatUtils.java b/java/src/com/android/inputmethod/compat/MotionEventCompatUtils.java
new file mode 100644
index 000000000..8518a4a78
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/MotionEventCompatUtils.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+public class MotionEventCompatUtils {
+ public static final int ACTION_HOVER_MOVE = 0x7;
+ public static final int ACTION_HOVER_ENTER = 0x9;
+ public static final int ACTION_HOVER_EXIT = 0xA;
+}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 2497eeb7b..b91134dd6 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -120,13 +120,17 @@ public class KeyboardId {
}
public boolean isSymbolsKeyboard() {
- return mXmlId == R.xml.kbd_symbols;
+ return mXmlId == R.xml.kbd_symbols || mXmlId == R.xml.kbd_symbols_shift;
}
public boolean isPhoneKeyboard() {
return mMode == MODE_PHONE;
}
+ public boolean isPhoneSymbolsKeyboard() {
+ return mXmlId == R.xml.kbd_phone_symbols;
+ }
+
public boolean isNumberKeyboard() {
return mMode == MODE_NUMBER;
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 7c7016840..90cf3d855 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -26,6 +26,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
+import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
import com.android.inputmethod.keyboard.internal.Key;
import com.android.inputmethod.keyboard.internal.ModifierKeyState;
@@ -759,6 +760,11 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
mKeyboardView.setOnKeyboardActionListener(mInputMethodService);
+
+ // This always needs to be set since the accessibility state can
+ // potentially change without the input view being re-created.
+ AccessibleKeyboardViewProxy.setView(mKeyboardView);
+
return mCurrentInputView;
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 4b162142d..6bb80648e 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -42,9 +42,12 @@ import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.PopupWindow;
import android.widget.TextView;
+import com.android.inputmethod.accessibility.AccessibilityUtils;
+import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
import com.android.inputmethod.compat.FrameLayoutCompatUtils;
import com.android.inputmethod.keyboard.internal.Key;
import com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder;
@@ -1325,4 +1328,37 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
public boolean handleBack() {
return dismissMiniKeyboard();
}
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+ return AccessibleKeyboardViewProxy.getInstance().dispatchTouchEvent(event)
+ || super.dispatchTouchEvent(event);
+ }
+
+ return super.dispatchTouchEvent(event);
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+ final PointerTracker tracker = getPointerTracker(0);
+ return AccessibleKeyboardViewProxy.getInstance().dispatchPopulateAccessibilityEvent(
+ event, tracker) || super.dispatchPopulateAccessibilityEvent(event);
+ }
+
+ return super.dispatchPopulateAccessibilityEvent(event);
+ }
+
+ public boolean onHoverEvent(MotionEvent event) {
+ // Since reflection doesn't support calling superclass methods, this
+ // method checks for the existence of onHoverEvent() in the View class
+ // before returning a value.
+ if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+ final PointerTracker tracker = getPointerTracker(0);
+ return AccessibleKeyboardViewProxy.getInstance().onHoverEvent(event, tracker);
+ }
+
+ return false;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 1645b1678..9c6465dd2 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -53,6 +53,7 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.InputConnection;
+import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.compat.CompatUtils;
import com.android.inputmethod.compat.EditorInfoCompatUtils;
import com.android.inputmethod.compat.InputConnectionCompatUtils;
@@ -353,6 +354,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
SubtypeSwitcher.init(this, prefs);
KeyboardSwitcher.init(this, prefs);
Recorrection.init(this, prefs);
+ AccessibilityUtils.init(this, prefs);
super.onCreate();