Loading java/res/values/strings.xml +75 −0 Original line number Diff line number Diff line Loading @@ -131,6 +131,81 @@ <!-- Label for "Wait" key of phone number keyboard. Must be short to fit on key! [CHAR LIMIT=5]--> <string name="label_wait_key">Wait</string> <!-- Spoken description for the currently entered text --> <string name="spoken_current_text_is">Current text is "%s"</string> <!-- Spoken description when there is no text entered --> <string name="spoken_no_text_entered">No text entered</string> <!-- Spoken description for unknown keyboard keys. --> <string name="spoken_description_unknown">Key code %d</string> <!-- Spoken description for the "Shift" keyboard key. --> <string name="spoken_description_shift">Shift</string> <!-- Spoken description for the "Shift" keyboard key's pressed state. --> <string name="spoken_description_shift_shifted">Shift enabled</string> <!-- Spoken description for the "Shift" keyboard key's pressed state. --> <string name="spoken_description_caps_lock">Caps lock enabled</string> <!-- Spoken description for the "Delete" keyboard key. --> <string name="spoken_description_delete">Delete</string> <!-- Spoken description for the "To Symbol" keyboard key. --> <string name="spoken_description_to_symbol">Symbols</string> <!-- Spoken description for the "To Alpha" keyboard key. --> <string name="spoken_description_to_alpha">Letters</string> <!-- Spoken description for the "To Numbers" keyboard key. --> <string name="spoken_description_to_numeric">Numbers</string> <!-- Spoken description for the "Settings" keyboard key. --> <string name="spoken_description_settings">Settings</string> <!-- Spoken description for the "Tab" keyboard key. --> <string name="spoken_description_tab">Tab</string> <!-- Spoken description for the "Space" keyboard key. --> <string name="spoken_description_space">Space</string> <!-- Spoken description for the "Mic" keyboard key. --> <string name="spoken_description_mic">Voice input</string> <!-- Spoken description for the "Smiley" keyboard key. --> <string name="spoken_description_smiley">Smiley face</string> <!-- Spoken description for the "Return" keyboard key. --> <string name="spoken_description_return">Return</string> <!-- Spoken description for the "," keyboard key. --> <string name="spoken_description_comma">Comma</string> <!-- Spoken description for the "." keyboard key. --> <string name="spoken_description_period">Period</string> <!-- Spoken description for the "(" keyboard key. --> <string name="spoken_description_left_parenthesis">Left parenthesis</string> <!-- Spoken description for the ")" keyboard key. --> <string name="spoken_description_right_parenthesis">Right parenthesis</string> <!-- Spoken description for the ":" keyboard key. --> <string name="spoken_description_colon">Colon</string> <!-- Spoken description for the ";" keyboard key. --> <string name="spoken_description_semicolon">Semicolon</string> <!-- Spoken description for the "!" keyboard key. --> <string name="spoken_description_exclamation_mark">Exclamation mark</string> <!-- Spoken description for the "?" keyboard key. --> <string name="spoken_description_question_mark">Question mark</string> <!-- Spoken description for the """ keyboard key. --> <string name="spoken_description_double_quote">Double quote</string> <!-- Spoken description for the "'" keyboard key. --> <string name="spoken_description_single_quote">Single quote</string> <!-- Spoken description for the "•" keyboard key. --> <string name="spoken_description_dot">Dot</string> <!-- Spoken description for the "√" keyboard key. --> <string name="spoken_description_square_root">Square root</string> <!-- Spoken description for the "π" keyboard key. --> <string name="spoken_description_pi">Pi</string> <!-- Spoken description for the "Δ" keyboard key. --> <string name="spoken_description_delta">Delta</string> <!-- Spoken description for the "™" keyboard key. --> <string name="spoken_description_trademark">Trademark</string> <!-- Spoken description for the "℅" keyboard key. --> <string name="spoken_description_care_of">Care of</string> <!-- Spoken description for the "*" keyboard key. --> <string name="spoken_description_star">Star</string> <!-- Spoken description for the "#" keyboard key. --> <string name="spoken_description_pound">Pound</string> <!-- Spoken description for the "…" keyboard key. --> <string name="spoken_description_ellipsis">Ellipsis</string> <!-- Spoken description for the "„" keyboard key. --> <string name="spoken_description_low_double_quote">Low double quote</string> <!-- Voice related labels --> <!-- Title of the warning dialog that shows when a user initiates voice input for Loading java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java 0 → 100644 +133 −0 Original line number Diff line number Diff line /* * 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); } } java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java 0 → 100644 +129 −0 Original line number Diff line number Diff line /* * 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); } } java/src/com/android/inputmethod/accessibility/AccessibleKeyboardActionListener.java 0 → 100644 +37 −0 Original line number Diff line number Diff line /* * 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); } java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java 0 → 100644 +201 −0 Original line number Diff line number Diff line /* * 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); } } Loading
java/res/values/strings.xml +75 −0 Original line number Diff line number Diff line Loading @@ -131,6 +131,81 @@ <!-- Label for "Wait" key of phone number keyboard. Must be short to fit on key! [CHAR LIMIT=5]--> <string name="label_wait_key">Wait</string> <!-- Spoken description for the currently entered text --> <string name="spoken_current_text_is">Current text is "%s"</string> <!-- Spoken description when there is no text entered --> <string name="spoken_no_text_entered">No text entered</string> <!-- Spoken description for unknown keyboard keys. --> <string name="spoken_description_unknown">Key code %d</string> <!-- Spoken description for the "Shift" keyboard key. --> <string name="spoken_description_shift">Shift</string> <!-- Spoken description for the "Shift" keyboard key's pressed state. --> <string name="spoken_description_shift_shifted">Shift enabled</string> <!-- Spoken description for the "Shift" keyboard key's pressed state. --> <string name="spoken_description_caps_lock">Caps lock enabled</string> <!-- Spoken description for the "Delete" keyboard key. --> <string name="spoken_description_delete">Delete</string> <!-- Spoken description for the "To Symbol" keyboard key. --> <string name="spoken_description_to_symbol">Symbols</string> <!-- Spoken description for the "To Alpha" keyboard key. --> <string name="spoken_description_to_alpha">Letters</string> <!-- Spoken description for the "To Numbers" keyboard key. --> <string name="spoken_description_to_numeric">Numbers</string> <!-- Spoken description for the "Settings" keyboard key. --> <string name="spoken_description_settings">Settings</string> <!-- Spoken description for the "Tab" keyboard key. --> <string name="spoken_description_tab">Tab</string> <!-- Spoken description for the "Space" keyboard key. --> <string name="spoken_description_space">Space</string> <!-- Spoken description for the "Mic" keyboard key. --> <string name="spoken_description_mic">Voice input</string> <!-- Spoken description for the "Smiley" keyboard key. --> <string name="spoken_description_smiley">Smiley face</string> <!-- Spoken description for the "Return" keyboard key. --> <string name="spoken_description_return">Return</string> <!-- Spoken description for the "," keyboard key. --> <string name="spoken_description_comma">Comma</string> <!-- Spoken description for the "." keyboard key. --> <string name="spoken_description_period">Period</string> <!-- Spoken description for the "(" keyboard key. --> <string name="spoken_description_left_parenthesis">Left parenthesis</string> <!-- Spoken description for the ")" keyboard key. --> <string name="spoken_description_right_parenthesis">Right parenthesis</string> <!-- Spoken description for the ":" keyboard key. --> <string name="spoken_description_colon">Colon</string> <!-- Spoken description for the ";" keyboard key. --> <string name="spoken_description_semicolon">Semicolon</string> <!-- Spoken description for the "!" keyboard key. --> <string name="spoken_description_exclamation_mark">Exclamation mark</string> <!-- Spoken description for the "?" keyboard key. --> <string name="spoken_description_question_mark">Question mark</string> <!-- Spoken description for the """ keyboard key. --> <string name="spoken_description_double_quote">Double quote</string> <!-- Spoken description for the "'" keyboard key. --> <string name="spoken_description_single_quote">Single quote</string> <!-- Spoken description for the "•" keyboard key. --> <string name="spoken_description_dot">Dot</string> <!-- Spoken description for the "√" keyboard key. --> <string name="spoken_description_square_root">Square root</string> <!-- Spoken description for the "π" keyboard key. --> <string name="spoken_description_pi">Pi</string> <!-- Spoken description for the "Δ" keyboard key. --> <string name="spoken_description_delta">Delta</string> <!-- Spoken description for the "™" keyboard key. --> <string name="spoken_description_trademark">Trademark</string> <!-- Spoken description for the "℅" keyboard key. --> <string name="spoken_description_care_of">Care of</string> <!-- Spoken description for the "*" keyboard key. --> <string name="spoken_description_star">Star</string> <!-- Spoken description for the "#" keyboard key. --> <string name="spoken_description_pound">Pound</string> <!-- Spoken description for the "…" keyboard key. --> <string name="spoken_description_ellipsis">Ellipsis</string> <!-- Spoken description for the "„" keyboard key. --> <string name="spoken_description_low_double_quote">Low double quote</string> <!-- Voice related labels --> <!-- Title of the warning dialog that shows when a user initiates voice input for Loading
java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java 0 → 100644 +133 −0 Original line number Diff line number Diff line /* * 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); } }
java/src/com/android/inputmethod/accessibility/AccessibleInputMethodServiceProxy.java 0 → 100644 +129 −0 Original line number Diff line number Diff line /* * 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); } }
java/src/com/android/inputmethod/accessibility/AccessibleKeyboardActionListener.java 0 → 100644 +37 −0 Original line number Diff line number Diff line /* * 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); }
java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java 0 → 100644 +201 −0 Original line number Diff line number Diff line /* * 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); } }