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 17package com.android.systemui.statusbar.policy; 18 19import android.animation.Animator; 20import android.animation.AnimatorSet; 21import android.animation.ObjectAnimator; 22import android.content.Context; 23import android.content.res.TypedArray; 24import android.graphics.Canvas; 25import android.graphics.RectF; 26import android.graphics.drawable.Drawable; 27import android.hardware.input.InputManager; 28import android.os.SystemClock; 29import android.util.AttributeSet; 30import android.util.Log; 31import android.view.HapticFeedbackConstants; 32import android.view.InputDevice; 33import android.view.KeyCharacterMap; 34import android.view.KeyEvent; 35import android.view.MotionEvent; 36import android.view.SoundEffectConstants; 37import android.view.View; 38import android.view.ViewConfiguration; 39import android.view.ViewDebug; 40import android.view.accessibility.AccessibilityEvent; 41import android.widget.ImageView; 42 43import com.android.systemui.R; 44 45public class KeyButtonView extends ImageView { 46 private static final String TAG = "StatusBar.KeyButtonView"; 47 private static final boolean DEBUG = false; 48 49 final float GLOW_MAX_SCALE_FACTOR = 1.8f; 50 public static final float DEFAULT_QUIESCENT_ALPHA = 0.70f; 51 52 long mDownTime; 53 int mCode; 54 int mTouchSlop; 55 Drawable mGlowBG; 56 int mGlowWidth, mGlowHeight; 57 float mGlowAlpha = 0f, mGlowScale = 1f; 58 @ViewDebug.ExportedProperty(category = "drawing") 59 float mDrawingAlpha = 1f; 60 @ViewDebug.ExportedProperty(category = "drawing") 61 float mQuiescentAlpha = DEFAULT_QUIESCENT_ALPHA; 62 boolean mSupportsLongpress = true; 63 RectF mRect = new RectF(); 64 AnimatorSet mPressedAnim; 65 Animator mAnimateToQuiescent = new ObjectAnimator(); 66 67 Runnable mCheckLongPress = new Runnable() { 68 public void run() { 69 if (isPressed()) { 70 // Log.d("KeyButtonView", "longpressed: " + this); 71 if (mCode != 0) { 72 sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS); 73 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); 74 } else { 75 // Just an old-fashioned ImageView 76 performLongClick(); 77 } 78 } 79 } 80 }; 81 82 public KeyButtonView(Context context, AttributeSet attrs) { 83 this(context, attrs, 0); 84 } 85 86 public KeyButtonView(Context context, AttributeSet attrs, int defStyle) { 87 super(context, attrs); 88 89 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KeyButtonView, 90 defStyle, 0); 91 92 mCode = a.getInteger(R.styleable.KeyButtonView_keyCode, 0); 93 94 mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true); 95 96 mGlowBG = a.getDrawable(R.styleable.KeyButtonView_glowBackground); 97 setDrawingAlpha(mQuiescentAlpha); 98 if (mGlowBG != null) { 99 mGlowWidth = mGlowBG.getIntrinsicWidth(); 100 mGlowHeight = mGlowBG.getIntrinsicHeight(); 101 } 102 103 a.recycle(); 104 105 setClickable(true); 106 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 107 } 108 109 @Override 110 protected void onDraw(Canvas canvas) { 111 if (mGlowBG != null) { 112 canvas.save(); 113 final int w = getWidth(); 114 final int h = getHeight(); 115 final float aspect = (float)mGlowWidth / mGlowHeight; 116 final int drawW = (int)(h*aspect); 117 final int drawH = h; 118 final int margin = (drawW-w)/2; 119 canvas.scale(mGlowScale, mGlowScale, w*0.5f, h*0.5f); 120 mGlowBG.setBounds(-margin, 0, drawW-margin, drawH); 121 mGlowBG.setAlpha((int)(mDrawingAlpha * mGlowAlpha * 255)); 122 mGlowBG.draw(canvas); 123 canvas.restore(); 124 mRect.right = w; 125 mRect.bottom = h; 126 } 127 super.onDraw(canvas); 128 } 129 130 public void setQuiescentAlpha(float alpha, boolean animate) { 131 mAnimateToQuiescent.cancel(); 132 alpha = Math.min(Math.max(alpha, 0), 1); 133 if (alpha == mQuiescentAlpha && alpha == mDrawingAlpha) return; 134 mQuiescentAlpha = alpha; 135 if (DEBUG) Log.d(TAG, "New quiescent alpha = " + mQuiescentAlpha); 136 if (mGlowBG != null && animate) { 137 mAnimateToQuiescent = animateToQuiescent(); 138 mAnimateToQuiescent.start(); 139 } else { 140 setDrawingAlpha(mQuiescentAlpha); 141 } 142 } 143 144 private ObjectAnimator animateToQuiescent() { 145 return ObjectAnimator.ofFloat(this, "drawingAlpha", mQuiescentAlpha); 146 } 147 148 public float getQuiescentAlpha() { 149 return mQuiescentAlpha; 150 } 151 152 public float getDrawingAlpha() { 153 return mDrawingAlpha; 154 } 155 156 public void setDrawingAlpha(float x) { 157 // Calling setAlpha(int), which is an ImageView-specific 158 // method that's different from setAlpha(float). This sets 159 // the alpha on this ImageView's drawable directly 160 setAlpha((int) (x * 255)); 161 mDrawingAlpha = x; 162 } 163 164 public float getGlowAlpha() { 165 if (mGlowBG == null) return 0; 166 return mGlowAlpha; 167 } 168 169 public void setGlowAlpha(float x) { 170 if (mGlowBG == null) return; 171 mGlowAlpha = x; 172 invalidate(); 173 } 174 175 public float getGlowScale() { 176 if (mGlowBG == null) return 0; 177 return mGlowScale; 178 } 179 180 public void setGlowScale(float x) { 181 if (mGlowBG == null) return; 182 mGlowScale = x; 183 final float w = getWidth(); 184 final float h = getHeight(); 185 if (GLOW_MAX_SCALE_FACTOR <= 1.0f) { 186 // this only works if we know the glow will never leave our bounds 187 invalidate(); 188 } else { 189 final float rx = (w * (GLOW_MAX_SCALE_FACTOR - 1.0f)) / 2.0f + 1.0f; 190 final float ry = (h * (GLOW_MAX_SCALE_FACTOR - 1.0f)) / 2.0f + 1.0f; 191 com.android.systemui.SwipeHelper.invalidateGlobalRegion( 192 this, 193 new RectF(getLeft() - rx, 194 getTop() - ry, 195 getRight() + rx, 196 getBottom() + ry)); 197 198 // also invalidate our immediate parent to help avoid situations where nearby glows 199 // interfere 200 ((View)getParent()).invalidate(); 201 } 202 } 203 204 public void setPressed(boolean pressed) { 205 if (mGlowBG != null) { 206 if (pressed != isPressed()) { 207 if (mPressedAnim != null && mPressedAnim.isRunning()) { 208 mPressedAnim.cancel(); 209 } 210 final AnimatorSet as = mPressedAnim = new AnimatorSet(); 211 if (pressed) { 212 if (mGlowScale < GLOW_MAX_SCALE_FACTOR) 213 mGlowScale = GLOW_MAX_SCALE_FACTOR; 214 if (mGlowAlpha < mQuiescentAlpha) 215 mGlowAlpha = mQuiescentAlpha; 216 setDrawingAlpha(1f); 217 as.playTogether( 218 ObjectAnimator.ofFloat(this, "glowAlpha", 1f), 219 ObjectAnimator.ofFloat(this, "glowScale", GLOW_MAX_SCALE_FACTOR) 220 ); 221 as.setDuration(50); 222 } else { 223 mAnimateToQuiescent.cancel(); 224 mAnimateToQuiescent = animateToQuiescent(); 225 as.playTogether( 226 ObjectAnimator.ofFloat(this, "glowAlpha", 0f), 227 ObjectAnimator.ofFloat(this, "glowScale", 1f), 228 mAnimateToQuiescent 229 ); 230 as.setDuration(500); 231 } 232 as.start(); 233 } 234 } 235 super.setPressed(pressed); 236 } 237 238 public boolean onTouchEvent(MotionEvent ev) { 239 final int action = ev.getAction(); 240 int x, y; 241 242 switch (action) { 243 case MotionEvent.ACTION_DOWN: 244 //Log.d("KeyButtonView", "press"); 245 mDownTime = SystemClock.uptimeMillis(); 246 setPressed(true); 247 if (mCode != 0) { 248 sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime); 249 } else { 250 // Provide the same haptic feedback that the system offers for virtual keys. 251 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 252 } 253 if (mSupportsLongpress) { 254 removeCallbacks(mCheckLongPress); 255 postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout()); 256 } 257 break; 258 case MotionEvent.ACTION_MOVE: 259 x = (int)ev.getX(); 260 y = (int)ev.getY(); 261 setPressed(x >= -mTouchSlop 262 && x < getWidth() + mTouchSlop 263 && y >= -mTouchSlop 264 && y < getHeight() + mTouchSlop); 265 break; 266 case MotionEvent.ACTION_CANCEL: 267 setPressed(false); 268 if (mCode != 0) { 269 sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED); 270 } 271 if (mSupportsLongpress) { 272 removeCallbacks(mCheckLongPress); 273 } 274 break; 275 case MotionEvent.ACTION_UP: 276 final boolean doIt = isPressed(); 277 setPressed(false); 278 if (mCode != 0) { 279 if (doIt) { 280 sendEvent(KeyEvent.ACTION_UP, 0); 281 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 282 playSoundEffect(SoundEffectConstants.CLICK); 283 } else { 284 sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED); 285 } 286 } else { 287 // no key code, just a regular ImageView 288 if (doIt) { 289 performClick(); 290 } 291 } 292 if (mSupportsLongpress) { 293 removeCallbacks(mCheckLongPress); 294 } 295 break; 296 } 297 298 return true; 299 } 300 301 void sendEvent(int action, int flags) { 302 sendEvent(action, flags, SystemClock.uptimeMillis()); 303 } 304 305 void sendEvent(int action, int flags, long when) { 306 final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0; 307 final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount, 308 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 309 flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, 310 InputDevice.SOURCE_KEYBOARD); 311 InputManager.getInstance().injectInputEvent(ev, 312 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 313 } 314} 315 316 317