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