Merge "Added support for touch exploration to Latin IME."

This commit is contained in:
Svetoslav Ganov 2011-06-22 11:50:28 -07:00 committed by Android (Google) Code Review
commit 8521781fd7
13 changed files with 954 additions and 1 deletions

View file

@ -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

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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();