Merge "Fixed speaking keys when editing password fields"

main
Ken Wakasa 2011-08-16 18:45:26 -07:00 committed by Android (Google) Code Review
commit e486175987
3 changed files with 114 additions and 14 deletions

View File

@ -20,12 +20,18 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.inputmethodservice.InputMethodService;
import android.media.AudioManager;
import android.util.Log; import android.util.Log;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.ViewConfiguration; import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityEvent;
import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.compat.AccessibilityEventCompatUtils; import com.android.inputmethod.compat.AccessibilityEventCompatUtils;
import com.android.inputmethod.compat.AudioManagerCompatWrapper;
import com.android.inputmethod.compat.EditorInfoCompatUtils;
import com.android.inputmethod.compat.InputTypeCompatUtils;
import com.android.inputmethod.compat.MotionEventCompatUtils; import com.android.inputmethod.compat.MotionEventCompatUtils;
import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyDetector; import com.android.inputmethod.keyboard.KeyDetector;
@ -39,17 +45,19 @@ public class AccessibleKeyboardViewProxy {
// Delay in milliseconds between key press DOWN and UP events // Delay in milliseconds between key press DOWN and UP events
private static final long DELAY_KEY_PRESS = 10; private static final long DELAY_KEY_PRESS = 10;
private int mScaledEdgeSlop; private InputMethodService mInputMethod;
private FlickGestureDetector mGestureDetector;
private LatinKeyboardBaseView mView; private LatinKeyboardBaseView mView;
private AccessibleKeyboardActionListener mListener; private AccessibleKeyboardActionListener mListener;
private FlickGestureDetector mGestureDetector; private AudioManagerCompatWrapper mAudioManager;
private int mScaledEdgeSlop;
private int mLastHoverKeyIndex = KeyDetector.NOT_A_KEY; private int mLastHoverKeyIndex = KeyDetector.NOT_A_KEY;
private int mLastX = -1; private int mLastX = -1;
private int mLastY = -1; private int mLastY = -1;
public static void init(Context context, SharedPreferences prefs) { public static void init(InputMethodService inputMethod, SharedPreferences prefs) {
sInstance.initInternal(context, prefs); sInstance.initInternal(inputMethod, prefs);
sInstance.mListener = AccessibleInputMethodServiceProxy.getInstance(); sInstance.mListener = AccessibleInputMethodServiceProxy.getInstance();
} }
@ -65,15 +73,36 @@ public class AccessibleKeyboardViewProxy {
// Not publicly instantiable. // Not publicly instantiable.
} }
private void initInternal(Context context, SharedPreferences prefs) { private void initInternal(InputMethodService inputMethod, SharedPreferences prefs) {
final Paint paint = new Paint(); final Paint paint = new Paint();
paint.setTextAlign(Paint.Align.LEFT); paint.setTextAlign(Paint.Align.LEFT);
paint.setTextSize(14.0f); paint.setTextSize(14.0f);
paint.setAntiAlias(true); paint.setAntiAlias(true);
paint.setColor(Color.YELLOW); paint.setColor(Color.YELLOW);
mGestureDetector = new KeyboardFlickGestureDetector(context); mInputMethod = inputMethod;
mScaledEdgeSlop = ViewConfiguration.get(context).getScaledEdgeSlop(); mGestureDetector = new KeyboardFlickGestureDetector(inputMethod);
mScaledEdgeSlop = ViewConfiguration.get(inputMethod).getScaledEdgeSlop();
final AudioManager audioManager = (AudioManager) inputMethod
.getSystemService(Context.AUDIO_SERVICE);
mAudioManager = new AudioManagerCompatWrapper(audioManager);
}
/**
* @return {@code true} if the device should not speak text (eg. non-control) characters
*/
private boolean shouldObscureInput() {
// Always speak if the user is listening through headphones.
if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn())
return false;
final EditorInfo info = mInputMethod.getCurrentInputEditorInfo();
if (info == null)
return false;
// Don't speak if the IME is connected to a password field.
return InputTypeCompatUtils.isPasswordInputType(info.inputType);
} }
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event, public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event,
@ -90,8 +119,10 @@ public class AccessibleKeyboardViewProxy {
if (key == null) if (key == null)
break; break;
final boolean shouldObscure = shouldObscureInput();
final CharSequence description = KeyCodeDescriptionMapper.getInstance() final CharSequence description = KeyCodeDescriptionMapper.getInstance()
.getDescriptionForKey(mView.getContext(), mView.getKeyboard(), key); .getDescriptionForKey(mView.getContext(), mView.getKeyboard(), key,
shouldObscure);
if (description == null) if (description == null)
return false; return false;

View File

@ -28,6 +28,9 @@ import com.android.inputmethod.latin.R;
import java.util.HashMap; import java.util.HashMap;
public class KeyCodeDescriptionMapper { public class KeyCodeDescriptionMapper {
// The resource ID of the string spoken for obscured keys
private static final int OBSCURED_KEY_RES_ID = R.string.spoken_description_dot;
private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper(); private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper();
// Map of key labels to spoken description resource IDs // Map of key labels to spoken description resource IDs
@ -118,10 +121,12 @@ public class KeyCodeDescriptionMapper {
* @param context The package's context. * @param context The package's context.
* @param keyboard The keyboard on which the key resides. * @param keyboard The keyboard on which the key resides.
* @param key The key from which to obtain a description. * @param key The key from which to obtain a description.
* @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured.
* @return a character sequence describing the action performed by pressing * @return a character sequence describing the action performed by pressing
* the key * the key
*/ */
public CharSequence getDescriptionForKey(Context context, Keyboard keyboard, Key key) { public CharSequence getDescriptionForKey(Context context, Keyboard keyboard, Key key,
boolean shouldObscure) {
if (key.mCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { if (key.mCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
final CharSequence description = getDescriptionForSwitchAlphaSymbol(context, keyboard); final CharSequence description = getDescriptionForSwitchAlphaSymbol(context, keyboard);
if (description != null) if (description != null)
@ -136,12 +141,12 @@ public class KeyCodeDescriptionMapper {
} else if (label.length() == 1 } else if (label.length() == 1
|| (keyboard.isManualTemporaryUpperCase() && !TextUtils || (keyboard.isManualTemporaryUpperCase() && !TextUtils
.isEmpty(key.mHintLabel))) { .isEmpty(key.mHintLabel))) {
return getDescriptionForKeyCode(context, keyboard, key); return getDescriptionForKeyCode(context, keyboard, key, shouldObscure);
} else { } else {
return label; return label;
} }
} else if (key.mCode != Keyboard.CODE_DUMMY) { } else if (key.mCode != Keyboard.CODE_DUMMY) {
return getDescriptionForKeyCode(context, keyboard, key); return getDescriptionForKeyCode(context, keyboard, key, shouldObscure);
} }
return null; return null;
@ -206,19 +211,29 @@ public class KeyCodeDescriptionMapper {
* @param context The package's context. * @param context The package's context.
* @param keyboard The keyboard on which the key resides. * @param keyboard The keyboard on which the key resides.
* @param key The key from which to obtain a description. * @param key The key from which to obtain a description.
* @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured.
* @return a character sequence describing the action performed by pressing * @return a character sequence describing the action performed by pressing
* the key * the key
*/ */
private CharSequence getDescriptionForKeyCode(Context context, Keyboard keyboard, Key key) { private CharSequence getDescriptionForKeyCode(Context context, Keyboard keyboard, Key key,
boolean shouldObscure) {
final int code = getCorrectKeyCode(keyboard, key); final int code = getCorrectKeyCode(keyboard, key);
if (keyboard.isShiftLocked() && mShiftLockedKeyCodeMap.containsKey(code)) { if (keyboard.isShiftLocked() && mShiftLockedKeyCodeMap.containsKey(code)) {
return context.getString(mShiftLockedKeyCodeMap.get(code)); return context.getString(mShiftLockedKeyCodeMap.get(code));
} else if (keyboard.isShiftedOrShiftLocked() && mShiftedKeyCodeMap.containsKey(code)) { } else if (keyboard.isShiftedOrShiftLocked() && mShiftedKeyCodeMap.containsKey(code)) {
return context.getString(mShiftedKeyCodeMap.get(code)); return context.getString(mShiftedKeyCodeMap.get(code));
} else if (mKeyCodeMap.containsKey(code)) { }
// If the key description should be obscured, now is the time to do it.
final boolean isDefinedNonCtrl = Character.isDefined(code) && !Character.isISOControl(code);
if (shouldObscure && isDefinedNonCtrl) {
return context.getString(OBSCURED_KEY_RES_ID);
}
if (mKeyCodeMap.containsKey(code)) {
return context.getString(mKeyCodeMap.get(code)); return context.getString(mKeyCodeMap.get(code));
} else if (Character.isDefined(code) && !Character.isISOControl(code)) { } else if (isDefinedNonCtrl) {
return Character.toString((char) code); return Character.toString((char) code);
} else { } else {
return context.getString(R.string.spoken_description_unknown, code); return context.getString(R.string.spoken_description_unknown, code);

View File

@ -0,0 +1,54 @@
/*
* 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.media.AudioManager;
import java.lang.reflect.Method;
public class AudioManagerCompatWrapper {
private static final Method METHOD_isWiredHeadsetOn = CompatUtils.getMethod(
AudioManager.class, "isWiredHeadsetOn");
private static final Method METHOD_isBluetoothA2dpOn = CompatUtils.getMethod(
AudioManager.class, "isBluetoothA2dpOn");
private final AudioManager mManager;
public AudioManagerCompatWrapper(AudioManager manager) {
mManager = manager;
}
/**
* Checks whether audio routing to the wired headset is on or off.
*
* @return true if audio is being routed to/from wired headset;
* false if otherwise
*/
public boolean isWiredHeadsetOn() {
return (Boolean) CompatUtils.invoke(mManager, false, METHOD_isWiredHeadsetOn);
}
/**
* Checks whether A2DP audio routing to the Bluetooth headset is on or off.
*
* @return true if A2DP audio is being routed to/from Bluetooth headset;
* false if otherwise
*/
public boolean isBluetoothA2dpOn() {
return (Boolean) CompatUtils.invoke(mManager, false, METHOD_isBluetoothA2dpOn);
}
}