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.animation.Animator;
20 import android.animation.ObjectAnimator;
21 import android.app.ActivityManager;
22 import android.content.Context;
23 import android.content.res.TypedArray;
24 import android.hardware.input.InputManager;
25 import android.media.AudioManager;
26 import android.os.Bundle;
27 import android.os.SystemClock;
28 import android.util.AttributeSet;
29 import android.util.Log;
30 import android.view.HapticFeedbackConstants;
31 import android.view.InputDevice;
32 import android.view.KeyCharacterMap;
33 import android.view.KeyEvent;
34 import android.view.MotionEvent;
35 import android.view.SoundEffectConstants;
36 import android.view.View;
37 import android.view.ViewConfiguration;
38 import android.view.accessibility.AccessibilityEvent;
39 import android.view.accessibility.AccessibilityNodeInfo;
40 import android.widget.ImageView;
41 
42 import com.android.systemui.R;
43 
44 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
45 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
46 
47 public class KeyButtonView extends ImageView {
48     private static final String TAG = "StatusBar.KeyButtonView";
49     private static final boolean DEBUG = false;
50 
51     // TODO: Get rid of this
52     public static final float DEFAULT_QUIESCENT_ALPHA = 1f;
53 
54     private long mDownTime;
55     private int mCode;
56     private int mTouchSlop;
57     private float mDrawingAlpha = 1f;
58     private float mQuiescentAlpha = DEFAULT_QUIESCENT_ALPHA;
59     private boolean mSupportsLongpress = true;
60     private AudioManager mAudioManager;
61     private Animator mAnimateToQuiescent = new ObjectAnimator();
62 
63     private final Runnable mCheckLongPress = new Runnable() {
64         public void run() {
65             if (isPressed()) {
66                 // Log.d("KeyButtonView", "longpressed: " + this);
67                 if (isLongClickable()) {
68                     // Just an old-fashioned ImageView
69                     performLongClick();
70                 } else {
71                     sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
72                     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
73                 }
74             }
75         }
76     };
77 
KeyButtonView(Context context, AttributeSet attrs)78     public KeyButtonView(Context context, AttributeSet attrs) {
79         this(context, attrs, 0);
80     }
81 
KeyButtonView(Context context, AttributeSet attrs, int defStyle)82     public KeyButtonView(Context context, AttributeSet attrs, int defStyle) {
83         super(context, attrs);
84 
85         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KeyButtonView,
86                 defStyle, 0);
87 
88         mCode = a.getInteger(R.styleable.KeyButtonView_keyCode, 0);
89 
90         mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true);
91 
92 
93         setDrawingAlpha(mQuiescentAlpha);
94 
95         a.recycle();
96 
97         setClickable(true);
98         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
99         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
100         setBackground(new KeyButtonRipple(context, this));
101     }
102 
103     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)104     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
105         super.onInitializeAccessibilityNodeInfo(info);
106         if (mCode != 0) {
107             info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null));
108             if (mSupportsLongpress) {
109                 info.addAction(
110                         new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK, null));
111             }
112         }
113     }
114 
115     @Override
onWindowVisibilityChanged(int visibility)116     protected void onWindowVisibilityChanged(int visibility) {
117         super.onWindowVisibilityChanged(visibility);
118         if (visibility != View.VISIBLE) {
119             jumpDrawablesToCurrentState();
120         }
121     }
122 
123     @Override
performAccessibilityAction(int action, Bundle arguments)124     public boolean performAccessibilityAction(int action, Bundle arguments) {
125         if (action == ACTION_CLICK && mCode != 0) {
126             sendEvent(KeyEvent.ACTION_DOWN, 0, SystemClock.uptimeMillis());
127             sendEvent(KeyEvent.ACTION_UP, 0);
128             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
129             playSoundEffect(SoundEffectConstants.CLICK);
130             return true;
131         } else if (action == ACTION_LONG_CLICK && mCode != 0 && mSupportsLongpress) {
132             sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
133             sendEvent(KeyEvent.ACTION_UP, 0);
134             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
135             return true;
136         }
137         return super.performAccessibilityAction(action, arguments);
138     }
139 
setQuiescentAlpha(float alpha, boolean animate)140     public void setQuiescentAlpha(float alpha, boolean animate) {
141         mAnimateToQuiescent.cancel();
142         alpha = Math.min(Math.max(alpha, 0), 1);
143         if (alpha == mQuiescentAlpha && alpha == mDrawingAlpha) return;
144         mQuiescentAlpha = alpha;
145         if (DEBUG) Log.d(TAG, "New quiescent alpha = " + mQuiescentAlpha);
146         if (animate) {
147             mAnimateToQuiescent = animateToQuiescent();
148             mAnimateToQuiescent.start();
149         } else {
150             setDrawingAlpha(mQuiescentAlpha);
151         }
152     }
153 
animateToQuiescent()154     private ObjectAnimator animateToQuiescent() {
155         return ObjectAnimator.ofFloat(this, "drawingAlpha", mQuiescentAlpha);
156     }
157 
getQuiescentAlpha()158     public float getQuiescentAlpha() {
159         return mQuiescentAlpha;
160     }
161 
getDrawingAlpha()162     public float getDrawingAlpha() {
163         return mDrawingAlpha;
164     }
165 
setDrawingAlpha(float x)166     public void setDrawingAlpha(float x) {
167         setImageAlpha((int) (x * 255));
168         mDrawingAlpha = x;
169     }
170 
onTouchEvent(MotionEvent ev)171     public boolean onTouchEvent(MotionEvent ev) {
172         final int action = ev.getAction();
173         int x, y;
174 
175         switch (action) {
176             case MotionEvent.ACTION_DOWN:
177                 //Log.d("KeyButtonView", "press");
178                 mDownTime = SystemClock.uptimeMillis();
179                 setPressed(true);
180                 if (mCode != 0) {
181                     sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
182                 } else {
183                     // Provide the same haptic feedback that the system offers for virtual keys.
184                     performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
185                 }
186                 if (mSupportsLongpress) {
187                     removeCallbacks(mCheckLongPress);
188                     postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
189                 }
190                 break;
191             case MotionEvent.ACTION_MOVE:
192                 x = (int)ev.getX();
193                 y = (int)ev.getY();
194                 setPressed(x >= -mTouchSlop
195                         && x < getWidth() + mTouchSlop
196                         && y >= -mTouchSlop
197                         && y < getHeight() + mTouchSlop);
198                 break;
199             case MotionEvent.ACTION_CANCEL:
200                 setPressed(false);
201                 if (mCode != 0) {
202                     sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
203                 }
204                 if (mSupportsLongpress) {
205                     removeCallbacks(mCheckLongPress);
206                 }
207                 break;
208             case MotionEvent.ACTION_UP:
209                 final boolean doIt = isPressed();
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                 if (mSupportsLongpress) {
226                     removeCallbacks(mCheckLongPress);
227                 }
228                 break;
229         }
230 
231         return true;
232     }
233 
playSoundEffect(int soundConstant)234     public void playSoundEffect(int soundConstant) {
235         mAudioManager.playSoundEffect(soundConstant, ActivityManager.getCurrentUser());
236     };
237 
sendEvent(int action, int flags)238     public void sendEvent(int action, int flags) {
239         sendEvent(action, flags, SystemClock.uptimeMillis());
240     }
241 
sendEvent(int action, int flags, long when)242     void sendEvent(int action, int flags, long when) {
243         final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
244         final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
245                 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
246                 flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
247                 InputDevice.SOURCE_KEYBOARD);
248         InputManager.getInstance().injectInputEvent(ev,
249                 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
250     }
251 }
252 
253 
254