Home | History | Annotate | Download | only in accessibility
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * 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, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.android.inputmethod.accessibility;
     18 
     19 import android.content.Context;
     20 import android.inputmethodservice.InputMethodService;
     21 import android.support.v4.view.AccessibilityDelegateCompat;
     22 import android.support.v4.view.ViewCompat;
     23 import android.support.v4.view.accessibility.AccessibilityEventCompat;
     24 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
     25 import android.view.MotionEvent;
     26 import android.view.View;
     27 import android.view.ViewConfiguration;
     28 
     29 import com.android.inputmethod.keyboard.Key;
     30 import com.android.inputmethod.keyboard.Keyboard;
     31 import com.android.inputmethod.keyboard.KeyboardId;
     32 import com.android.inputmethod.keyboard.LatinKeyboardView;
     33 import com.android.inputmethod.keyboard.PointerTracker;
     34 import com.android.inputmethod.latin.R;
     35 
     36 public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
     37     private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy();
     38 
     39     private InputMethodService mInputMethod;
     40     private LatinKeyboardView mView;
     41     private AccessibilityEntityProvider mAccessibilityNodeProvider;
     42 
     43     private Key mLastHoverKey = null;
     44 
     45     /**
     46      * Inset in pixels to look for keys when the user's finger exits the
     47      * keyboard area. See {@link ViewConfiguration#getScaledEdgeSlop()}.
     48      */
     49     private int mEdgeSlop;
     50 
     51     public static void init(InputMethodService inputMethod) {
     52         sInstance.initInternal(inputMethod);
     53     }
     54 
     55     public static AccessibleKeyboardViewProxy getInstance() {
     56         return sInstance;
     57     }
     58 
     59     private AccessibleKeyboardViewProxy() {
     60         // Not publicly instantiable.
     61     }
     62 
     63     private void initInternal(InputMethodService inputMethod) {
     64         mInputMethod = inputMethod;
     65         mEdgeSlop = ViewConfiguration.get(inputMethod).getScaledEdgeSlop();
     66     }
     67 
     68     /**
     69      * Sets the view wrapped by this proxy.
     70      *
     71      * @param view The view to wrap.
     72      */
     73     public void setView(LatinKeyboardView view) {
     74         if (view == null) {
     75             // Ignore null views.
     76             return;
     77         }
     78 
     79         mView = view;
     80 
     81         // Ensure that the view has an accessibility delegate.
     82         ViewCompat.setAccessibilityDelegate(view, this);
     83 
     84         if (mAccessibilityNodeProvider != null) {
     85             mAccessibilityNodeProvider.setView(view);
     86         }
     87     }
     88 
     89     public void setKeyboard(Keyboard keyboard) {
     90         if (mAccessibilityNodeProvider != null) {
     91             mAccessibilityNodeProvider.setKeyboard(keyboard);
     92         }
     93     }
     94 
     95     /**
     96      * Proxy method for View.getAccessibilityNodeProvider(). This method is
     97      * called in SDK version 15 and higher to obtain the virtual node hierarchy
     98      * provider.
     99      *
    100      * @return The accessibility node provider for the current keyboard.
    101      */
    102     @Override
    103     public AccessibilityEntityProvider getAccessibilityNodeProvider(View host) {
    104         return getAccessibilityNodeProvider();
    105     }
    106 
    107     /**
    108      * Receives hover events when accessibility is turned on in SDK versions ICS
    109      * and higher.
    110      *
    111      * @param event The hover event.
    112      * @return {@code true} if the event is handled
    113      */
    114     public boolean dispatchHoverEvent(MotionEvent event, PointerTracker tracker) {
    115         final int x = (int) event.getX();
    116         final int y = (int) event.getY();
    117         final Key key = tracker.getKeyOn(x, y);
    118         final Key previousKey = mLastHoverKey;
    119 
    120         mLastHoverKey = key;
    121 
    122         switch (event.getAction()) {
    123         case MotionEvent.ACTION_HOVER_EXIT:
    124             // Make sure we're not getting an EXIT event because the user slid
    125             // off the keyboard area, then force a key press.
    126             if (pointInView(x, y) && (key != null)) {
    127                 getAccessibilityNodeProvider().simulateKeyPress(key);
    128             }
    129             //$FALL-THROUGH$
    130         case MotionEvent.ACTION_HOVER_ENTER:
    131             return onHoverKey(key, event);
    132         case MotionEvent.ACTION_HOVER_MOVE:
    133             if (key != previousKey) {
    134                 return onTransitionKey(key, previousKey, event);
    135             } else {
    136                 return onHoverKey(key, event);
    137             }
    138         }
    139 
    140         return false;
    141     }
    142 
    143     /**
    144      * @return A lazily-instantiated node provider for this view proxy.
    145      */
    146     private AccessibilityEntityProvider getAccessibilityNodeProvider() {
    147         // Instantiate the provide only when requested. Since the system
    148         // will call this method multiple times it is a good practice to
    149         // cache the provider instance.
    150         if (mAccessibilityNodeProvider == null) {
    151             mAccessibilityNodeProvider = new AccessibilityEntityProvider(mView, mInputMethod);
    152         }
    153         return mAccessibilityNodeProvider;
    154     }
    155 
    156     /**
    157      * Utility method to determine whether the given point, in local
    158      * coordinates, is inside the view, where the area of the view is contracted
    159      * by the edge slop factor.
    160      *
    161      * @param localX The local x-coordinate.
    162      * @param localY The local y-coordinate.
    163      */
    164     private boolean pointInView(int localX, int localY) {
    165         return (localX >= mEdgeSlop) && (localY >= mEdgeSlop)
    166                 && (localX < (mView.getWidth() - mEdgeSlop))
    167                 && (localY < (mView.getHeight() - mEdgeSlop));
    168     }
    169 
    170     /**
    171      * Simulates a transition between two {@link Key}s by sending a HOVER_EXIT
    172      * on the previous key, a HOVER_ENTER on the current key, and a HOVER_MOVE
    173      * on the current key.
    174      *
    175      * @param currentKey The currently hovered key.
    176      * @param previousKey The previously hovered key.
    177      * @param event The event that triggered the transition.
    178      * @return {@code true} if the event was handled.
    179      */
    180     private boolean onTransitionKey(Key currentKey, Key previousKey, MotionEvent event) {
    181         final int savedAction = event.getAction();
    182 
    183         event.setAction(MotionEvent.ACTION_HOVER_EXIT);
    184         onHoverKey(previousKey, event);
    185 
    186         event.setAction(MotionEvent.ACTION_HOVER_ENTER);
    187         onHoverKey(currentKey, event);
    188 
    189         event.setAction(MotionEvent.ACTION_HOVER_MOVE);
    190         final boolean handled = onHoverKey(currentKey, event);
    191 
    192         event.setAction(savedAction);
    193 
    194         return handled;
    195     }
    196 
    197     /**
    198      * Handles a hover event on a key. If {@link Key} extended View, this would
    199      * be analogous to calling View.onHoverEvent(MotionEvent).
    200      *
    201      * @param key The currently hovered key.
    202      * @param event The hover event.
    203      * @return {@code true} if the event was handled.
    204      */
    205     private boolean onHoverKey(Key key, MotionEvent event) {
    206         // Null keys can't receive events.
    207         if (key == null) {
    208             return false;
    209         }
    210 
    211         final AccessibilityEntityProvider provider = getAccessibilityNodeProvider();
    212 
    213         switch (event.getAction()) {
    214         case MotionEvent.ACTION_HOVER_ENTER:
    215             provider.sendAccessibilityEventForKey(
    216                     key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
    217             provider.performActionForKey(
    218                     key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
    219             break;
    220         case MotionEvent.ACTION_HOVER_EXIT:
    221             provider.sendAccessibilityEventForKey(
    222                     key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
    223             break;
    224         }
    225 
    226         return true;
    227     }
    228 
    229     /**
    230      * Notifies the user of changes in the keyboard shift state.
    231      */
    232     public void notifyShiftState() {
    233         final Keyboard keyboard = mView.getKeyboard();
    234         final KeyboardId keyboardId = keyboard.mId;
    235         final int elementId = keyboardId.mElementId;
    236         final Context context = mView.getContext();
    237         final CharSequence text;
    238 
    239         switch (elementId) {
    240         case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
    241         case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
    242             text = context.getText(R.string.spoken_description_shiftmode_locked);
    243             break;
    244         case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
    245         case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
    246         case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
    247             text = context.getText(R.string.spoken_description_shiftmode_on);
    248             break;
    249         default:
    250             text = context.getText(R.string.spoken_description_shiftmode_off);
    251         }
    252 
    253         AccessibilityUtils.getInstance().speak(text);
    254     }
    255 
    256     /**
    257      * Notifies the user of changes in the keyboard symbols state.
    258      */
    259     public void notifySymbolsState() {
    260         final Keyboard keyboard = mView.getKeyboard();
    261         final Context context = mView.getContext();
    262         final KeyboardId keyboardId = keyboard.mId;
    263         final int elementId = keyboardId.mElementId;
    264         final int resId;
    265 
    266         switch (elementId) {
    267         case KeyboardId.ELEMENT_ALPHABET:
    268         case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
    269         case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
    270         case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
    271         case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
    272             resId = R.string.spoken_description_mode_alpha;
    273             break;
    274         case KeyboardId.ELEMENT_SYMBOLS:
    275         case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
    276             resId = R.string.spoken_description_mode_symbol;
    277             break;
    278         case KeyboardId.ELEMENT_PHONE:
    279             resId = R.string.spoken_description_mode_phone;
    280             break;
    281         case KeyboardId.ELEMENT_PHONE_SYMBOLS:
    282             resId = R.string.spoken_description_mode_phone_shift;
    283             break;
    284         default:
    285             resId = -1;
    286         }
    287 
    288         if (resId < 0) {
    289             return;
    290         }
    291 
    292         final String text = context.getString(resId);
    293         AccessibilityUtils.getInstance().speak(text);
    294     }
    295 }
    296