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