Home | History | Annotate | Download | only in emoji
      1 /*
      2  * Copyright (C) 2013 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.keyboard.emoji;
     18 
     19 import android.content.Context;
     20 import android.os.Handler;
     21 import android.util.AttributeSet;
     22 import android.view.GestureDetector;
     23 import android.view.MotionEvent;
     24 import android.view.accessibility.AccessibilityEvent;
     25 
     26 import com.android.inputmethod.accessibility.AccessibilityUtils;
     27 import com.android.inputmethod.accessibility.KeyboardAccessibilityDelegate;
     28 import com.android.inputmethod.keyboard.Key;
     29 import com.android.inputmethod.keyboard.KeyDetector;
     30 import com.android.inputmethod.keyboard.Keyboard;
     31 import com.android.inputmethod.keyboard.KeyboardView;
     32 import com.android.inputmethod.latin.R;
     33 
     34 /**
     35  * This is an extended {@link KeyboardView} class that hosts an emoji page keyboard.
     36  * Multi-touch unsupported. No gesture support.
     37  */
     38 // TODO: Implement key popup preview.
     39 final class EmojiPageKeyboardView extends KeyboardView implements
     40         GestureDetector.OnGestureListener {
     41     private static final long KEY_PRESS_DELAY_TIME = 250;  // msec
     42     private static final long KEY_RELEASE_DELAY_TIME = 30;  // msec
     43 
     44     public interface OnKeyEventListener {
     45         public void onPressKey(Key key);
     46         public void onReleaseKey(Key key);
     47     }
     48 
     49     private static final OnKeyEventListener EMPTY_LISTENER = new OnKeyEventListener() {
     50         @Override
     51         public void onPressKey(final Key key) {}
     52         @Override
     53         public void onReleaseKey(final Key key) {}
     54     };
     55 
     56     private OnKeyEventListener mListener = EMPTY_LISTENER;
     57     private final KeyDetector mKeyDetector = new KeyDetector();
     58     private final GestureDetector mGestureDetector;
     59     private KeyboardAccessibilityDelegate<EmojiPageKeyboardView> mAccessibilityDelegate;
     60 
     61     public EmojiPageKeyboardView(final Context context, final AttributeSet attrs) {
     62         this(context, attrs, R.attr.keyboardViewStyle);
     63     }
     64 
     65     public EmojiPageKeyboardView(final Context context, final AttributeSet attrs,
     66             final int defStyle) {
     67         super(context, attrs, defStyle);
     68         mGestureDetector = new GestureDetector(context, this);
     69         mGestureDetector.setIsLongpressEnabled(false /* isLongpressEnabled */);
     70         mHandler = new Handler();
     71     }
     72 
     73     public void setOnKeyEventListener(final OnKeyEventListener listener) {
     74         mListener = listener;
     75     }
     76 
     77     /**
     78      * {@inheritDoc}
     79      */
     80     @Override
     81     public void setKeyboard(final Keyboard keyboard) {
     82         super.setKeyboard(keyboard);
     83         mKeyDetector.setKeyboard(keyboard, 0 /* correctionX */, 0 /* correctionY */);
     84         if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
     85             if (mAccessibilityDelegate == null) {
     86                 mAccessibilityDelegate = new KeyboardAccessibilityDelegate<>(this, mKeyDetector);
     87             }
     88             mAccessibilityDelegate.setKeyboard(keyboard);
     89         } else {
     90             mAccessibilityDelegate = null;
     91         }
     92     }
     93 
     94     @Override
     95     public boolean dispatchPopulateAccessibilityEvent(final AccessibilityEvent event) {
     96         // Don't populate accessibility event with all Emoji keys.
     97         return true;
     98     }
     99 
    100     /**
    101      * {@inheritDoc}
    102      */
    103     @Override
    104     public boolean onHoverEvent(final MotionEvent event) {
    105         final KeyboardAccessibilityDelegate<EmojiPageKeyboardView> accessibilityDelegate =
    106                 mAccessibilityDelegate;
    107         if (accessibilityDelegate != null
    108                 && AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
    109             return accessibilityDelegate.onHoverEvent(event);
    110         }
    111         return super.onHoverEvent(event);
    112     }
    113 
    114     /**
    115      * {@inheritDoc}
    116      */
    117     @Override
    118     public boolean onTouchEvent(final MotionEvent e) {
    119         if (mGestureDetector.onTouchEvent(e)) {
    120             return true;
    121         }
    122         final Key key = getKey(e);
    123         if (key != null && key != mCurrentKey) {
    124             releaseCurrentKey(false /* withKeyRegistering */);
    125         }
    126         return true;
    127     }
    128 
    129     // {@link GestureEnabler#OnGestureListener} methods.
    130     private Key mCurrentKey;
    131     private Runnable mPendingKeyDown;
    132     private final Handler mHandler;
    133 
    134     private Key getKey(final MotionEvent e) {
    135         final int index = e.getActionIndex();
    136         final int x = (int)e.getX(index);
    137         final int y = (int)e.getY(index);
    138         return mKeyDetector.detectHitKey(x, y);
    139     }
    140 
    141     void callListenerOnReleaseKey(final Key releasedKey, final boolean withKeyRegistering) {
    142         releasedKey.onReleased();
    143         invalidateKey(releasedKey);
    144         if (withKeyRegistering) {
    145             mListener.onReleaseKey(releasedKey);
    146         }
    147     }
    148 
    149     void callListenerOnPressKey(final Key pressedKey) {
    150         mPendingKeyDown = null;
    151         pressedKey.onPressed();
    152         invalidateKey(pressedKey);
    153         mListener.onPressKey(pressedKey);
    154     }
    155 
    156     public void releaseCurrentKey(final boolean withKeyRegistering) {
    157         mHandler.removeCallbacks(mPendingKeyDown);
    158         mPendingKeyDown = null;
    159         final Key currentKey = mCurrentKey;
    160         if (currentKey == null) {
    161             return;
    162         }
    163         callListenerOnReleaseKey(currentKey, withKeyRegistering);
    164         mCurrentKey = null;
    165     }
    166 
    167     @Override
    168     public boolean onDown(final MotionEvent e) {
    169         final Key key = getKey(e);
    170         releaseCurrentKey(false /* withKeyRegistering */);
    171         mCurrentKey = key;
    172         if (key == null) {
    173             return false;
    174         }
    175         // Do not trigger key-down effect right now in case this is actually a fling action.
    176         mPendingKeyDown = new Runnable() {
    177             @Override
    178             public void run() {
    179                 callListenerOnPressKey(key);
    180             }
    181         };
    182         mHandler.postDelayed(mPendingKeyDown, KEY_PRESS_DELAY_TIME);
    183         return false;
    184     }
    185 
    186     @Override
    187     public void onShowPress(final MotionEvent e) {
    188         // User feedback is done at {@link #onDown(MotionEvent)}.
    189     }
    190 
    191     @Override
    192     public boolean onSingleTapUp(final MotionEvent e) {
    193         final Key key = getKey(e);
    194         final Runnable pendingKeyDown = mPendingKeyDown;
    195         final Key currentKey = mCurrentKey;
    196         releaseCurrentKey(false /* withKeyRegistering */);
    197         if (key == null) {
    198             return false;
    199         }
    200         if (key == currentKey && pendingKeyDown != null) {
    201             pendingKeyDown.run();
    202             // Trigger key-release event a little later so that a user can see visual feedback.
    203             mHandler.postDelayed(new Runnable() {
    204                 @Override
    205                 public void run() {
    206                     callListenerOnReleaseKey(key, true /* withRegistering */);
    207                 }
    208             }, KEY_RELEASE_DELAY_TIME);
    209         } else {
    210             callListenerOnReleaseKey(key, true /* withRegistering */);
    211         }
    212         return true;
    213     }
    214 
    215     @Override
    216     public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX,
    217            final float distanceY) {
    218         releaseCurrentKey(false /* withKeyRegistering */);
    219         return false;
    220     }
    221 
    222     @Override
    223     public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX,
    224             final float velocityY) {
    225         releaseCurrentKey(false /* withKeyRegistering */);
    226         return false;
    227     }
    228 
    229     @Override
    230     public void onLongPress(final MotionEvent e) {
    231         // Long press detection of {@link #mGestureDetector} is disabled and not used.
    232     }
    233 }
    234