Home | History | Annotate | Download | only in systemui
      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(context.getMainLooper());
    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