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     public void releaseCurrentKey(final boolean withKeyRegistering) {
    142         mHandler.removeCallbacks(mPendingKeyDown);
    143         mPendingKeyDown = null;
    144         final Key currentKey = mCurrentKey;
    145         if (currentKey == null) {
    146             return;
    147         }
    148         currentKey.onReleased();
    149         invalidateKey(currentKey);
    150         if (withKeyRegistering) {
    151             mListener.onReleaseKey(currentKey);
    152         }
    153         mCurrentKey = null;
    154     }
    155 
    156     @Override
    157     public boolean onDown(final MotionEvent e) {
    158         final Key key = getKey(e);
    159         releaseCurrentKey(false /* withKeyRegistering */);
    160         mCurrentKey = key;
    161         if (key == null) {
    162             return false;
    163         }
    164         // Do not trigger key-down effect right now in case this is actually a fling action.
    165         mPendingKeyDown = new Runnable() {
    166             @Override
    167             public void run() {
    168                 mPendingKeyDown = null;
    169                 key.onPressed();
    170                 invalidateKey(key);
    171                 mListener.onPressKey(key);
    172             }
    173         };
    174         mHandler.postDelayed(mPendingKeyDown, KEY_PRESS_DELAY_TIME);
    175         return false;
    176     }
    177 
    178     @Override
    179     public void onShowPress(final MotionEvent e) {
    180         // User feedback is done at {@link #onDown(MotionEvent)}.
    181     }
    182 
    183     @Override
    184     public boolean onSingleTapUp(final MotionEvent e) {
    185         final Key key = getKey(e);
    186         final Runnable pendingKeyDown = mPendingKeyDown;
    187         final Key currentKey = mCurrentKey;
    188         releaseCurrentKey(false /* withKeyRegistering */);
    189         if (key == null) {
    190             return false;
    191         }
    192         if (key == currentKey && pendingKeyDown != null) {
    193             pendingKeyDown.run();
    194             // Trigger key-release event a little later so that a user can see visual feedback.
    195             mHandler.postDelayed(new Runnable() {
    196                 @Override
    197                 public void run() {
    198                     key.onReleased();
    199                     invalidateKey(key);
    200                     mListener.onReleaseKey(key);
    201                 }
    202             }, KEY_RELEASE_DELAY_TIME);
    203         } else {
    204             key.onReleased();
    205             invalidateKey(key);
    206             mListener.onReleaseKey(key);
    207         }
    208         return true;
    209     }
    210 
    211     @Override
    212     public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX,
    213            final float distanceY) {
    214         releaseCurrentKey(false /* withKeyRegistering */);
    215         return false;
    216     }
    217 
    218     @Override
    219     public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX,
    220             final float velocityY) {
    221         releaseCurrentKey(false /* withKeyRegistering */);
    222         return false;
    223     }
    224 
    225     @Override
    226     public void onLongPress(final MotionEvent e) {
    227         // Long press detection of {@link #mGestureDetector} is disabled and not used.
    228     }
    229 }
    230