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