blob: f859880707ccdc1fa74146298d97dbdc9edd80b0 [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;
Jeff Brown98365d72012-08-19 20:30:52 -070036import android.view.WindowManagerGlobal;
Dianne Hackborna4b7f2f2012-05-21 11:28:41 -070037import android.view.animation.Transformation;
38import android.widget.FrameLayout;
39
40public 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);
Jeff Brownf9e989d2013-04-04 23:04:03 -0700100 mSession = WindowManagerGlobal.getWindowSession();
Dianne Hackborna4b7f2f2012-05-21 11:28:41 -0700101 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
Jeff Brown98365d72012-08-19 20:30:52 -0700159 public WindowManager.LayoutParams getLayoutParams() {
Dianne Hackborna4b7f2f2012-05-21 11:28:41 -0700160 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
Jeff Brown98365d72012-08-19 20:30:52 -0700169 if (ActivityManager.isHighEndGfx()) {
Dianne Hackborna4b7f2f2012-05-21 11:28:41 -0700170 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}