1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.inputmethod.accessibility; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.inputmethodservice.InputMethodService; 22 import android.media.AudioManager; 23 import android.os.Looper; 24 import android.os.Message; 25 import android.os.Vibrator; 26 import android.text.TextUtils; 27 import android.view.KeyEvent; 28 import android.view.inputmethod.ExtractedText; 29 import android.view.inputmethod.ExtractedTextRequest; 30 31 import com.android.inputmethod.latin.R; 32 import com.android.inputmethod.latin.StaticInnerHandlerWrapper; 33 34 public class AccessibleInputMethodServiceProxy implements AccessibleKeyboardActionListener { 35 private static final AccessibleInputMethodServiceProxy sInstance = 36 new AccessibleInputMethodServiceProxy(); 37 38 /* 39 * Delay for the handler event that's fired when Accessibility is on and the 40 * user hovers outside of any valid keys. This is used to let the user know 41 * that if they lift their finger, nothing will be typed. 42 */ 43 private static final long DELAY_NO_HOVER_SELECTION = 250; 44 45 /** 46 * Duration of the key click vibration in milliseconds. 47 */ 48 private static final long VIBRATE_KEY_CLICK = 50; 49 50 private static final float FX_VOLUME = -1.0f; 51 52 private InputMethodService mInputMethod; 53 private Vibrator mVibrator; 54 private AudioManager mAudioManager; 55 private AccessibilityHandler mAccessibilityHandler; 56 57 private static class AccessibilityHandler 58 extends StaticInnerHandlerWrapper<AccessibleInputMethodServiceProxy> { 59 private static final int MSG_NO_HOVER_SELECTION = 0; 60 AccessibilityHandler(AccessibleInputMethodServiceProxy outerInstance, Looper looper)61 public AccessibilityHandler(AccessibleInputMethodServiceProxy outerInstance, 62 Looper looper) { 63 super(outerInstance, looper); 64 } 65 66 @Override handleMessage(Message msg)67 public void handleMessage(Message msg) { 68 switch (msg.what) { 69 case MSG_NO_HOVER_SELECTION: 70 getOuterInstance().notifyNoHoverSelection(); 71 break; 72 } 73 } 74 postNoHoverSelection()75 public void postNoHoverSelection() { 76 removeMessages(MSG_NO_HOVER_SELECTION); 77 sendEmptyMessageDelayed(MSG_NO_HOVER_SELECTION, DELAY_NO_HOVER_SELECTION); 78 } 79 cancelNoHoverSelection()80 public void cancelNoHoverSelection() { 81 removeMessages(MSG_NO_HOVER_SELECTION); 82 } 83 } 84 init(InputMethodService inputMethod, SharedPreferences prefs)85 public static void init(InputMethodService inputMethod, SharedPreferences prefs) { 86 sInstance.initInternal(inputMethod, prefs); 87 } 88 getInstance()89 public static AccessibleInputMethodServiceProxy getInstance() { 90 return sInstance; 91 } 92 AccessibleInputMethodServiceProxy()93 private AccessibleInputMethodServiceProxy() { 94 // Not publicly instantiable. 95 } 96 initInternal(InputMethodService inputMethod, SharedPreferences prefs)97 private void initInternal(InputMethodService inputMethod, SharedPreferences prefs) { 98 mInputMethod = inputMethod; 99 mVibrator = (Vibrator) inputMethod.getSystemService(Context.VIBRATOR_SERVICE); 100 mAudioManager = (AudioManager) inputMethod.getSystemService(Context.AUDIO_SERVICE); 101 mAccessibilityHandler = new AccessibilityHandler(this, inputMethod.getMainLooper()); 102 } 103 104 /** 105 * If touch exploration is enabled, cancels the event sent by 106 * {@link AccessibleInputMethodServiceProxy#onHoverExit(int)} because the 107 * user is currently hovering above a key. 108 */ 109 @Override onHoverEnter(int primaryCode)110 public void onHoverEnter(int primaryCode) { 111 mAccessibilityHandler.cancelNoHoverSelection(); 112 } 113 114 /** 115 * If touch exploration is enabled, sends a delayed event to notify the user 116 * that they are not currently hovering above a key. 117 */ 118 @Override onHoverExit(int primaryCode)119 public void onHoverExit(int primaryCode) { 120 mAccessibilityHandler.postNoHoverSelection(); 121 } 122 123 /** 124 * Handle flick gestures by mapping them to directional pad keys. 125 */ 126 @Override onFlickGesture(int direction)127 public void onFlickGesture(int direction) { 128 final int keyEventCode; 129 130 switch (direction) { 131 case FlickGestureDetector.FLICK_LEFT: 132 sendDownUpKeyEvents(KeyEvent.KEYCODE_DPAD_LEFT); 133 break; 134 case FlickGestureDetector.FLICK_RIGHT: 135 sendDownUpKeyEvents(KeyEvent.KEYCODE_DPAD_RIGHT); 136 break; 137 } 138 } 139 140 /** 141 * Provide haptic feedback and send the specified keyCode to the input 142 * connection as a pair of down/up events. 143 * 144 * @param keyCode 145 */ sendDownUpKeyEvents(int keyCode)146 private void sendDownUpKeyEvents(int keyCode) { 147 mVibrator.vibrate(VIBRATE_KEY_CLICK); 148 mAudioManager.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, FX_VOLUME); 149 mInputMethod.sendDownUpKeyEvents(keyCode); 150 } 151 152 /** 153 * When Accessibility is turned on, notifies the user that they are not 154 * currently hovering above a key. By default this will speak the currently 155 * entered text. 156 */ notifyNoHoverSelection()157 private void notifyNoHoverSelection() { 158 final ExtractedText extracted = mInputMethod.getCurrentInputConnection().getExtractedText( 159 new ExtractedTextRequest(), 0); 160 161 if (extracted == null) 162 return; 163 164 final CharSequence text; 165 166 if (TextUtils.isEmpty(extracted.text)) { 167 text = mInputMethod.getString(R.string.spoken_no_text_entered); 168 } else { 169 text = mInputMethod.getString(R.string.spoken_current_text_is, extracted.text); 170 } 171 172 AccessibilityUtils.getInstance().speak(text); 173 } 174 } 175