1 /* 2 * Copyright (C) 2012 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; 18 19 import android.app.ActivityManager; 20 import android.content.Context; 21 import android.content.res.Configuration; 22 import android.content.res.Resources; 23 import android.graphics.Matrix; 24 import android.graphics.PixelFormat; 25 import android.os.RemoteException; 26 import android.util.Log; 27 import android.util.Slog; 28 import android.view.Choreographer; 29 import android.view.Display; 30 import android.view.IWindowSession; 31 import android.view.MotionEvent; 32 import android.view.VelocityTracker; 33 import android.view.View; 34 import android.view.ViewRootImpl; 35 import android.view.WindowManager; 36 import android.view.WindowManagerGlobal; 37 import android.view.animation.Transformation; 38 import android.widget.FrameLayout; 39 40 public class UniverseBackground extends FrameLayout { 41 static final String TAG = "UniverseBackground"; 42 static final boolean SPEW = false; 43 static final boolean CHATTY = false; 44 45 final IWindowSession mSession; 46 final View mContent; 47 final View mBottomAnchor; 48 49 final Runnable mAnimationCallback = new Runnable() { 50 @Override 51 public void run() { 52 doAnimation(mChoreographer.getFrameTimeNanos()); 53 } 54 }; 55 56 // fling gesture tuning parameters, scaled to display density 57 private float mSelfExpandVelocityPx; // classic value: 2000px/s 58 private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up") 59 private float mFlingExpandMinVelocityPx; // classic value: 200px/s 60 private float mFlingCollapseMinVelocityPx; // classic value: 200px/s 61 private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1) 62 private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand) 63 private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s 64 65 private float mExpandAccelPx; // classic value: 2000px/s/s 66 private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up") 67 68 static final int STATE_CLOSED = 0; 69 static final int STATE_OPENING = 1; 70 static final int STATE_OPEN = 2; 71 private int mState = STATE_CLOSED; 72 73 private float mDragStartX, mDragStartY; 74 private float mAverageX, mAverageY; 75 76 // position 77 private int[] mPositionTmp = new int[2]; 78 private boolean mExpanded; 79 private boolean mExpandedVisible; 80 81 private boolean mTracking; 82 private VelocityTracker mVelocityTracker; 83 84 private Choreographer mChoreographer; 85 private boolean mAnimating; 86 private boolean mClosing; // only valid when mAnimating; indicates the initial acceleration 87 private float mAnimY; 88 private float mAnimVel; 89 private float mAnimAccel; 90 private long mAnimLastTimeNanos; 91 private boolean mAnimatingReveal = false; 92 93 private int mYDelta = 0; 94 private Transformation mUniverseTransform = new Transformation(); 95 private final float[] mTmpFloats = new float[9]; 96 97 public UniverseBackground(Context context) { 98 super(context); 99 setBackgroundColor(0xff000000); 100 mSession = WindowManagerGlobal.getWindowSession(); 101 mContent = View.inflate(context, R.layout.universe, null); 102 addView(mContent); 103 mContent.findViewById(R.id.close).setOnClickListener(new View.OnClickListener() { 104 @Override public void onClick(View v) { 105 animateCollapse(); 106 } 107 }); 108 mBottomAnchor = mContent.findViewById(R.id.bottom); 109 mChoreographer = Choreographer.getInstance(); 110 loadDimens(); 111 } 112 113 @Override 114 protected void onConfigurationChanged(Configuration newConfig) { 115 super.onConfigurationChanged(newConfig); 116 loadDimens(); 117 } 118 119 private void loadDimens() { 120 final Resources res = getContext().getResources(); 121 mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity); 122 mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity); 123 mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity); 124 mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity); 125 126 mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1); 127 mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1); 128 129 mExpandAccelPx = res.getDimension(R.dimen.expand_accel); 130 mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel); 131 132 mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity); 133 } 134 135 private void computeAveragePos(MotionEvent event) { 136 final int num = event.getPointerCount(); 137 float x = 0, y = 0; 138 for (int i=0; i<num; i++) { 139 x += event.getX(i); 140 y += event.getY(i); 141 } 142 mAverageX = x / num; 143 mAverageY = y / num; 144 } 145 146 private void sendUniverseTransform() { 147 if (getWindowToken() != null) { 148 mUniverseTransform.getMatrix().getValues(mTmpFloats); 149 try { 150 mSession.setUniverseTransform(getWindowToken(), mUniverseTransform.getAlpha(), 151 mTmpFloats[Matrix.MTRANS_X], mTmpFloats[Matrix.MTRANS_Y], 152 mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], 153 mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); 154 } catch (RemoteException e) { 155 } 156 } 157 } 158 159 public WindowManager.LayoutParams getLayoutParams() { 160 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 161 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 162 WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND, 163 0 164 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 165 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 166 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 167 PixelFormat.OPAQUE); 168 // this will allow the window to run in an overlay on devices that support this 169 if (ActivityManager.isHighEndGfx()) { 170 lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 171 } 172 lp.setTitle("UniverseBackground"); 173 lp.windowAnimations = 0; 174 return lp; 175 } 176 177 private int getExpandedViewMaxHeight() { 178 return mBottomAnchor.getTop(); 179 } 180 181 public void animateCollapse() { 182 animateCollapse(1.0f); 183 } 184 185 public void animateCollapse(float velocityMultiplier) { 186 if (SPEW) { 187 Slog.d(TAG, "animateCollapse(): mExpanded=" + mExpanded 188 + " mExpandedVisible=" + mExpandedVisible 189 + " mExpanded=" + mExpanded 190 + " mAnimating=" + mAnimating 191 + " mAnimY=" + mAnimY 192 + " mAnimVel=" + mAnimVel); 193 } 194 195 mState = STATE_CLOSED; 196 if (!mExpandedVisible) { 197 return; 198 } 199 200 int y; 201 if (mAnimating) { 202 y = (int)mAnimY; 203 } else { 204 y = getExpandedViewMaxHeight()-1; 205 } 206 // Let the fling think that we're open so it goes in the right direction 207 // and doesn't try to re-open the windowshade. 208 mExpanded = true; 209 prepareTracking(y, false); 210 performFling(y, -mSelfCollapseVelocityPx*velocityMultiplier, true); 211 } 212 213 private void updateUniverseScale() { 214 if (mYDelta > 0) { 215 int w = getWidth(); 216 int h = getHeight(); 217 float scale = (h-mYDelta+.5f) / (float)h; 218 mUniverseTransform.getMatrix().setScale(scale, scale, w/2, h); 219 if (CHATTY) Log.i(TAG, "w=" + w + " h=" + h + " scale=" + scale 220 + ": " + mUniverseTransform); 221 sendUniverseTransform(); 222 if (getVisibility() != VISIBLE) { 223 setVisibility(VISIBLE); 224 } 225 } else { 226 if (CHATTY) Log.i(TAG, "mYDelta=" + mYDelta); 227 mUniverseTransform.clear(); 228 sendUniverseTransform(); 229 if (getVisibility() == VISIBLE) { 230 setVisibility(GONE); 231 } 232 } 233 } 234 235 void resetLastAnimTime() { 236 mAnimLastTimeNanos = System.nanoTime(); 237 if (SPEW) { 238 Throwable t = new Throwable(); 239 t.fillInStackTrace(); 240 Slog.d(TAG, "resetting last anim time=" + mAnimLastTimeNanos, t); 241 } 242 } 243 244 void doAnimation(long frameTimeNanos) { 245 if (mAnimating) { 246 if (SPEW) Slog.d(TAG, "doAnimation dt=" + (frameTimeNanos - mAnimLastTimeNanos)); 247 if (SPEW) Slog.d(TAG, "doAnimation before mAnimY=" + mAnimY); 248 incrementAnim(frameTimeNanos); 249 if (SPEW) { 250 Slog.d(TAG, "doAnimation after mAnimY=" + mAnimY); 251 } 252 253 if (mAnimY >= getExpandedViewMaxHeight()-1 && !mClosing) { 254 if (SPEW) Slog.d(TAG, "Animation completed to expanded state."); 255 mAnimating = false; 256 mYDelta = getExpandedViewMaxHeight(); 257 updateUniverseScale(); 258 mExpanded = true; 259 mState = STATE_OPEN; 260 return; 261 } 262 263 if (mAnimY <= 0 && mClosing) { 264 if (SPEW) Slog.d(TAG, "Animation completed to collapsed state."); 265 mAnimating = false; 266 mYDelta = 0; 267 updateUniverseScale(); 268 mExpanded = false; 269 mState = STATE_CLOSED; 270 return; 271 } 272 273 mYDelta = (int)mAnimY; 274 updateUniverseScale(); 275 mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, 276 mAnimationCallback, null); 277 } 278 } 279 280 void stopTracking() { 281 mTracking = false; 282 mVelocityTracker.recycle(); 283 mVelocityTracker = null; 284 } 285 286 void incrementAnim(long frameTimeNanos) { 287 final long deltaNanos = Math.max(frameTimeNanos - mAnimLastTimeNanos, 0); 288 final float t = deltaNanos * 0.000000001f; // ns -> s 289 final float y = mAnimY; 290 final float v = mAnimVel; // px/s 291 final float a = mAnimAccel; // px/s/s 292 mAnimY = y + (v*t) + (0.5f*a*t*t); // px 293 mAnimVel = v + (a*t); // px/s 294 mAnimLastTimeNanos = frameTimeNanos; // ns 295 //Slog.d(TAG, "y=" + y + " v=" + v + " a=" + a + " t=" + t + " mAnimY=" + mAnimY 296 // + " mAnimAccel=" + mAnimAccel); 297 } 298 299 void prepareTracking(int y, boolean opening) { 300 if (CHATTY) { 301 Slog.d(TAG, "panel: beginning to track the user's touch, y=" + y + " opening=" + opening); 302 } 303 304 mTracking = true; 305 mVelocityTracker = VelocityTracker.obtain(); 306 if (opening) { 307 mAnimAccel = mExpandAccelPx; 308 mAnimVel = mFlingExpandMinVelocityPx; 309 mAnimY = y; 310 mAnimating = true; 311 mAnimatingReveal = true; 312 resetLastAnimTime(); 313 mExpandedVisible = true; 314 } 315 if (mAnimating) { 316 mAnimating = false; 317 mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, 318 mAnimationCallback, null); 319 } 320 } 321 322 void performFling(int y, float vel, boolean always) { 323 if (CHATTY) { 324 Slog.d(TAG, "panel: will fling, y=" + y + " vel=" + vel); 325 } 326 327 mAnimatingReveal = false; 328 329 mAnimY = y; 330 mAnimVel = vel; 331 332 //Slog.d(TAG, "starting with mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel); 333 334 if (mExpanded) { 335 if (!always && ( 336 vel > mFlingCollapseMinVelocityPx 337 || (y > (getExpandedViewMaxHeight()*(1f-mCollapseMinDisplayFraction)) && 338 vel > -mFlingExpandMinVelocityPx))) { 339 // We are expanded, but they didn't move sufficiently to cause 340 // us to retract. Animate back to the expanded position. 341 mAnimAccel = mExpandAccelPx; 342 if (vel < 0) { 343 mAnimVel = 0; 344 } 345 } 346 else { 347 // We are expanded and are now going to animate away. 348 mAnimAccel = -mCollapseAccelPx; 349 if (vel > 0) { 350 mAnimVel = 0; 351 } 352 } 353 } else { 354 if (always || ( 355 vel > mFlingExpandMinVelocityPx 356 || (y > (getExpandedViewMaxHeight()*(1f-mExpandMinDisplayFraction)) && 357 vel > -mFlingCollapseMinVelocityPx))) { 358 // We are collapsed, and they moved enough to allow us to 359 // expand. Animate in the notifications. 360 mAnimAccel = mExpandAccelPx; 361 if (vel < 0) { 362 mAnimVel = 0; 363 } 364 } 365 else { 366 // We are collapsed, but they didn't move sufficiently to cause 367 // us to retract. Animate back to the collapsed position. 368 mAnimAccel = -mCollapseAccelPx; 369 if (vel > 0) { 370 mAnimVel = 0; 371 } 372 } 373 } 374 //Slog.d(TAG, "mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel 375 // + " mAnimAccel=" + mAnimAccel); 376 377 resetLastAnimTime(); 378 mAnimating = true; 379 mClosing = mAnimAccel < 0; 380 mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, 381 mAnimationCallback, null); 382 mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, 383 mAnimationCallback, null); 384 385 stopTracking(); 386 } 387 388 private void trackMovement(MotionEvent event) { 389 mVelocityTracker.addMovement(event); 390 } 391 392 public boolean consumeEvent(MotionEvent event) { 393 if (mState == STATE_CLOSED) { 394 if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { 395 // Second finger down, time to start opening! 396 computeAveragePos(event); 397 mDragStartX = mAverageX; 398 mDragStartY = mAverageY; 399 mYDelta = 0; 400 mUniverseTransform.clear(); 401 sendUniverseTransform(); 402 setVisibility(VISIBLE); 403 mState = STATE_OPENING; 404 prepareTracking((int)mDragStartY, true); 405 mVelocityTracker.clear(); 406 trackMovement(event); 407 return true; 408 } 409 return false; 410 } 411 412 if (mState == STATE_OPENING) { 413 if (event.getActionMasked() == MotionEvent.ACTION_UP 414 || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { 415 mVelocityTracker.computeCurrentVelocity(1000); 416 computeAveragePos(event); 417 418 float yVel = mVelocityTracker.getYVelocity(); 419 boolean negative = yVel < 0; 420 421 float xVel = mVelocityTracker.getXVelocity(); 422 if (xVel < 0) { 423 xVel = -xVel; 424 } 425 if (xVel > mFlingGestureMaxXVelocityPx) { 426 xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis 427 } 428 429 float vel = (float)Math.hypot(yVel, xVel); 430 if (negative) { 431 vel = -vel; 432 } 433 434 if (CHATTY) { 435 Slog.d(TAG, String.format("gesture: vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f", 436 mVelocityTracker.getXVelocity(), 437 mVelocityTracker.getYVelocity(), 438 xVel, yVel, 439 vel)); 440 } 441 442 performFling((int)mAverageY, vel, false); 443 mState = STATE_OPEN; 444 return true; 445 } 446 447 computeAveragePos(event); 448 mYDelta = (int)(mAverageY - mDragStartY); 449 if (mYDelta > getExpandedViewMaxHeight()) { 450 mYDelta = getExpandedViewMaxHeight(); 451 } 452 updateUniverseScale(); 453 return true; 454 } 455 456 return false; 457 } 458 } 459