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.hardware.input.InputManager; 24 import android.media.AudioManager; 25 import android.os.Bundle; 26 import android.os.SystemClock; 27 import android.util.AttributeSet; 28 import android.util.TypedValue; 29 import android.view.HapticFeedbackConstants; 30 import android.view.InputDevice; 31 import android.view.KeyCharacterMap; 32 import android.view.KeyEvent; 33 import android.view.MotionEvent; 34 import android.view.SoundEffectConstants; 35 import android.view.View; 36 import android.view.ViewConfiguration; 37 import android.view.accessibility.AccessibilityEvent; 38 import android.view.accessibility.AccessibilityNodeInfo; 39 import android.widget.ImageView; 40 41 import com.android.systemui.R; 42 43 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; 44 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK; 45 46 public class KeyButtonView extends ImageView { 47 48 private int mContentDescriptionRes; 49 private long mDownTime; 50 private int mCode; 51 private int mTouchSlop; 52 private boolean mSupportsLongpress = true; 53 private AudioManager mAudioManager; 54 private boolean mGestureAborted; 55 56 private final Runnable mCheckLongPress = new Runnable() { 57 public void run() { 58 if (isPressed()) { 59 // Log.d("KeyButtonView", "longpressed: " + this); 60 if (isLongClickable()) { 61 // Just an old-fashioned ImageView 62 performLongClick(); 63 } else if (mSupportsLongpress) { 64 sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS); 65 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); 66 } 67 } 68 } 69 }; 70 KeyButtonView(Context context, AttributeSet attrs)71 public KeyButtonView(Context context, AttributeSet attrs) { 72 this(context, attrs, 0); 73 } 74 KeyButtonView(Context context, AttributeSet attrs, int defStyle)75 public KeyButtonView(Context context, AttributeSet attrs, int defStyle) { 76 super(context, attrs); 77 78 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KeyButtonView, 79 defStyle, 0); 80 81 mCode = a.getInteger(R.styleable.KeyButtonView_keyCode, 0); 82 83 mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true); 84 85 TypedValue value = new TypedValue(); 86 if (a.getValue(R.styleable.KeyButtonView_android_contentDescription, value)) { 87 mContentDescriptionRes = value.resourceId; 88 } 89 90 a.recycle(); 91 92 93 setClickable(true); 94 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 95 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 96 setBackground(new KeyButtonRipple(context, this)); 97 } 98 99 @Override onConfigurationChanged(Configuration newConfig)100 protected void onConfigurationChanged(Configuration newConfig) { 101 super.onConfigurationChanged(newConfig); 102 103 if (mContentDescriptionRes != 0) { 104 setContentDescription(mContext.getString(mContentDescriptionRes)); 105 } 106 } 107 108 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)109 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 110 super.onInitializeAccessibilityNodeInfo(info); 111 if (mCode != 0) { 112 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null)); 113 if (mSupportsLongpress || isLongClickable()) { 114 info.addAction( 115 new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK, null)); 116 } 117 } 118 } 119 120 @Override onWindowVisibilityChanged(int visibility)121 protected void onWindowVisibilityChanged(int visibility) { 122 super.onWindowVisibilityChanged(visibility); 123 if (visibility != View.VISIBLE) { 124 jumpDrawablesToCurrentState(); 125 } 126 } 127 128 @Override performAccessibilityActionInternal(int action, Bundle arguments)129 public boolean performAccessibilityActionInternal(int action, Bundle arguments) { 130 if (action == ACTION_CLICK && mCode != 0) { 131 sendEvent(KeyEvent.ACTION_DOWN, 0, SystemClock.uptimeMillis()); 132 sendEvent(KeyEvent.ACTION_UP, 0); 133 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 134 playSoundEffect(SoundEffectConstants.CLICK); 135 return true; 136 } else if (action == ACTION_LONG_CLICK && mCode != 0) { 137 sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS); 138 sendEvent(KeyEvent.ACTION_UP, 0); 139 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); 140 return true; 141 } 142 return super.performAccessibilityActionInternal(action, arguments); 143 } 144 onTouchEvent(MotionEvent ev)145 public boolean onTouchEvent(MotionEvent ev) { 146 final int action = ev.getAction(); 147 int x, y; 148 if (action == MotionEvent.ACTION_DOWN) { 149 mGestureAborted = false; 150 } 151 if (mGestureAborted) { 152 return false; 153 } 154 155 switch (action) { 156 case MotionEvent.ACTION_DOWN: 157 mDownTime = SystemClock.uptimeMillis(); 158 setPressed(true); 159 if (mCode != 0) { 160 sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime); 161 } else { 162 // Provide the same haptic feedback that the system offers for virtual keys. 163 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 164 } 165 removeCallbacks(mCheckLongPress); 166 postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout()); 167 break; 168 case MotionEvent.ACTION_MOVE: 169 x = (int)ev.getX(); 170 y = (int)ev.getY(); 171 setPressed(x >= -mTouchSlop 172 && x < getWidth() + mTouchSlop 173 && y >= -mTouchSlop 174 && y < getHeight() + mTouchSlop); 175 break; 176 case MotionEvent.ACTION_CANCEL: 177 setPressed(false); 178 if (mCode != 0) { 179 sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED); 180 } 181 removeCallbacks(mCheckLongPress); 182 break; 183 case MotionEvent.ACTION_UP: 184 final boolean doIt = isPressed(); 185 setPressed(false); 186 if (mCode != 0) { 187 if (doIt) { 188 sendEvent(KeyEvent.ACTION_UP, 0); 189 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 190 playSoundEffect(SoundEffectConstants.CLICK); 191 } else { 192 sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED); 193 } 194 } else { 195 // no key code, just a regular ImageView 196 if (doIt) { 197 performClick(); 198 } 199 } 200 removeCallbacks(mCheckLongPress); 201 break; 202 } 203 204 return true; 205 } 206 playSoundEffect(int soundConstant)207 public void playSoundEffect(int soundConstant) { 208 mAudioManager.playSoundEffect(soundConstant, ActivityManager.getCurrentUser()); 209 }; 210 sendEvent(int action, int flags)211 public void sendEvent(int action, int flags) { 212 sendEvent(action, flags, SystemClock.uptimeMillis()); 213 } 214 sendEvent(int action, int flags, long when)215 void sendEvent(int action, int flags, long when) { 216 final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0; 217 final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount, 218 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 219 flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, 220 InputDevice.SOURCE_KEYBOARD); 221 InputManager.getInstance().injectInputEvent(ev, 222 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 223 } 224 abortCurrentGesture()225 public void abortCurrentGesture() { 226 setPressed(false); 227 mGestureAborted = true; 228 } 229 } 230 231 232