Let accessibility users know to connect a headset when the IME connects to a password field.
Change-Id: If96cd7626950dd12e88a8a97f5e405d303d41e06main
parent
c1368a8efc
commit
b0c8db018d
|
@ -142,6 +142,9 @@
|
||||||
<!-- Label for "Wait" key of phone number keyboard. Must be short to fit on key! [CHAR LIMIT=5]-->
|
<!-- 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>
|
<string name="label_wait_key">Wait</string>
|
||||||
|
|
||||||
|
<!-- Spoken description to let the user know that when typing in a password, they can plug in a headset in to hear spoken descriptions of the keys they type. [CHAR LIMIT=NONE] -->
|
||||||
|
<string name="spoken_use_headphones">Plug in a headset to hear password keys spoken aloud.</string>
|
||||||
|
|
||||||
<!-- Spoken description for the currently entered text -->
|
<!-- Spoken description for the currently entered text -->
|
||||||
<string name="spoken_current_text_is">Current text is "%s"</string>
|
<string name="spoken_current_text_is">Current text is "%s"</string>
|
||||||
<!-- Spoken description when there is no text entered -->
|
<!-- Spoken description when there is no text entered -->
|
||||||
|
|
|
@ -19,15 +19,19 @@ package com.android.inputmethod.accessibility;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.inputmethodservice.InputMethodService;
|
import android.inputmethodservice.InputMethodService;
|
||||||
|
import android.media.AudioManager;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.accessibility.AccessibilityEvent;
|
import android.view.accessibility.AccessibilityEvent;
|
||||||
import android.view.accessibility.AccessibilityManager;
|
import android.view.accessibility.AccessibilityManager;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
|
||||||
import com.android.inputmethod.compat.AccessibilityEventCompatUtils;
|
|
||||||
import com.android.inputmethod.compat.AccessibilityManagerCompatWrapper;
|
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.MotionEventCompatUtils;
|
||||||
|
import com.android.inputmethod.latin.R;
|
||||||
|
|
||||||
public class AccessibilityUtils {
|
public class AccessibilityUtils {
|
||||||
private static final String TAG = AccessibilityUtils.class.getSimpleName();
|
private static final String TAG = AccessibilityUtils.class.getSimpleName();
|
||||||
|
@ -37,8 +41,10 @@ public class AccessibilityUtils {
|
||||||
|
|
||||||
private static final AccessibilityUtils sInstance = new AccessibilityUtils();
|
private static final AccessibilityUtils sInstance = new AccessibilityUtils();
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
private AccessibilityManager mAccessibilityManager;
|
private AccessibilityManager mAccessibilityManager;
|
||||||
private AccessibilityManagerCompatWrapper mCompatManager;
|
private AccessibilityManagerCompatWrapper mCompatManager;
|
||||||
|
private AudioManagerCompatWrapper mAudioManager;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Setting this constant to {@code false} will disable all keyboard
|
* Setting this constant to {@code false} will disable all keyboard
|
||||||
|
@ -67,9 +73,14 @@ public class AccessibilityUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initInternal(Context context, SharedPreferences prefs) {
|
private void initInternal(Context context, SharedPreferences prefs) {
|
||||||
|
mContext = context;
|
||||||
mAccessibilityManager = (AccessibilityManager) context
|
mAccessibilityManager = (AccessibilityManager) context
|
||||||
.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||||
mCompatManager = new AccessibilityManagerCompatWrapper(mAccessibilityManager);
|
mCompatManager = new AccessibilityManagerCompatWrapper(mAccessibilityManager);
|
||||||
|
|
||||||
|
final AudioManager audioManager = (AudioManager) context
|
||||||
|
.getSystemService(Context.AUDIO_SERVICE);
|
||||||
|
mAudioManager = new AudioManagerCompatWrapper(audioManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -101,6 +112,22 @@ public class AccessibilityUtils {
|
||||||
|| action == MotionEventCompatUtils.ACTION_HOVER_MOVE;
|
|| action == MotionEventCompatUtils.ACTION_HOVER_MOVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the device should not speak text (eg.
|
||||||
|
* non-control) characters
|
||||||
|
*/
|
||||||
|
public boolean shouldObscureInput(EditorInfo attribute) {
|
||||||
|
if (attribute == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Always speak if the user is listening through headphones.
|
||||||
|
if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Don't speak if the IME is connected to a password field.
|
||||||
|
return InputTypeCompatUtils.isPasswordInputType(attribute.inputType);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the specified text to the {@link AccessibilityManager} to be
|
* Sends the specified text to the {@link AccessibilityManager} to be
|
||||||
* spoken.
|
* spoken.
|
||||||
|
@ -117,7 +144,7 @@ public class AccessibilityUtils {
|
||||||
// class. Instead, we're just forcing a fake AccessibilityEvent into
|
// class. Instead, we're just forcing a fake AccessibilityEvent into
|
||||||
// the screen reader to make it speak.
|
// the screen reader to make it speak.
|
||||||
final AccessibilityEvent event = AccessibilityEvent
|
final AccessibilityEvent event = AccessibilityEvent
|
||||||
.obtain(AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_ENTER);
|
.obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
|
||||||
|
|
||||||
event.setPackageName(PACKAGE);
|
event.setPackageName(PACKAGE);
|
||||||
event.setClassName(CLASS);
|
event.setClassName(CLASS);
|
||||||
|
@ -127,4 +154,18 @@ public class AccessibilityUtils {
|
||||||
|
|
||||||
mAccessibilityManager.sendAccessibilityEvent(event);
|
mAccessibilityManager.sendAccessibilityEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 restarting Whether the connection is being restarted.
|
||||||
|
*/
|
||||||
|
public void onStartInputViewInternal(EditorInfo attribute, boolean restarting) {
|
||||||
|
if (shouldObscureInput(attribute)) {
|
||||||
|
final CharSequence text = mContext.getText(R.string.spoken_use_headphones);
|
||||||
|
speak(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ 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.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;
|
||||||
|
@ -29,8 +28,6 @@ import android.view.accessibility.AccessibilityEvent;
|
||||||
import android.view.inputmethod.EditorInfo;
|
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.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;
|
||||||
|
@ -48,7 +45,6 @@ public class AccessibleKeyboardViewProxy {
|
||||||
private FlickGestureDetector mGestureDetector;
|
private FlickGestureDetector mGestureDetector;
|
||||||
private LatinKeyboardView mView;
|
private LatinKeyboardView mView;
|
||||||
private AccessibleKeyboardActionListener mListener;
|
private AccessibleKeyboardActionListener mListener;
|
||||||
private AudioManagerCompatWrapper mAudioManager;
|
|
||||||
|
|
||||||
private int mScaledEdgeSlop;
|
private int mScaledEdgeSlop;
|
||||||
private int mLastHoverKeyIndex = KeyDetector.NOT_A_KEY;
|
private int mLastHoverKeyIndex = KeyDetector.NOT_A_KEY;
|
||||||
|
@ -82,26 +78,6 @@ public class AccessibleKeyboardViewProxy {
|
||||||
mInputMethod = inputMethod;
|
mInputMethod = inputMethod;
|
||||||
mGestureDetector = new KeyboardFlickGestureDetector(inputMethod);
|
mGestureDetector = new KeyboardFlickGestureDetector(inputMethod);
|
||||||
mScaledEdgeSlop = ViewConfiguration.get(inputMethod).getScaledEdgeSlop();
|
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,
|
||||||
|
@ -118,7 +94,8 @@ public class AccessibleKeyboardViewProxy {
|
||||||
if (key == null)
|
if (key == null)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
final boolean shouldObscure = shouldObscureInput();
|
final EditorInfo info = mInputMethod.getCurrentInputEditorInfo();
|
||||||
|
final boolean shouldObscure = AccessibilityUtils.getInstance().shouldObscureInput(info);
|
||||||
final CharSequence description = KeyCodeDescriptionMapper.getInstance()
|
final CharSequence description = KeyCodeDescriptionMapper.getInstance()
|
||||||
.getDescriptionForKey(mView.getContext(), mView.getKeyboard(), key,
|
.getDescriptionForKey(mView.getContext(), mView.getKeyboard(), key,
|
||||||
shouldObscure);
|
shouldObscure);
|
||||||
|
|
|
@ -693,6 +693,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Forward this event to the accessibility utilities, if enabled.
|
||||||
|
final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance();
|
||||||
|
if (accessUtils.isTouchExplorationEnabled()) {
|
||||||
|
accessUtils.onStartInputViewInternal(attribute, restarting);
|
||||||
|
}
|
||||||
|
|
||||||
mSubtypeSwitcher.updateParametersOnStartInputView();
|
mSubtypeSwitcher.updateParametersOnStartInputView();
|
||||||
|
|
||||||
TextEntryState.reset();
|
TextEntryState.reset();
|
||||||
|
|
Loading…
Reference in New Issue