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