diff options
Diffstat (limited to 'java/src/com/android/inputmethod/accessibility')
7 files changed, 656 insertions, 297 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java new file mode 100644 index 000000000..dd43166af --- /dev/null +++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2012 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.graphics.Rect; +import android.inputmethodservice.InputMethodService; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat; +import android.support.v4.view.accessibility.AccessibilityRecordCompat; +import android.util.Log; +import android.util.SparseArray; +import android.view.View; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.view.accessibility.AccessibilityEvent; +import android.view.inputmethod.EditorInfo; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardView; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * Exposes a virtual view sub-tree for {@link KeyboardView} and generates + * {@link AccessibilityEvent}s for individual {@link Key}s. + * <p> + * A virtual sub-tree is composed of imaginary {@link View}s that are reported + * as a part of the view hierarchy for accessibility purposes. This enables + * custom views that draw complex content to report them selves as a tree of + * virtual views, thus conveying their logical structure. + * </p> + */ +public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat { + private static final String TAG = AccessibilityEntityProvider.class.getSimpleName(); + + private final KeyboardView mKeyboardView; + private final InputMethodService mInputMethodService; + private final KeyCodeDescriptionMapper mKeyCodeDescriptionMapper; + private final AccessibilityUtils mAccessibilityUtils; + + /** A map of integer IDs to {@link Key}s. */ + private final SparseArray<Key> mVirtualViewIdToKey = new SparseArray<Key>(); + + /** Temporary rect used to calculate in-screen bounds. */ + private final Rect mTempBoundsInScreen = new Rect(); + + /** The parent view's cached on-screen location. */ + private final int[] mParentLocation = new int[2]; + + public AccessibilityEntityProvider(KeyboardView keyboardView, InputMethodService inputMethod) { + mKeyboardView = keyboardView; + mInputMethodService = inputMethod; + + mKeyCodeDescriptionMapper = KeyCodeDescriptionMapper.getInstance(); + mAccessibilityUtils = AccessibilityUtils.getInstance(); + + assignVirtualViewIds(); + updateParentLocation(); + + // Ensure that the on-screen bounds are cleared when the layout changes. + mKeyboardView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener); + } + + /** + * Creates and populates an {@link AccessibilityEvent} for the specified key + * and event type. + * + * @param key A key on the host keyboard view. + * @param eventType The event type to create. + * @return A populated {@link AccessibilityEvent} for the key. + * @see AccessibilityEvent + */ + public AccessibilityEvent createAccessibilityEvent(Key key, int eventType) { + final int virtualViewId = generateVirtualViewIdForKey(key); + final String keyDescription = getKeyDescription(key); + + final AccessibilityEvent event = AccessibilityEvent.obtain(eventType); + event.setPackageName(mKeyboardView.getContext().getPackageName()); + event.setClassName(key.getClass().getName()); + event.getText().add(keyDescription); + + final AccessibilityRecordCompat record = new AccessibilityRecordCompat(event); + record.setSource(mKeyboardView, virtualViewId); + + return event; + } + + /** + * Returns an {@link AccessibilityNodeInfoCompat} representing a virtual + * view, i.e. a descendant of the host View, with the given <code>virtualViewId</code> or + * the host View itself if <code>virtualViewId</code> equals to {@link View#NO_ID}. + * <p> + * A virtual descendant is an imaginary View that is reported as a part of + * the view hierarchy for accessibility purposes. This enables custom views + * that draw complex content to report them selves as a tree of virtual + * views, thus conveying their logical structure. + * </p> + * <p> + * The implementer is responsible for obtaining an accessibility node info + * from the pool of reusable instances and setting the desired properties of + * the node info before returning it. + * </p> + * + * @param virtualViewId A client defined virtual view id. + * @return A populated {@link AccessibilityNodeInfoCompat} for a virtual + * descendant or the host View. + * @see AccessibilityNodeInfoCompat + */ + @Override + public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) { + AccessibilityNodeInfoCompat info = null; + + if (virtualViewId == View.NO_ID) { + // We are requested to create an AccessibilityNodeInfo describing + // this View, i.e. the root of the virtual sub-tree. + info = AccessibilityNodeInfoCompat.obtain(mKeyboardView); + ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView, info); + + // Add the virtual children of the root View. + // TODO: Need to assign a unique ID to each key. + final Keyboard keyboard = mKeyboardView.getKeyboard(); + final Key[] keys = keyboard.mKeys; + for (Key key : keys) { + final int childVirtualViewId = generateVirtualViewIdForKey(key); + info.addChild(mKeyboardView, childVirtualViewId); + } + } else { + // Find the view that corresponds to the given id. + final Key key = mVirtualViewIdToKey.get(virtualViewId); + if (key == null) { + Log.e(TAG, "Invalid virtual view ID: " + virtualViewId); + return null; + } + + final String keyDescription = getKeyDescription(key); + final Rect boundsInParent = key.mHitBox; + + // Calculate the key's in-screen bounds. + mTempBoundsInScreen.set(boundsInParent); + mTempBoundsInScreen.offset(mParentLocation[0], mParentLocation[1]); + + final Rect boundsInScreen = mTempBoundsInScreen; + + // Obtain and initialize an AccessibilityNodeInfo with + // information about the virtual view. + info = AccessibilityNodeInfoCompat.obtain(); + info.addAction(AccessibilityNodeInfoCompat.ACTION_SELECT); + info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION); + info.setPackageName(mKeyboardView.getContext().getPackageName()); + info.setClassName(key.getClass().getName()); + info.setBoundsInParent(boundsInParent); + info.setBoundsInScreen(boundsInScreen); + info.setParent(mKeyboardView); + info.setSource(mKeyboardView, virtualViewId); + info.setBoundsInScreen(boundsInScreen); + info.setText(keyDescription); + } + + return info; + } + + /** + * Performs an accessibility action on a virtual view, i.e. a descendant of + * the host View, with the given <code>virtualViewId</code> or the host View itself if + * <code>virtualViewId</code> equals to {@link View#NO_ID}. + * + * @param action The action to perform. + * @param virtualViewId A client defined virtual view id. + * @return True if the action was performed. + * @see #createAccessibilityNodeInfo(int) + * @see AccessibilityNodeInfoCompat + */ + @Override + public boolean performAccessibilityAction(int action, int virtualViewId) { + if (virtualViewId == View.NO_ID) { + // Perform the action on the host View. + switch (action) { + case AccessibilityNodeInfoCompat.ACTION_SELECT: + if (!mKeyboardView.isSelected()) { + mKeyboardView.setSelected(true); + return mKeyboardView.isSelected(); + } + break; + case AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION: + if (mKeyboardView.isSelected()) { + mKeyboardView.setSelected(false); + return !mKeyboardView.isSelected(); + } + break; + } + } else { + // Find the view that corresponds to the given id. + final Key child = mVirtualViewIdToKey.get(virtualViewId); + if (child == null) + return false; + + // Perform the action on a virtual view. + switch (action) { + case AccessibilityNodeInfoCompat.ACTION_SELECT: + // TODO: Provide some focus indicator. + return true; + case AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION: + // TODO: Provide some clear focus indicator. + return true; + } + } + + return false; + } + + /** + * Finds {@link AccessibilityNodeInfoCompat}s by text. The match is case + * insensitive containment. The search is relative to the virtual view, i.e. + * a descendant of the host View, with the given <code>virtualViewId</code> or the host + * View itself <code>virtualViewId</code> equals to {@link View#NO_ID}. + * + * @param virtualViewId A client defined virtual view id which defined the + * root of the tree in which to perform the search. + * @param text The searched text. + * @return A list of node info. + * @see #createAccessibilityNodeInfo(int) + * @see AccessibilityNodeInfoCompat + */ + @Override + public List<AccessibilityNodeInfoCompat> findAccessibilityNodeInfosByText( + String text, int virtualViewId) { + final String searchedLowerCase = text.toLowerCase(); + final Keyboard keyboard = mKeyboardView.getKeyboard(); + + List<AccessibilityNodeInfoCompat> results = null; + + if (virtualViewId == View.NO_ID) { + for (Key key : keyboard.mKeys) { + results = findByTextAndPopulate(searchedLowerCase, key, results); + } + } else { + final Key key = mVirtualViewIdToKey.get(virtualViewId); + + results = findByTextAndPopulate(searchedLowerCase, key, results); + } + + if (results == null) { + return Collections.emptyList(); + } + + return results; + } + + /** + * Helper method for {@link #findAccessibilityNodeInfosByText(String, int)}. + * Takes a current set of results and matches a specified key against a + * lower-case search string. Returns an updated list of results. + * + * @param searchedLowerCase The lower-case search string. + * @param key The key to compare against. + * @param results The current list of results, or {@code null} if no results + * found. + * @return An updated list of results, or {@code null} if no results found. + */ + private List<AccessibilityNodeInfoCompat> findByTextAndPopulate(String searchedLowerCase, + Key key, List<AccessibilityNodeInfoCompat> results) { + if (!keyContainsText(key, searchedLowerCase)) { + return results; + } + + final int childVirtualViewId = generateVirtualViewIdForKey(key); + final AccessibilityNodeInfoCompat nodeInfo = createAccessibilityNodeInfo( + childVirtualViewId); + + if (results == null) { + results = new LinkedList<AccessibilityNodeInfoCompat>(); + } + + results.add(nodeInfo); + + return results; + } + + /** + * Returns whether a key's current description contains the lower-case + * search text. + * + * @param key The key to compare against. + * @param textLowerCase The lower-case search string. + * @return {@code true} if the key contains the search text. + */ + private boolean keyContainsText(Key key, String textLowerCase) { + if (key == null) { + return false; + } + + final String description = getKeyDescription(key); + + if (description == null) { + return false; + } + + return description.toLowerCase().contains(textLowerCase); + } + + /** + * Returns the context-specific description for a {@link Key}. + * + * @param key The key to describe. + * @return The context-specific description of the key. + */ + private String getKeyDescription(Key key) { + final EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo(); + final boolean shouldObscure = mAccessibilityUtils.shouldObscureInput(editorInfo); + final String keyDescription = mKeyCodeDescriptionMapper.getDescriptionForKey( + mKeyboardView.getContext(), mKeyboardView.getKeyboard(), key, shouldObscure); + + return keyDescription; + } + + /** + * Assigns virtual view IDs to keyboard keys and populates the related maps. + */ + private void assignVirtualViewIds() { + final Keyboard keyboard = mKeyboardView.getKeyboard(); + if (keyboard == null) { + return; + } + + mVirtualViewIdToKey.clear(); + + final Key[] keys = keyboard.mKeys; + for (Key key : keys) { + final int virtualViewId = generateVirtualViewIdForKey(key); + mVirtualViewIdToKey.put(virtualViewId, key); + } + } + + /** + * Updates the parent's on-screen location. + */ + private void updateParentLocation() { + mKeyboardView.getLocationOnScreen(mParentLocation); + } + + /** + * Generates a virtual view identifier for the specified key. + * + * @param key The key to identify. + * @return A virtual view identifier. + */ + private static int generateVirtualViewIdForKey(Key key) { + // The key code is unique within an instance of a Keyboard. + return key.mCode; + } + + private final OnGlobalLayoutListener mGlobalLayoutListener = new OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + assignVirtualViewIds(); + updateParentLocation(); + } + }; +} diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java index 46663327d..667b109cb 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java @@ -17,7 +17,6 @@ package com.android.inputmethod.accessibility; import android.content.Context; -import android.content.SharedPreferences; import android.inputmethodservice.InputMethodService; import android.media.AudioManager; import android.os.SystemClock; @@ -28,11 +27,9 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.EditorInfo; -import com.android.inputmethod.compat.AccessibilityManagerCompatWrapper; import com.android.inputmethod.compat.AudioManagerCompatWrapper; -import com.android.inputmethod.compat.InputTypeCompatUtils; -import com.android.inputmethod.compat.MotionEventCompatUtils; import com.android.inputmethod.compat.SettingsSecureCompatUtils; +import com.android.inputmethod.latin.InputTypeUtils; import com.android.inputmethod.latin.R; public class AccessibilityUtils { @@ -45,7 +42,6 @@ public class AccessibilityUtils { private Context mContext; private AccessibilityManager mAccessibilityManager; - private AccessibilityManagerCompatWrapper mCompatManager; private AudioManagerCompatWrapper mAudioManager; /* @@ -55,15 +51,15 @@ public class AccessibilityUtils { */ private static final boolean ENABLE_ACCESSIBILITY = true; - public static void init(InputMethodService inputMethod, SharedPreferences prefs) { + public static void init(InputMethodService inputMethod) { 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); + sInstance.initInternal(inputMethod); + KeyCodeDescriptionMapper.init(); + AccessibleInputMethodServiceProxy.init(inputMethod); + AccessibleKeyboardViewProxy.init(inputMethod); } public static AccessibilityUtils getInstance() { @@ -74,11 +70,10 @@ public class AccessibilityUtils { // This class is not publicly instantiable. } - private void initInternal(Context context, SharedPreferences prefs) { + private void initInternal(Context context) { mContext = context; mAccessibilityManager = (AccessibilityManager) context .getSystemService(Context.ACCESSIBILITY_SERVICE); - mCompatManager = new AccessibilityManagerCompatWrapper(mAccessibilityManager); final AudioManager audioManager = (AudioManager) context .getSystemService(Context.AUDIO_SERVICE); @@ -95,7 +90,7 @@ public class AccessibilityUtils { public boolean isTouchExplorationEnabled() { return ENABLE_ACCESSIBILITY && mAccessibilityManager.isEnabled() - && mCompatManager.isTouchExplorationEnabled(); + && mAccessibilityManager.isTouchExplorationEnabled(); } /** @@ -109,19 +104,19 @@ public class AccessibilityUtils { 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; + return action == MotionEvent.ACTION_HOVER_ENTER + || action == MotionEvent.ACTION_HOVER_EXIT + || action == MotionEvent.ACTION_HOVER_MOVE; } /** * Returns whether the device should obscure typed password characters. * Typically this means speaking "dot" in place of non-control characters. - * + * * @return {@code true} if the device should obscure password characters. */ - public boolean shouldObscureInput(EditorInfo attribute) { - if (attribute == null) + public boolean shouldObscureInput(EditorInfo editorInfo) { + if (editorInfo == null) return false; // The user can optionally force speaking passwords. @@ -137,7 +132,7 @@ public class AccessibilityUtils { return false; // Don't speak if the IME is connected to a password field. - return InputTypeCompatUtils.isPasswordInputType(attribute.inputType); + return InputTypeUtils.isPasswordInputType(editorInfo.inputType); } /** @@ -171,11 +166,11 @@ public class AccessibilityUtils { * Handles speaking the "connect a headset to hear passwords" notification * when connecting to a password field. * - * @param attribute The input connection's editor info attribute. + * @param editorInfo The input connection's editor info attribute. * @param restarting Whether the connection is being restarted. */ - public void onStartInputViewInternal(EditorInfo attribute, boolean restarting) { - if (shouldObscureInput(attribute)) { + public void onStartInputViewInternal(EditorInfo editorInfo, boolean restarting) { + if (shouldObscureInput(editorInfo)) { final CharSequence text = mContext.getText(R.string.spoken_use_headphones); speak(text); } diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java index 4ab9cb898..961176bb8 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java @@ -17,31 +17,15 @@ package com.android.inputmethod.accessibility; import android.content.Context; -import android.content.SharedPreferences; import android.inputmethodservice.InputMethodService; import android.media.AudioManager; -import android.os.Looper; -import android.os.Message; import android.os.Vibrator; -import android.text.TextUtils; import android.view.KeyEvent; -import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.StaticInnerHandlerWrapper; 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; - /** * Duration of the key click vibration in milliseconds. */ @@ -52,38 +36,9 @@ public class AccessibleInputMethodServiceProxy implements AccessibleKeyboardActi private InputMethodService mInputMethod; private Vibrator mVibrator; private AudioManager mAudioManager; - private AccessibilityHandler mAccessibilityHandler; - private static class AccessibilityHandler - extends StaticInnerHandlerWrapper<AccessibleInputMethodServiceProxy> { - private static final int MSG_NO_HOVER_SELECTION = 0; - - public AccessibilityHandler(AccessibleInputMethodServiceProxy outerInstance, - Looper looper) { - super(outerInstance, looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_NO_HOVER_SELECTION: - getOuterInstance().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 void init(InputMethodService inputMethod) { + sInstance.initInternal(inputMethod); } public static AccessibleInputMethodServiceProxy getInstance() { @@ -94,30 +49,10 @@ public class AccessibleInputMethodServiceProxy implements AccessibleKeyboardActi // Not publicly instantiable. } - private void initInternal(InputMethodService inputMethod, SharedPreferences prefs) { + private void initInternal(InputMethodService inputMethod) { mInputMethod = inputMethod; mVibrator = (Vibrator) inputMethod.getSystemService(Context.VIBRATOR_SERVICE); mAudioManager = (AudioManager) inputMethod.getSystemService(Context.AUDIO_SERVICE); - mAccessibilityHandler = new AccessibilityHandler(this, 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(); } /** @@ -125,8 +60,6 @@ public class AccessibleInputMethodServiceProxy implements AccessibleKeyboardActi */ @Override public void onFlickGesture(int direction) { - final int keyEventCode; - switch (direction) { case FlickGestureDetector.FLICK_LEFT: sendDownUpKeyEvents(KeyEvent.KEYCODE_DPAD_LEFT); @@ -148,27 +81,4 @@ public class AccessibleInputMethodServiceProxy implements AccessibleKeyboardActi mAudioManager.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, FX_VOLUME); mInputMethod.sendDownUpKeyEvents(keyCode); } - - /** - * 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 index c1e92bec8..31d17d09f 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardActionListener.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardActionListener.java @@ -18,24 +18,6 @@ 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); - - /** * @param direction the direction of the flick gesture, one of * <ul> * <li>{@link FlickGestureDetector#FLICK_UP} diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java index cef82267f..c85a5514e 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java @@ -17,35 +17,36 @@ package com.android.inputmethod.accessibility; import android.content.Context; -import android.content.SharedPreferences; import android.graphics.Color; import android.graphics.Paint; import android.inputmethodservice.InputMethodService; -import android.util.Log; +import android.support.v4.view.AccessibilityDelegateCompat; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.accessibility.AccessibilityEventCompat; import android.view.MotionEvent; +import android.view.View; import android.view.accessibility.AccessibilityEvent; -import android.view.inputmethod.EditorInfo; -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.Keyboard; +import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.LatinKeyboardView; import com.android.inputmethod.keyboard.PointerTracker; +import com.android.inputmethod.latin.R; -public class AccessibleKeyboardViewProxy { - private static final String TAG = AccessibleKeyboardViewProxy.class.getSimpleName(); +public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat { private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy(); private InputMethodService mInputMethod; private FlickGestureDetector mGestureDetector; private LatinKeyboardView mView; private AccessibleKeyboardActionListener mListener; + private AccessibilityEntityProvider mAccessibilityNodeProvider; - private int mLastHoverKeyIndex = KeyDetector.NOT_A_KEY; + private Key mLastHoverKey = null; - public static void init(InputMethodService inputMethod, SharedPreferences prefs) { - sInstance.initInternal(inputMethod, prefs); + public static void init(InputMethodService inputMethod) { + sInstance.initInternal(inputMethod); sInstance.mListener = AccessibleInputMethodServiceProxy.getInstance(); } @@ -53,15 +54,11 @@ public class AccessibleKeyboardViewProxy { return sInstance; } - public static void setView(LatinKeyboardView view) { - sInstance.mView = view; - } - private AccessibleKeyboardViewProxy() { // Not publicly instantiable. } - private void initInternal(InputMethodService inputMethod, SharedPreferences prefs) { + private void initInternal(InputMethodService inputMethod) { final Paint paint = new Paint(); paint.setTextAlign(Paint.Align.LEFT); paint.setTextSize(14.0f); @@ -72,35 +69,39 @@ public class AccessibleKeyboardViewProxy { mGestureDetector = new KeyboardFlickGestureDetector(inputMethod); } - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event, - PointerTracker tracker) { - if (mView == null) { - Log.e(TAG, "No keyboard view set!"); - return false; + /** + * Sets the view wrapped by this proxy. + * + * @param view The view to wrap. + */ + public void setView(LatinKeyboardView view) { + if (view == null) { + // Ignore null views. + return; } - switch (event.getEventType()) { - case AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_ENTER: - final Key key = tracker.getKey(mLastHoverKeyIndex); - - if (key == null) - break; - - final EditorInfo info = mInputMethod.getCurrentInputEditorInfo(); - final boolean shouldObscure = AccessibilityUtils.getInstance().shouldObscureInput(info); - final CharSequence description = KeyCodeDescriptionMapper.getInstance() - .getDescriptionForKey(mView.getContext(), mView.getKeyboard(), key, - shouldObscure); + mView = view; - if (description == null) - return false; - - event.getText().add(description); + // Ensure that the view has an accessibility delegate. + ViewCompat.setAccessibilityDelegate(view, this); + } - break; + /** + * Proxy method for View.getAccessibilityNodeProvider(). This method is + * called in SDK version 15 and higher to obtain the virtual node hierarchy + * provider. + * + * @return The accessibility node provider for the current keyboard. + */ + @Override + public AccessibilityEntityProvider getAccessibilityNodeProvider(View host) { + // Instantiate the provide only when requested. Since the system + // will call this method multiple times it is a good practice to + // cache the provider instance. + if (mAccessibilityNodeProvider == null) { + mAccessibilityNodeProvider = new AccessibilityEntityProvider(mView, mInputMethod); } - - return true; + return mAccessibilityNodeProvider; } /** @@ -123,53 +124,94 @@ public class AccessibleKeyboardViewProxy { * @param event The touch exploration hover event. * @return {@code true} if the event was handled */ - /*package*/ boolean onHoverEventInternal(MotionEvent event, PointerTracker tracker) { + /* package */boolean onHoverEventInternal(MotionEvent event, PointerTracker tracker) { final int x = (int) event.getX(); final int y = (int) event.getY(); + final Key key = tracker.getKeyOn(x, y); + final Key previousKey = mLastHoverKey; + + mLastHoverKey = key; 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; - fireKeyHoverEvent(tracker, mLastHoverKeyIndex, true); + case MotionEvent.ACTION_HOVER_ENTER: + case MotionEvent.ACTION_HOVER_EXIT: + return onHoverKey(key, event); + case MotionEvent.ACTION_HOVER_MOVE: + if (key != previousKey) { + return onTransitionKey(key, previousKey, event); + } else { + return onHoverKey(key, event); } - - 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; - } + /** + * Simulates a transition between two {@link Key}s by sending a HOVER_EXIT + * on the previous key, a HOVER_ENTER on the current key, and a HOVER_MOVE + * on the current key. + * + * @param currentKey The currently hovered key. + * @param previousKey The previously hovered key. + * @param event The event that triggered the transition. + * @return {@code true} if the event was handled. + */ + private boolean onTransitionKey(Key currentKey, Key previousKey, MotionEvent event) { + final int savedAction = event.getAction(); - if (mView == null) { - Log.e(TAG, "No keyboard view set!"); - return; - } + event.setAction(MotionEvent.ACTION_HOVER_EXIT); + onHoverKey(previousKey, event); - if (keyIndex == KeyDetector.NOT_A_KEY) - return; + event.setAction(MotionEvent.ACTION_HOVER_ENTER); + onHoverKey(currentKey, event); - final Key key = tracker.getKey(keyIndex); + event.setAction(MotionEvent.ACTION_HOVER_MOVE); + final boolean handled = onHoverKey(currentKey, event); - if (key == null) - return; + event.setAction(savedAction); + + return handled; + } + + /** + * Handles a hover event on a key. If {@link Key} extended View, this would + * be analogous to calling View.onHoverEvent(MotionEvent). + * + * @param key The currently hovered key. + * @param event The hover event. + * @return {@code true} if the event was handled. + */ + private boolean onHoverKey(Key key, MotionEvent event) { + // Null keys can't receive events. + if (key == null) { + return false; + } - 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); + switch (event.getAction()) { + case MotionEvent.ACTION_HOVER_ENTER: + sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER); + break; + case MotionEvent.ACTION_HOVER_EXIT: + sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT); + break; } + + return true; + } + + /** + * Populates and sends an {@link AccessibilityEvent} for the specified key. + * + * @param key The key to send an event for. + * @param eventType The type of event to send. + */ + private void sendAccessibilityEventForKey(Key key, int eventType) { + final AccessibilityEntityProvider nodeProvider = getAccessibilityNodeProvider(null); + final AccessibilityEvent event = nodeProvider.createAccessibilityEvent(key, eventType); + + // Propagates the event up the view hierarchy. + mView.getParent().requestSendAccessibilityEvent(mView, event); } private class KeyboardFlickGestureDetector extends FlickGestureDetector { @@ -185,4 +227,71 @@ public class AccessibleKeyboardViewProxy { return true; } } + + /** + * Notifies the user of changes in the keyboard shift state. + */ + public void notifyShiftState() { + final Keyboard keyboard = mView.getKeyboard(); + final KeyboardId keyboardId = keyboard.mId; + final int elementId = keyboardId.mElementId; + final Context context = mView.getContext(); + final CharSequence text; + + switch (elementId) { + case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: + text = context.getText(R.string.spoken_description_shiftmode_locked); + break; + case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: + case KeyboardId.ELEMENT_SYMBOLS_SHIFTED: + text = context.getText(R.string.spoken_description_shiftmode_on); + break; + default: + text = context.getText(R.string.spoken_description_shiftmode_off); + } + + AccessibilityUtils.getInstance().speak(text); + } + + /** + * Notifies the user of changes in the keyboard symbols state. + */ + public void notifySymbolsState() { + final Keyboard keyboard = mView.getKeyboard(); + final Context context = mView.getContext(); + final KeyboardId keyboardId = keyboard.mId; + final int elementId = keyboardId.mElementId; + final int resId; + + switch (elementId) { + case KeyboardId.ELEMENT_ALPHABET: + case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: + resId = R.string.spoken_description_mode_alpha; + break; + case KeyboardId.ELEMENT_SYMBOLS: + case KeyboardId.ELEMENT_SYMBOLS_SHIFTED: + resId = R.string.spoken_description_mode_symbol; + break; + case KeyboardId.ELEMENT_PHONE: + resId = R.string.spoken_description_mode_phone; + break; + case KeyboardId.ELEMENT_PHONE_SYMBOLS: + resId = R.string.spoken_description_mode_phone_shift; + break; + default: + resId = -1; + } + + if (resId < 0) { + return; + } + + final String text = context.getString(resId); + AccessibilityUtils.getInstance().speak(text); + } } diff --git a/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java b/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java index 9d99e3131..e8ec37600 100644 --- a/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java +++ b/java/src/com/android/inputmethod/accessibility/FlickGestureDetector.java @@ -21,7 +21,6 @@ import android.os.Message; import android.view.MotionEvent; import android.view.ViewConfiguration; -import com.android.inputmethod.compat.MotionEventCompatUtils; import com.android.inputmethod.keyboard.PointerTracker; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; @@ -31,10 +30,10 @@ import com.android.inputmethod.latin.StaticInnerHandlerWrapper; * A flick gesture is defined as a stream of hover events with the following * properties: * <ul> - * <li>Begins with a {@link MotionEventCompatUtils#ACTION_HOVER_ENTER} event - * <li>Contains any number of {@link MotionEventCompatUtils#ACTION_HOVER_MOVE} + * <li>Begins with a {@link MotionEvent#ACTION_HOVER_ENTER} event + * <li>Contains any number of {@link MotionEvent#ACTION_HOVER_MOVE} * events - * <li>Ends with a {@link MotionEventCompatUtils#ACTION_HOVER_EXIT} event + * <li>Ends with a {@link MotionEvent#ACTION_HOVER_EXIT} event * <li>Maximum duration of 250 milliseconds * <li>Minimum distance between enter and exit points must be at least equal to * scaled double tap slop (see @@ -112,7 +111,7 @@ public abstract class FlickGestureDetector { public boolean onHoverEvent(MotionEvent event, AccessibleKeyboardViewProxy view, PointerTracker tracker) { // Always cache and consume the first hover event. - if (event.getAction() == MotionEventCompatUtils.ACTION_HOVER_ENTER) { + if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) { mCachedView = view; mCachedTracker = tracker; mCachedHoverEnter = MotionEvent.obtain(event); @@ -126,13 +125,12 @@ public abstract class FlickGestureDetector { } final float distanceSquare = calculateDistanceSquare(mCachedHoverEnter, event); - final long timeout = event.getEventTime() - mCachedHoverEnter.getEventTime(); switch (event.getAction()) { - case MotionEventCompatUtils.ACTION_HOVER_MOVE: + case MotionEvent.ACTION_HOVER_MOVE: // Consume all valid move events before timeout. return true; - case MotionEventCompatUtils.ACTION_HOVER_EXIT: + case MotionEvent.ACTION_HOVER_EXIT: // Ignore exit events outside the flick radius. if (distanceSquare < mFlickRadiusSquare) { clearFlick(true); @@ -171,10 +169,8 @@ public abstract class FlickGestureDetector { * Computes the direction of a flick gesture and forwards it to * {@link #onFlick(MotionEvent, MotionEvent, int)} for handling. * - * @param e1 The {@link MotionEventCompatUtils#ACTION_HOVER_ENTER} event - * where the flick started. - * @param e2 The {@link MotionEventCompatUtils#ACTION_HOVER_EXIT} event - * where the flick ended. + * @param e1 The {@link MotionEvent#ACTION_HOVER_ENTER} event where the flick started. + * @param e2 The {@link MotionEvent#ACTION_HOVER_EXIT} event where the flick ended. * @return {@code true} if the flick event was handled. */ private boolean dispatchFlick(MotionEvent e1, MotionEvent e2) { diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java index 7302830d4..3d861c231 100644 --- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java +++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java @@ -17,8 +17,8 @@ package com.android.inputmethod.accessibility; import android.content.Context; -import android.content.SharedPreferences; import android.text.TextUtils; +import android.util.Log; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; @@ -28,6 +28,8 @@ import com.android.inputmethod.latin.R; import java.util.HashMap; public class KeyCodeDescriptionMapper { + private static final String TAG = KeyCodeDescriptionMapper.class.getSimpleName(); + // The resource ID of the string spoken for obscured keys private static final int OBSCURED_KEY_RES_ID = R.string.spoken_description_dot; @@ -39,14 +41,8 @@ public class KeyCodeDescriptionMapper { // 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 void init() { + sInstance.initInternal(); } public static KeyCodeDescriptionMapper getInstance() { @@ -56,40 +52,15 @@ public class KeyCodeDescriptionMapper { 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) { + private void initInternal() { // 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); - mKeyCodeMap.put((int) '\uFF0A', R.string.spoken_description_star); - // 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); @@ -98,12 +69,6 @@ public class KeyCodeDescriptionMapper { 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); } /** @@ -125,27 +90,31 @@ public class KeyCodeDescriptionMapper { * @return a character sequence describing the action performed by pressing * the key */ - public CharSequence getDescriptionForKey(Context context, Keyboard keyboard, Key key, + public String getDescriptionForKey(Context context, Keyboard keyboard, Key key, boolean shouldObscure) { - if (key.mCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { - final CharSequence description = getDescriptionForSwitchAlphaSymbol(context, keyboard); + final int code = key.mCode; + + if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { + final String description = getDescriptionForSwitchAlphaSymbol(context, keyboard); if (description != null) return description; } + if (code == Keyboard.CODE_SHIFT) { + return getDescriptionForShiftKey(context, keyboard); + } + if (!TextUtils.isEmpty(key.mLabel)) { final String label = key.mLabel.toString().trim(); + // First, attempt to map the label to a pre-defined description. if (mKeyLabelMap.containsKey(label)) { return context.getString(mKeyLabelMap.get(label)); - } else if (label.length() == 1 - || (keyboard.isManualTemporaryUpperCase() && !TextUtils - .isEmpty(key.mHintLabel))) { - return getDescriptionForKeyCode(context, keyboard, key, shouldObscure); - } else { - return label; } - } else if (key.mCode != Keyboard.CODE_DUMMY) { + } + + // Just attempt to speak the description. + if (key.mCode != Keyboard.CODE_UNSPECIFIED) { return getDescriptionForKeyCode(context, keyboard, key, shouldObscure); } @@ -162,36 +131,64 @@ public class KeyCodeDescriptionMapper { * @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.isPhoneShiftKeyboard()) { - return context.getString(R.string.spoken_description_to_numeric); - } else if (id.isPhoneKeyboard()) { - return context.getString(R.string.spoken_description_to_symbol); - } else { + private String getDescriptionForSwitchAlphaSymbol(Context context, Keyboard keyboard) { + final KeyboardId keyboardId = keyboard.mId; + final int elementId = keyboardId.mElementId; + final int resId; + + switch (elementId) { + case KeyboardId.ELEMENT_ALPHABET: + case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: + resId = R.string.spoken_description_to_symbol; + break; + case KeyboardId.ELEMENT_SYMBOLS: + case KeyboardId.ELEMENT_SYMBOLS_SHIFTED: + resId = R.string.spoken_description_to_alpha; + break; + case KeyboardId.ELEMENT_PHONE: + resId = R.string.spoken_description_to_symbol; + break; + case KeyboardId.ELEMENT_PHONE_SYMBOLS: + resId = R.string.spoken_description_to_numeric; + break; + default: + Log.e(TAG, "Missing description for keyboard element ID:" + elementId); return null; } + + return context.getString(resId); } /** - * Returns the keycode for the specified key given the current keyboard - * state. + * Returns a context-sensitive description of the "Shift" key. * + * @param context The package's context. * @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 + * @return A context-sensitive description of the "Shift" key. */ - private int getCorrectKeyCode(Keyboard keyboard, Key key) { - if (keyboard.isManualTemporaryUpperCase() && !TextUtils.isEmpty(key.mHintLabel)) { - return key.mHintLabel.charAt(0); - } else { - return key.mCode; + private String getDescriptionForShiftKey(Context context, Keyboard keyboard) { + final KeyboardId keyboardId = keyboard.mId; + final int elementId = keyboardId.mElementId; + final int resId; + + switch (elementId) { + case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: + resId = R.string.spoken_description_caps_lock; + break; + case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: + case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: + case KeyboardId.ELEMENT_SYMBOLS_SHIFTED: + resId = R.string.spoken_description_shift_shifted; + break; + default: + resId = R.string.spoken_description_shift; } + + return context.getString(resId); } /** @@ -215,15 +212,9 @@ public class KeyCodeDescriptionMapper { * @return a character sequence describing the action performed by pressing * the key */ - private CharSequence getDescriptionForKeyCode(Context context, Keyboard keyboard, Key key, + private String getDescriptionForKeyCode(Context context, Keyboard keyboard, Key key, boolean shouldObscure) { - 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)); - } + final int code = key.mCode; // If the key description should be obscured, now is the time to do it. final boolean isDefinedNonCtrl = Character.isDefined(code) && !Character.isISOControl(code); |