Home | History | Annotate | Download | only in policy
      1 /*
      2  * Copyright (C) 2008 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.systemui.statusbar.policy;
     18 
     19 import android.app.ActivityManager;
     20 import android.content.Context;
     21 import android.content.res.Configuration;
     22 import android.content.res.TypedArray;
     23 import android.graphics.drawable.Drawable;
     24 import android.graphics.drawable.Icon;
     25 import android.hardware.input.InputManager;
     26 import android.media.AudioManager;
     27 import android.os.AsyncTask;
     28 import android.os.Bundle;
     29 import android.os.SystemClock;
     30 import android.util.AttributeSet;
     31 import android.util.TypedValue;
     32 import android.view.HapticFeedbackConstants;
     33 import android.view.InputDevice;
     34 import android.view.KeyCharacterMap;
     35 import android.view.KeyEvent;
     36 import android.view.MotionEvent;
     37 import android.view.SoundEffectConstants;
     38 import android.view.View;
     39 import android.view.ViewConfiguration;
     40 import android.view.accessibility.AccessibilityEvent;
     41 import android.view.accessibility.AccessibilityNodeInfo;
     42 import android.widget.ImageView;
     43 
     44 import com.android.systemui.R;
     45 
     46 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
     47 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
     48 
     49 public class KeyButtonView extends ImageView {
     50 
     51     private int mContentDescriptionRes;
     52     private long mDownTime;
     53     private int mCode;
     54     private int mTouchSlop;
     55     private boolean mSupportsLongpress = true;
     56     private AudioManager mAudioManager;
     57     private boolean mGestureAborted;
     58     private boolean mLongClicked;
     59 
     60     private final Runnable mCheckLongPress = new Runnable() {
     61         public void run() {
     62             if (isPressed()) {
     63                 // Log.d("KeyButtonView", "longpressed: " + this);
     64                 if (isLongClickable()) {
     65                     // Just an old-fashioned ImageView
     66                     performLongClick();
     67                     mLongClicked = true;
     68                 } else if (mSupportsLongpress) {
     69                     sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
     70                     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
     71                     mLongClicked = true;
     72                 }
     73             }
     74         }
     75     };
     76 
     77     public KeyButtonView(Context context, AttributeSet attrs) {
     78         this(context, attrs, 0);
     79     }
     80 
     81     public KeyButtonView(Context context, AttributeSet attrs, int defStyle) {
     82         super(context, attrs);
     83 
     84         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KeyButtonView,
     85                 defStyle, 0);
     86 
     87         mCode = a.getInteger(R.styleable.KeyButtonView_keyCode, 0);
     88 
     89         mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true);
     90 
     91         TypedValue value = new TypedValue();
     92         if (a.getValue(R.styleable.KeyButtonView_android_contentDescription, value)) {
     93             mContentDescriptionRes = value.resourceId;
     94         }
     95 
     96         a.recycle();
     97 
     98 
     99         setClickable(true);
    100         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    101         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    102         setBackground(new KeyButtonRipple(context, this));
    103     }
    104 
    105     public void setCode(int code) {
    106         mCode = code;
    107     }
    108 
    109     public void loadAsync(String uri) {
    110         new AsyncTask<String, Void, Drawable>() {
    111             @Override
    112             protected Drawable doInBackground(String... params) {
    113                 return Icon.createWithContentUri(params[0]).loadDrawable(mContext);
    114             }
    115 
    116             @Override
    117             protected void onPostExecute(Drawable drawable) {
    118                 setImageDrawable(drawable);
    119             }
    120         }.execute(uri);
    121     }
    122 
    123     @Override
    124     protected void onConfigurationChanged(Configuration newConfig) {
    125         super.onConfigurationChanged(newConfig);
    126 
    127         if (mContentDescriptionRes != 0) {
    128             setContentDescription(mContext.getString(mContentDescriptionRes));
    129         }
    130     }
    131 
    132     @Override
    133     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    134         super.onInitializeAccessibilityNodeInfo(info);
    135         if (mCode != 0) {
    136             info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null));
    137             if (mSupportsLongpress || isLongClickable()) {
    138                 info.addAction(
    139                         new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK, null));
    140             }
    141         }
    142     }
    143 
    144     @Override
    145     protected void onWindowVisibilityChanged(int visibility) {
    146         super.onWindowVisibilityChanged(visibility);
    147         if (visibility != View.VISIBLE) {
    148             jumpDrawablesToCurrentState();
    149         }
    150     }
    151 
    152     @Override
    153     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
    154         if (action == ACTION_CLICK && mCode != 0) {
    155             sendEvent(KeyEvent.ACTION_DOWN, 0, SystemClock.uptimeMillis());
    156             sendEvent(KeyEvent.ACTION_UP, 0);
    157             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    158             playSoundEffect(SoundEffectConstants.CLICK);
    159             return true;
    160         } else if (action == ACTION_LONG_CLICK && mCode != 0) {
    161             sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
    162             sendEvent(KeyEvent.ACTION_UP, 0);
    163             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
    164             return true;
    165         }
    166         return super.performAccessibilityActionInternal(action, arguments);
    167     }
    168 
    169     public boolean onTouchEvent(MotionEvent ev) {
    170         final int action = ev.getAction();
    171         int x, y;
    172         if (action == MotionEvent.ACTION_DOWN) {
    173             mGestureAborted = false;
    174         }
    175         if (mGestureAborted) {
    176             return false;
    177         }
    178 
    179         switch (action) {
    180             case MotionEvent.ACTION_DOWN:
    181                 mDownTime = SystemClock.uptimeMillis();
    182                 mLongClicked = false;
    183                 setPressed(true);
    184                 if (mCode != 0) {
    185                     sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
    186                 } else {
    187                     // Provide the same haptic feedback that the system offers for virtual keys.
    188                     performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
    189                 }
    190                 removeCallbacks(mCheckLongPress);
    191                 postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
    192                 break;
    193             case MotionEvent.ACTION_MOVE:
    194                 x = (int)ev.getX();
    195                 y = (int)ev.getY();
    196                 setPressed(x >= -mTouchSlop
    197                         && x < getWidth() + mTouchSlop
    198                         && y >= -mTouchSlop
    199                         && y < getHeight() + mTouchSlop);
    200                 break;
    201             case MotionEvent.ACTION_CANCEL:
    202                 setPressed(false);
    203                 if (mCode != 0) {
    204                     sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
    205                 }
    206                 removeCallbacks(mCheckLongPress);
    207                 break;
    208             case MotionEvent.ACTION_UP:
    209                 final boolean doIt = isPressed() && !mLongClicked;
    210                 setPressed(false);
    211                 if (mCode != 0) {
    212                     if (doIt) {
    213                         sendEvent(KeyEvent.ACTION_UP, 0);
    214                         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    215                         playSoundEffect(SoundEffectConstants.CLICK);
    216                     } else {
    217                         sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
    218                     }
    219                 } else {
    220                     // no key code, just a regular ImageView
    221                     if (doIt) {
    222                         performClick();
    223                     }
    224                 }
    225                 removeCallbacks(mCheckLongPress);
    226                 break;
    227         }
    228 
    229         return true;
    230     }
    231 
    232     public void playSoundEffect(int soundConstant) {
    233         mAudioManager.playSoundEffect(soundConstant, ActivityManager.getCurrentUser());
    234     };
    235 
    236     public void sendEvent(int action, int flags) {
    237         sendEvent(action, flags, SystemClock.uptimeMillis());
    238     }
    239 
    240     void sendEvent(int action, int flags, long when) {
    241         final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
    242         final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
    243                 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
    244                 flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
    245                 InputDevice.SOURCE_KEYBOARD);
    246         InputManager.getInstance().injectInputEvent(ev,
    247                 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
    248     }
    249 
    250     public void abortCurrentGesture() {
    251         setPressed(false);
    252         mGestureAborted = true;
    253     }
    254 }
    255 
    256 
    257