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