/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui; import android.app.ActivityManager; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.os.RemoteException; import android.util.Log; import android.util.Slog; import android.view.Choreographer; import android.view.Display; import android.view.IWindowSession; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewRootImpl; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.animation.Transformation; import android.widget.FrameLayout; public class UniverseBackground extends FrameLayout { static final String TAG = "UniverseBackground"; static final boolean SPEW = false; static final boolean CHATTY = false; final IWindowSession mSession; final View mContent; final View mBottomAnchor; final Runnable mAnimationCallback = new Runnable() { @Override public void run() { doAnimation(mChoreographer.getFrameTimeNanos()); } }; // fling gesture tuning parameters, scaled to display density private float mSelfExpandVelocityPx; // classic value: 2000px/s private float mSelfCollapseVelocityPx; // classic value: 2000px/s (will be negated to collapse "up") private float mFlingExpandMinVelocityPx; // classic value: 200px/s private float mFlingCollapseMinVelocityPx; // classic value: 200px/s private float mCollapseMinDisplayFraction; // classic value: 0.08 (25px/min(320px,480px) on G1) private float mExpandMinDisplayFraction; // classic value: 0.5 (drag open halfway to expand) private float mFlingGestureMaxXVelocityPx; // classic value: 150px/s private float mExpandAccelPx; // classic value: 2000px/s/s private float mCollapseAccelPx; // classic value: 2000px/s/s (will be negated to collapse "up") static final int STATE_CLOSED = 0; static final int STATE_OPENING = 1; static final int STATE_OPEN = 2; private int mState = STATE_CLOSED; private float mDragStartX, mDragStartY; private float mAverageX, mAverageY; // position private int[] mPositionTmp = new int[2]; private boolean mExpanded; private boolean mExpandedVisible; private boolean mTracking; private VelocityTracker mVelocityTracker; private Choreographer mChoreographer; private boolean mAnimating; private boolean mClosing; // only valid when mAnimating; indicates the initial acceleration private float mAnimY; private float mAnimVel; private float mAnimAccel; private long mAnimLastTimeNanos; private boolean mAnimatingReveal = false; private int mYDelta = 0; private Transformation mUniverseTransform = new Transformation(); private final float[] mTmpFloats = new float[9]; public UniverseBackground(Context context) { super(context); setBackgroundColor(0xff000000); mSession = WindowManagerGlobal.getWindowSession(); mContent = View.inflate(context, R.layout.universe, null); addView(mContent); mContent.findViewById(R.id.close).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { animateCollapse(); } }); mBottomAnchor = mContent.findViewById(R.id.bottom); mChoreographer = Choreographer.getInstance(); loadDimens(); } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); loadDimens(); } private void loadDimens() { final Resources res = getContext().getResources(); mSelfExpandVelocityPx = res.getDimension(R.dimen.self_expand_velocity); mSelfCollapseVelocityPx = res.getDimension(R.dimen.self_collapse_velocity); mFlingExpandMinVelocityPx = res.getDimension(R.dimen.fling_expand_min_velocity); mFlingCollapseMinVelocityPx = res.getDimension(R.dimen.fling_collapse_min_velocity); mCollapseMinDisplayFraction = res.getFraction(R.dimen.collapse_min_display_fraction, 1, 1); mExpandMinDisplayFraction = res.getFraction(R.dimen.expand_min_display_fraction, 1, 1); mExpandAccelPx = res.getDimension(R.dimen.expand_accel); mCollapseAccelPx = res.getDimension(R.dimen.collapse_accel); mFlingGestureMaxXVelocityPx = res.getDimension(R.dimen.fling_gesture_max_x_velocity); } private void computeAveragePos(MotionEvent event) { final int num = event.getPointerCount(); float x = 0, y = 0; for (int i=0; i s final float y = mAnimY; final float v = mAnimVel; // px/s final float a = mAnimAccel; // px/s/s mAnimY = y + (v*t) + (0.5f*a*t*t); // px mAnimVel = v + (a*t); // px/s mAnimLastTimeNanos = frameTimeNanos; // ns //Slog.d(TAG, "y=" + y + " v=" + v + " a=" + a + " t=" + t + " mAnimY=" + mAnimY // + " mAnimAccel=" + mAnimAccel); } void prepareTracking(int y, boolean opening) { if (CHATTY) { Slog.d(TAG, "panel: beginning to track the user's touch, y=" + y + " opening=" + opening); } mTracking = true; mVelocityTracker = VelocityTracker.obtain(); if (opening) { mAnimAccel = mExpandAccelPx; mAnimVel = mFlingExpandMinVelocityPx; mAnimY = y; mAnimating = true; mAnimatingReveal = true; resetLastAnimTime(); mExpandedVisible = true; } if (mAnimating) { mAnimating = false; mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, mAnimationCallback, null); } } void performFling(int y, float vel, boolean always) { if (CHATTY) { Slog.d(TAG, "panel: will fling, y=" + y + " vel=" + vel); } mAnimatingReveal = false; mAnimY = y; mAnimVel = vel; //Slog.d(TAG, "starting with mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel); if (mExpanded) { if (!always && ( vel > mFlingCollapseMinVelocityPx || (y > (getExpandedViewMaxHeight()*(1f-mCollapseMinDisplayFraction)) && vel > -mFlingExpandMinVelocityPx))) { // We are expanded, but they didn't move sufficiently to cause // us to retract. Animate back to the expanded position. mAnimAccel = mExpandAccelPx; if (vel < 0) { mAnimVel = 0; } } else { // We are expanded and are now going to animate away. mAnimAccel = -mCollapseAccelPx; if (vel > 0) { mAnimVel = 0; } } } else { if (always || ( vel > mFlingExpandMinVelocityPx || (y > (getExpandedViewMaxHeight()*(1f-mExpandMinDisplayFraction)) && vel > -mFlingCollapseMinVelocityPx))) { // We are collapsed, and they moved enough to allow us to // expand. Animate in the notifications. mAnimAccel = mExpandAccelPx; if (vel < 0) { mAnimVel = 0; } } else { // We are collapsed, but they didn't move sufficiently to cause // us to retract. Animate back to the collapsed position. mAnimAccel = -mCollapseAccelPx; if (vel > 0) { mAnimVel = 0; } } } //Slog.d(TAG, "mAnimY=" + mAnimY + " mAnimVel=" + mAnimVel // + " mAnimAccel=" + mAnimAccel); resetLastAnimTime(); mAnimating = true; mClosing = mAnimAccel < 0; mChoreographer.removeCallbacks(Choreographer.CALLBACK_ANIMATION, mAnimationCallback, null); mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimationCallback, null); stopTracking(); } private void trackMovement(MotionEvent event) { mVelocityTracker.addMovement(event); } public boolean consumeEvent(MotionEvent event) { if (mState == STATE_CLOSED) { if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { // Second finger down, time to start opening! computeAveragePos(event); mDragStartX = mAverageX; mDragStartY = mAverageY; mYDelta = 0; mUniverseTransform.clear(); sendUniverseTransform(); setVisibility(VISIBLE); mState = STATE_OPENING; prepareTracking((int)mDragStartY, true); mVelocityTracker.clear(); trackMovement(event); return true; } return false; } if (mState == STATE_OPENING) { if (event.getActionMasked() == MotionEvent.ACTION_UP || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { mVelocityTracker.computeCurrentVelocity(1000); computeAveragePos(event); float yVel = mVelocityTracker.getYVelocity(); boolean negative = yVel < 0; float xVel = mVelocityTracker.getXVelocity(); if (xVel < 0) { xVel = -xVel; } if (xVel > mFlingGestureMaxXVelocityPx) { xVel = mFlingGestureMaxXVelocityPx; // limit how much we care about the x axis } float vel = (float)Math.hypot(yVel, xVel); if (negative) { vel = -vel; } if (CHATTY) { Slog.d(TAG, String.format("gesture: vraw=(%f,%f) vnorm=(%f,%f) vlinear=%f", mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity(), xVel, yVel, vel)); } performFling((int)mAverageY, vel, false); mState = STATE_OPEN; return true; } computeAveragePos(event); mYDelta = (int)(mAverageY - mDragStartY); if (mYDelta > getExpandedViewMaxHeight()) { mYDelta = getExpandedViewMaxHeight(); } updateUniverseScale(); return true; } return false; } }