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.animation.Animator; 20 import android.animation.ObjectAnimator; 21 import android.app.ActivityManager; 22 import android.content.Context; 23 import android.content.res.TypedArray; 24 import android.hardware.input.InputManager; 25 import android.media.AudioManager; 26 import android.os.Bundle; 27 import android.os.SystemClock; 28 import android.util.AttributeSet; 29 import android.util.Log; 30 import android.view.HapticFeedbackConstants; 31 import android.view.InputDevice; 32 import android.view.KeyCharacterMap; 33 import android.view.KeyEvent; 34 import android.view.MotionEvent; 35 import android.view.SoundEffectConstants; 36 import android.view.View; 37 import android.view.ViewConfiguration; 38 import android.view.accessibility.AccessibilityEvent; 39 import android.view.accessibility.AccessibilityNodeInfo; 40 import android.widget.ImageView; 41 42 import com.android.systemui.R; 43 44 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; 45 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK; 46 47 public class KeyButtonView extends ImageView { 48 private static final String TAG = "StatusBar.KeyButtonView"; 49 private static final boolean DEBUG = false; 50 51 // TODO: Get rid of this 52 public static final float DEFAULT_QUIESCENT_ALPHA = 1f; 53 54 private long mDownTime; 55 private int mCode; 56 private int mTouchSlop; 57 private float mDrawingAlpha = 1f; 58 private float mQuiescentAlpha = DEFAULT_QUIESCENT_ALPHA; 59 private boolean mSupportsLongpress = true; 60 private AudioManager mAudioManager; 61 private Animator mAnimateToQuiescent = new ObjectAnimator(); 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 } else { 71 sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS); 72 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); 73 } 74 } 75 } 76 }; 77 KeyButtonView(Context context, AttributeSet attrs)78 public KeyButtonView(Context context, AttributeSet attrs) { 79 this(context, attrs, 0); 80 } 81 KeyButtonView(Context context, AttributeSet attrs, int defStyle)82 public KeyButtonView(Context context, AttributeSet attrs, int defStyle) { 83 super(context, attrs); 84 85 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KeyButtonView, 86 defStyle, 0); 87 88 mCode = a.getInteger(R.styleable.KeyButtonView_keyCode, 0); 89 90 mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true); 91 92 93 setDrawingAlpha(mQuiescentAlpha); 94 95 a.recycle(); 96 97 setClickable(true); 98 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 99 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 100 setBackground(new KeyButtonRipple(context, this)); 101 } 102 103 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)104 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 105 super.onInitializeAccessibilityNodeInfo(info); 106 if (mCode != 0) { 107 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null)); 108 if (mSupportsLongpress) { 109 info.addAction( 110 new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK, null)); 111 } 112 } 113 } 114 115 @Override onWindowVisibilityChanged(int visibility)116 protected void onWindowVisibilityChanged(int visibility) { 117 super.onWindowVisibilityChanged(visibility); 118 if (visibility != View.VISIBLE) { 119 jumpDrawablesToCurrentState(); 120 } 121 } 122 123 @Override performAccessibilityAction(int action, Bundle arguments)124 public boolean performAccessibilityAction(int action, Bundle arguments) { 125 if (action == ACTION_CLICK && mCode != 0) { 126 sendEvent(KeyEvent.ACTION_DOWN, 0, SystemClock.uptimeMillis()); 127 sendEvent(KeyEvent.ACTION_UP, 0); 128 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 129 playSoundEffect(SoundEffectConstants.CLICK); 130 return true; 131 } else if (action == ACTION_LONG_CLICK && mCode != 0 && mSupportsLongpress) { 132 sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS); 133 sendEvent(KeyEvent.ACTION_UP, 0); 134 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); 135 return true; 136 } 137 return super.performAccessibilityAction(action, arguments); 138 } 139 setQuiescentAlpha(float alpha, boolean animate)140 public void setQuiescentAlpha(float alpha, boolean animate) { 141 mAnimateToQuiescent.cancel(); 142 alpha = Math.min(Math.max(alpha, 0), 1); 143 if (alpha == mQuiescentAlpha && alpha == mDrawingAlpha) return; 144 mQuiescentAlpha = alpha; 145 if (DEBUG) Log.d(TAG, "New quiescent alpha = " + mQuiescentAlpha); 146 if (animate) { 147 mAnimateToQuiescent = animateToQuiescent(); 148 mAnimateToQuiescent.start(); 149 } else { 150 setDrawingAlpha(mQuiescentAlpha); 151 } 152 } 153 animateToQuiescent()154 private ObjectAnimator animateToQuiescent() { 155 return ObjectAnimator.ofFloat(this, "drawingAlpha", mQuiescentAlpha); 156 } 157 getQuiescentAlpha()158 public float getQuiescentAlpha() { 159 return mQuiescentAlpha; 160 } 161 getDrawingAlpha()162 public float getDrawingAlpha() { 163 return mDrawingAlpha; 164 } 165 setDrawingAlpha(float x)166 public void setDrawingAlpha(float x) { 167 setImageAlpha((int) (x * 255)); 168 mDrawingAlpha = x; 169 } 170 onTouchEvent(MotionEvent ev)171 public boolean onTouchEvent(MotionEvent ev) { 172 final int action = ev.getAction(); 173 int x, y; 174 175 switch (action) { 176 case MotionEvent.ACTION_DOWN: 177 //Log.d("KeyButtonView", "press"); 178 mDownTime = SystemClock.uptimeMillis(); 179 setPressed(true); 180 if (mCode != 0) { 181 sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime); 182 } else { 183 // Provide the same haptic feedback that the system offers for virtual keys. 184 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 185 } 186 if (mSupportsLongpress) { 187 removeCallbacks(mCheckLongPress); 188 postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout()); 189 } 190 break; 191 case MotionEvent.ACTION_MOVE: 192 x = (int)ev.getX(); 193 y = (int)ev.getY(); 194 setPressed(x >= -mTouchSlop 195 && x < getWidth() + mTouchSlop 196 && y >= -mTouchSlop 197 && y < getHeight() + mTouchSlop); 198 break; 199 case MotionEvent.ACTION_CANCEL: 200 setPressed(false); 201 if (mCode != 0) { 202 sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED); 203 } 204 if (mSupportsLongpress) { 205 removeCallbacks(mCheckLongPress); 206 } 207 break; 208 case MotionEvent.ACTION_UP: 209 final boolean doIt = isPressed(); 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 if (mSupportsLongpress) { 226 removeCallbacks(mCheckLongPress); 227 } 228 break; 229 } 230 231 return true; 232 } 233 playSoundEffect(int soundConstant)234 public void playSoundEffect(int soundConstant) { 235 mAudioManager.playSoundEffect(soundConstant, ActivityManager.getCurrentUser()); 236 }; 237 sendEvent(int action, int flags)238 public void sendEvent(int action, int flags) { 239 sendEvent(action, flags, SystemClock.uptimeMillis()); 240 } 241 sendEvent(int action, int flags, long when)242 void sendEvent(int action, int flags, long when) { 243 final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0; 244 final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount, 245 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 246 flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, 247 InputDevice.SOURCE_KEYBOARD); 248 InputManager.getInstance().injectInputEvent(ev, 249 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 250 } 251 } 252 253 254