AccessibilityUtils.java revision 282adf733093b41a31514746825ea05fc90fb3ee
15ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette/*
25ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Copyright (C) 2011 The Android Open Source Project
35ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette *
45ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Licensed under the Apache License, Version 2.0 (the "License");
55ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * you may not use this file except in compliance with the License.
65ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * You may obtain a copy of the License at
75ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette *
85ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette *      http://www.apache.org/licenses/LICENSE-2.0
95ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette *
105ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * Unless required by applicable law or agreed to in writing, software
115ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * distributed under the License is distributed on an "AS IS" BASIS,
125ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * See the License for the specific language governing permissions and
145ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette * limitations under the License.
155ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette */
165ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
175ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverettepackage com.android.inputmethod.accessibility;
185ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
195ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.content.Context;
205ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.inputmethodservice.InputMethodService;
21b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport android.media.AudioManager;
225ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.os.SystemClock;
23c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viveretteimport android.provider.Settings;
245ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.util.Log;
255ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.MotionEvent;
265ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.accessibility.AccessibilityEvent;
275ac4638f999db4fea8a9e24171dbceb640a10858Alan Viveretteimport android.view.accessibility.AccessibilityManager;
28b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport android.view.inputmethod.EditorInfo;
295ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
30b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport com.android.inputmethod.compat.AudioManagerCompatWrapper;
31c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viveretteimport com.android.inputmethod.compat.SettingsSecureCompatUtils;
32be55086fd9218bc03ee0ccac1052d96b40d8a979Tadashi G. Takaokaimport com.android.inputmethod.latin.InputTypeUtils;
33b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viveretteimport com.android.inputmethod.latin.R;
345ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
355ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverettepublic class AccessibilityUtils {
365ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private static final String TAG = AccessibilityUtils.class.getSimpleName();
375ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private static final String CLASS = AccessibilityUtils.class.getClass().getName();
385ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private static final String PACKAGE = AccessibilityUtils.class.getClass().getPackage()
395ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            .getName();
405ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
415ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private static final AccessibilityUtils sInstance = new AccessibilityUtils();
425ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
43b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette    private Context mContext;
445ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private AccessibilityManager mAccessibilityManager;
45b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette    private AudioManagerCompatWrapper mAudioManager;
465ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
475ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    /*
485ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * Setting this constant to {@code false} will disable all keyboard
495ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * accessibility code, regardless of whether Accessibility is turned on in
505ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * the system settings. It should ONLY be used in the event of an emergency.
515ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     */
525ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private static final boolean ENABLE_ACCESSIBILITY = true;
535ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
542ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka    public static void init(InputMethodService inputMethod) {
555ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        if (!ENABLE_ACCESSIBILITY)
565ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            return;
575ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
585ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        // These only need to be initialized if the kill switch is off.
592ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka        sInstance.initInternal(inputMethod);
602ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka        KeyCodeDescriptionMapper.init();
612ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka        AccessibleKeyboardViewProxy.init(inputMethod);
625ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
635ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
645ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    public static AccessibilityUtils getInstance() {
655ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        return sInstance;
665ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
675ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
685ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    private AccessibilityUtils() {
695ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        // This class is not publicly instantiable.
705ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
715ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
722ac5988f84b5c38d313951a3d7faddebf5f25e04Tadashi G. Takaoka    private void initInternal(Context context) {
73b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette        mContext = context;
745ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        mAccessibilityManager = (AccessibilityManager) context
755ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette                .getSystemService(Context.ACCESSIBILITY_SERVICE);
76b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette
77b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette        final AudioManager audioManager = (AudioManager) context
78b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette                .getSystemService(Context.AUDIO_SERVICE);
79b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette        mAudioManager = new AudioManagerCompatWrapper(audioManager);
805ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
815ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
825ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    /**
835ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * Returns {@code true} if touch exploration is enabled. Currently, this
845ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * means that the kill switch is off, the device supports touch exploration,
855ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * and a spoken feedback service is turned on.
865ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     *
875ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * @return {@code true} if touch exploration is enabled.
885ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     */
895ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    public boolean isTouchExplorationEnabled() {
905ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        return ENABLE_ACCESSIBILITY
915ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette                && mAccessibilityManager.isEnabled()
92c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka                && mAccessibilityManager.isTouchExplorationEnabled();
935ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
945ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
955ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    /**
965ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * Returns {@true} if the provided event is a touch exploration (e.g. hover)
975ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * event. This is used to determine whether the event should be processed by
985ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * the touch exploration code within the keyboard.
995ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     *
1005ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * @param event The event to check.
1015ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * @return {@true} is the event is a touch exploration event
1025ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     */
1035ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    public boolean isTouchExplorationEvent(MotionEvent event) {
1045ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        final int action = event.getAction();
1055ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
106c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka        return action == MotionEvent.ACTION_HOVER_ENTER
107c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka                || action == MotionEvent.ACTION_HOVER_EXIT
108c6435f92a80c6664870f9d1a4bb2a1c5153ef2c3Tadashi G. Takaoka                || action == MotionEvent.ACTION_HOVER_MOVE;
1095ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
1105ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1115ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    /**
112c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette     * Returns whether the device should obscure typed password characters.
113c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette     * Typically this means speaking "dot" in place of non-control characters.
1149a81ce92c381007affe6bb2310bf94c9856eaae1alanv     *
115c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette     * @return {@code true} if the device should obscure password characters.
116b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette     */
117e7eac906c0a14b644d457beeb73a407fa1b63673Tadashi G. Takaoka    public boolean shouldObscureInput(EditorInfo editorInfo) {
118e7eac906c0a14b644d457beeb73a407fa1b63673Tadashi G. Takaoka        if (editorInfo == null)
119b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette            return false;
120b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette
121c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette        // The user can optionally force speaking passwords.
122c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette        if (SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD != null) {
123c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette            final boolean speakPassword = Settings.Secure.getInt(mContext.getContentResolver(),
124c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette                    SettingsSecureCompatUtils.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0;
125c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette            if (speakPassword)
126c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette                return false;
127c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette        }
128c960695f38ae0564dff3a6897fd1843c8e74c604Alan Viverette
129b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette        // Always speak if the user is listening through headphones.
130b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette        if (mAudioManager.isWiredHeadsetOn() || mAudioManager.isBluetoothA2dpOn())
131b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette            return false;
132b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette
133b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette        // Don't speak if the IME is connected to a password field.
134be55086fd9218bc03ee0ccac1052d96b40d8a979Tadashi G. Takaoka        return InputTypeUtils.isPasswordInputType(editorInfo.inputType);
135b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette    }
136b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette
137b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette    /**
1385ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * Sends the specified text to the {@link AccessibilityManager} to be
1395ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * spoken.
1405ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     *
1415ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     * @param text the text to speak
1425ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette     */
1435ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    public void speak(CharSequence text) {
1445ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        if (!mAccessibilityManager.isEnabled()) {
1455ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            Log.e(TAG, "Attempted to speak when accessibility was disabled!");
1465ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette            return;
1475ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        }
1485ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1495ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        // The following is a hack to avoid using the heavy-weight TextToSpeech
1505ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        // class. Instead, we're just forcing a fake AccessibilityEvent into
1515ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        // the screen reader to make it speak.
1525ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        final AccessibilityEvent event = AccessibilityEvent
153b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette                .obtain(AccessibilityEvent.TYPE_VIEW_FOCUSED);
1545ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1555ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        event.setPackageName(PACKAGE);
1565ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        event.setClassName(CLASS);
1575ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        event.setEventTime(SystemClock.uptimeMillis());
1585ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        event.setEnabled(true);
1595ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        event.getText().add(text);
1605ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette
1615ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette        mAccessibilityManager.sendAccessibilityEvent(event);
1625ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette    }
163b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette
164b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette    /**
165b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette     * Handles speaking the "connect a headset to hear passwords" notification
166b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette     * when connecting to a password field.
167b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette     *
168e7eac906c0a14b644d457beeb73a407fa1b63673Tadashi G. Takaoka     * @param editorInfo The input connection's editor info attribute.
169b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette     * @param restarting Whether the connection is being restarted.
170b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette     */
171e7eac906c0a14b644d457beeb73a407fa1b63673Tadashi G. Takaoka    public void onStartInputViewInternal(EditorInfo editorInfo, boolean restarting) {
172e7eac906c0a14b644d457beeb73a407fa1b63673Tadashi G. Takaoka        if (shouldObscureInput(editorInfo)) {
173b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette            final CharSequence text = mContext.getText(R.string.spoken_use_headphones);
174b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette            speak(text);
175b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette        }
176b0c8db018d53b103dcb4b699be27a4e1a2c2f92cAlan Viverette    }
177282adf733093b41a31514746825ea05fc90fb3eealanv
178282adf733093b41a31514746825ea05fc90fb3eealanv    /**
179282adf733093b41a31514746825ea05fc90fb3eealanv     * Sends the specified {@link AccessibilityEvent} if accessibility is
180282adf733093b41a31514746825ea05fc90fb3eealanv     * enabled. No operation if accessibility is disabled.
181282adf733093b41a31514746825ea05fc90fb3eealanv     *
182282adf733093b41a31514746825ea05fc90fb3eealanv     * @param event The event to send.
183282adf733093b41a31514746825ea05fc90fb3eealanv     */
184282adf733093b41a31514746825ea05fc90fb3eealanv    public void requestSendAccessibilityEvent(AccessibilityEvent event) {
185282adf733093b41a31514746825ea05fc90fb3eealanv        if (mAccessibilityManager.isEnabled()) {
186282adf733093b41a31514746825ea05fc90fb3eealanv            mAccessibilityManager.sendAccessibilityEvent(event);
187282adf733093b41a31514746825ea05fc90fb3eealanv        }
188282adf733093b41a31514746825ea05fc90fb3eealanv    }
1895ac4638f999db4fea8a9e24171dbceb640a10858Alan Viverette}
190