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 
KeyButtonView(Context context, AttributeSet attrs)77     public KeyButtonView(Context context, AttributeSet attrs) {
78         this(context, attrs, 0);
79     }
80 
KeyButtonView(Context context, AttributeSet attrs, int defStyle)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 
setCode(int code)105     public void setCode(int code) {
106         mCode = code;
107     }
108 
loadAsync(String uri)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
onConfigurationChanged(Configuration newConfig)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
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)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
onWindowVisibilityChanged(int visibility)145     protected void onWindowVisibilityChanged(int visibility) {
146         super.onWindowVisibilityChanged(visibility);
147         if (visibility != View.VISIBLE) {
148             jumpDrawablesToCurrentState();
149         }
150     }
151 
152     @Override
performAccessibilityActionInternal(int action, Bundle arguments)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 
onTouchEvent(MotionEvent ev)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 
playSoundEffect(int soundConstant)232     public void playSoundEffect(int soundConstant) {
233         mAudioManager.playSoundEffect(soundConstant, ActivityManager.getCurrentUser());
234     };
235 
sendEvent(int action, int flags)236     public void sendEvent(int action, int flags) {
237         sendEvent(action, flags, SystemClock.uptimeMillis());
238     }
239 
sendEvent(int action, int flags, long when)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 
abortCurrentGesture()250     public void abortCurrentGesture() {
251         setPressed(false);
252         mGestureAborted = true;
253     }
254 }
255 
256 
257