• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
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                     mLongClicked = true;
71                 } else if (mSupportsLongpress) {
72                     sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
73                     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
74                     mLongClicked = true;
75                 }
76             }
77         }
78     };
79 
KeyButtonView(Context context, AttributeSet attrs)80     public KeyButtonView(Context context, AttributeSet attrs) {
81         this(context, attrs, 0);
82     }
83 
KeyButtonView(Context context, AttributeSet attrs, int defStyle)84     public KeyButtonView(Context context, AttributeSet attrs, int defStyle) {
85         super(context, attrs);
86 
87         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KeyButtonView,
88                 defStyle, 0);
89 
90         mCode = a.getInteger(R.styleable.KeyButtonView_keyCode, 0);
91 
92         mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true);
93 
94         TypedValue value = new TypedValue();
95         if (a.getValue(R.styleable.KeyButtonView_android_contentDescription, value)) {
96             mContentDescriptionRes = value.resourceId;
97         }
98 
99         a.recycle();
100 
101 
102         setClickable(true);
103         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
104         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
105         setBackground(new KeyButtonRipple(context, this));
106     }
107 
setCode(int code)108     public void setCode(int code) {
109         mCode = code;
110     }
111 
loadAsync(String uri)112     public void loadAsync(String uri) {
113         new AsyncTask<String, Void, Drawable>() {
114             @Override
115             protected Drawable doInBackground(String... params) {
116                 return Icon.createWithContentUri(params[0]).loadDrawable(mContext);
117             }
118 
119             @Override
120             protected void onPostExecute(Drawable drawable) {
121                 setImageDrawable(drawable);
122             }
123         }.execute(uri);
124     }
125 
126     @Override
onConfigurationChanged(Configuration newConfig)127     protected void onConfigurationChanged(Configuration newConfig) {
128         super.onConfigurationChanged(newConfig);
129 
130         if (mContentDescriptionRes != 0) {
131             setContentDescription(mContext.getString(mContentDescriptionRes));
132         }
133     }
134 
135     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)136     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
137         super.onInitializeAccessibilityNodeInfo(info);
138         if (mCode != 0) {
139             info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null));
140             if (mSupportsLongpress || isLongClickable()) {
141                 info.addAction(
142                         new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK, null));
143             }
144         }
145     }
146 
147     @Override
onWindowVisibilityChanged(int visibility)148     protected void onWindowVisibilityChanged(int visibility) {
149         super.onWindowVisibilityChanged(visibility);
150         if (visibility != View.VISIBLE) {
151             jumpDrawablesToCurrentState();
152         }
153     }
154 
155     @Override
performAccessibilityActionInternal(int action, Bundle arguments)156     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
157         if (action == ACTION_CLICK && mCode != 0) {
158             sendEvent(KeyEvent.ACTION_DOWN, 0, SystemClock.uptimeMillis());
159             sendEvent(KeyEvent.ACTION_UP, 0);
160             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
161             playSoundEffect(SoundEffectConstants.CLICK);
162             return true;
163         } else if (action == ACTION_LONG_CLICK && mCode != 0) {
164             sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
165             sendEvent(KeyEvent.ACTION_UP, 0);
166             sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
167             return true;
168         }
169         return super.performAccessibilityActionInternal(action, arguments);
170     }
171 
onTouchEvent(MotionEvent ev)172     public boolean onTouchEvent(MotionEvent ev) {
173         final int action = ev.getAction();
174         int x, y;
175         if (action == MotionEvent.ACTION_DOWN) {
176             mGestureAborted = false;
177         }
178         if (mGestureAborted) {
179             return false;
180         }
181 
182         switch (action) {
183             case MotionEvent.ACTION_DOWN:
184                 mDownTime = SystemClock.uptimeMillis();
185                 mLongClicked = false;
186                 setPressed(true);
187                 if (mCode != 0) {
188                     sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
189                 } else {
190                     // Provide the same haptic feedback that the system offers for virtual keys.
191                     performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
192                 }
193                 removeCallbacks(mCheckLongPress);
194                 postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
195                 break;
196             case MotionEvent.ACTION_MOVE:
197                 x = (int)ev.getX();
198                 y = (int)ev.getY();
199                 setPressed(x >= -mTouchSlop
200                         && x < getWidth() + mTouchSlop
201                         && y >= -mTouchSlop
202                         && y < getHeight() + mTouchSlop);
203                 break;
204             case MotionEvent.ACTION_CANCEL:
205                 setPressed(false);
206                 if (mCode != 0) {
207                     sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
208                 }
209                 removeCallbacks(mCheckLongPress);
210                 break;
211             case MotionEvent.ACTION_UP:
212                 final boolean doIt = isPressed() && !mLongClicked;
213                 setPressed(false);
214                 if (mCode != 0) {
215                     if (doIt) {
216                         sendEvent(KeyEvent.ACTION_UP, 0);
217                         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
218                         playSoundEffect(SoundEffectConstants.CLICK);
219                     } else {
220                         sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
221                     }
222                 } else {
223                     // no key code, just a regular ImageView
224                     if (doIt) {
225                         performClick();
226                     }
227                 }
228                 removeCallbacks(mCheckLongPress);
229                 break;
230         }
231 
232         return true;
233     }
234 
playSoundEffect(int soundConstant)235     public void playSoundEffect(int soundConstant) {
236         mAudioManager.playSoundEffect(soundConstant, ActivityManager.getCurrentUser());
237     };
238 
sendEvent(int action, int flags)239     public void sendEvent(int action, int flags) {
240         sendEvent(action, flags, SystemClock.uptimeMillis());
241     }
242 
sendEvent(int action, int flags, long when)243     void sendEvent(int action, int flags, long when) {
244         final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
245         final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
246                 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
247                 flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
248                 InputDevice.SOURCE_KEYBOARD);
249         InputManager.getInstance().injectInputEvent(ev,
250                 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
251     }
252 
253     @Override
abortCurrentGesture()254     public void abortCurrentGesture() {
255         setPressed(false);
256         mGestureAborted = true;
257     }
258 
259     @Override
setImageResource(@rawableRes int resId)260     public void setImageResource(@DrawableRes int resId) {
261         super.setImageResource(resId);
262     }
263 
264     @Override
setImageDrawable(@ullable Drawable drawable)265     public void setImageDrawable(@Nullable Drawable drawable) {
266         super.setImageDrawable(drawable);
267     }
268 
269     @Override
setLandscape(boolean landscape)270     public void setLandscape(boolean landscape) {
271         //no op
272     }
273 
274     @Override
setCarMode(boolean carMode)275     public void setCarMode(boolean carMode) {
276         // no op
277     }
278 }
279 
280 
281