NotificationPanelView.java revision d45c6d4f403a90031411651d57448a1e08309bbf
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
17package com.android.systemui.statusbar.phone;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.ValueAnimator;
23import android.content.Context;
24import android.content.res.Configuration;
25import android.util.AttributeSet;
26import android.view.MotionEvent;
27import android.view.VelocityTracker;
28import android.view.View;
29import android.view.ViewGroup;
30import android.view.ViewTreeObserver;
31import android.view.accessibility.AccessibilityEvent;
32import android.view.animation.AnimationUtils;
33import android.view.animation.Interpolator;
34import android.widget.LinearLayout;
35import android.widget.TextView;
36
37import com.android.systemui.R;
38import com.android.systemui.qs.QSPanel;
39import com.android.systemui.statusbar.ExpandableView;
40import com.android.systemui.statusbar.FlingAnimationUtils;
41import com.android.systemui.statusbar.GestureRecorder;
42import com.android.systemui.statusbar.KeyguardAffordanceView;
43import com.android.systemui.statusbar.MirrorView;
44import com.android.systemui.statusbar.StatusBarState;
45import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
46import com.android.systemui.statusbar.stack.StackStateAnimator;
47
48public class NotificationPanelView extends PanelView implements
49        ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
50        View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
51        KeyguardAffordanceHelper.Callback {
52
53    // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
54    // changed.
55    private static final int CAP_HEIGHT = 1456;
56    private static final int FONT_HEIGHT = 2163;
57
58    private static final float HEADER_RUBBERBAND_FACTOR = 2.15f;
59    private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f;
60
61    private KeyguardAffordanceHelper mAfforanceHelper;
62    private StatusBarHeaderView mHeader;
63    private KeyguardStatusBarView mKeyguardStatusBar;
64    private View mQsContainer;
65    private QSPanel mQsPanel;
66    private View mKeyguardStatusView;
67    private ObservableScrollView mScrollView;
68    private TextView mClockView;
69    private View mReserveNotificationSpace;
70    private MirrorView mSystemIconsCopy;
71
72    private NotificationStackScrollLayout mNotificationStackScroller;
73    private int mNotificationTopPadding;
74    private boolean mAnimateNextTopPaddingChange;
75
76    private int mTrackingPointer;
77    private VelocityTracker mVelocityTracker;
78    private boolean mQsTracking;
79
80    /**
81     * Handles launching the secure camera properly even when other applications may be using the
82     * camera hardware.
83     */
84    private SecureCameraLaunchManager mSecureCameraLaunchManager;
85
86    /**
87     * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and
88     * the expansion for quick settings.
89     */
90    private boolean mConflictingQsExpansionGesture;
91
92    /**
93     * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't
94     * intercepted yet.
95     */
96    private boolean mIntercepting;
97    private boolean mQsExpanded;
98    private boolean mQsExpandedWhenExpandingStarted;
99    private boolean mQsFullyExpanded;
100    private boolean mKeyguardShowing;
101    private int mStatusBarState;
102    private float mInitialHeightOnTouch;
103    private float mInitialTouchX;
104    private float mInitialTouchY;
105    private float mLastTouchX;
106    private float mLastTouchY;
107    private float mQsExpansionHeight;
108    private int mQsMinExpansionHeight;
109    private int mQsMaxExpansionHeight;
110    private int mQsPeekHeight;
111    private boolean mStackScrollerOverscrolling;
112    private boolean mQsExpansionFromOverscroll;
113    private boolean mQsExpansionEnabled = true;
114    private ValueAnimator mQsExpansionAnimator;
115    private FlingAnimationUtils mFlingAnimationUtils;
116    private int mStatusBarMinHeight;
117    private boolean mUnlockIconActive;
118    private int mNotificationsHeaderCollideDistance;
119    private int mUnlockMoveDistance;
120    private float mEmptyDragAmount;
121
122    private Interpolator mFastOutSlowInInterpolator;
123    private Interpolator mFastOutLinearInterpolator;
124    private ObjectAnimator mClockAnimator;
125    private int mClockAnimationTarget = -1;
126    private int mTopPaddingAdjustment;
127    private KeyguardClockPositionAlgorithm mClockPositionAlgorithm =
128            new KeyguardClockPositionAlgorithm();
129    private KeyguardClockPositionAlgorithm.Result mClockPositionResult =
130            new KeyguardClockPositionAlgorithm.Result();
131    private boolean mIsExpanding;
132
133    private boolean mBlockTouches;
134    private int mNotificationScrimWaitDistance;
135    private boolean mTwoFingerQsExpand;
136    private boolean mTwoFingerQsExpandPossible;
137
138    /**
139     * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
140     * need to take this into account in our panel height calculation.
141     */
142    private int mScrollYOverride = -1;
143    private boolean mQsAnimatorExpand;
144    private boolean mIsLaunchTransitionFinished;
145    private boolean mIsLaunchTransitionRunning;
146    private Runnable mLaunchAnimationEndRunnable;
147    private boolean mOnlyAffordanceInThisMotion;
148    private boolean mKeyguardStatusViewAnimating;
149
150    public NotificationPanelView(Context context, AttributeSet attrs) {
151        super(context, attrs);
152        mSystemIconsCopy = new MirrorView(context);
153    }
154
155    public void setStatusBar(PhoneStatusBar bar) {
156        mStatusBar = bar;
157    }
158
159    @Override
160    protected void onFinishInflate() {
161        super.onFinishInflate();
162        mHeader = (StatusBarHeaderView) findViewById(R.id.header);
163        mHeader.setOnClickListener(this);
164        mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header);
165        mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
166        mQsContainer = findViewById(R.id.quick_settings_container);
167        mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
168        mClockView = (TextView) findViewById(R.id.clock_view);
169        mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
170        mScrollView.setListener(this);
171        mReserveNotificationSpace = findViewById(R.id.reserve_notification_space);
172        mNotificationStackScroller = (NotificationStackScrollLayout)
173                findViewById(R.id.notification_stack_scroller);
174        mNotificationStackScroller.setOnHeightChangedListener(this);
175        mNotificationStackScroller.setOverscrollTopChangedListener(this);
176        mNotificationStackScroller.setScrollView(mScrollView);
177        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
178                android.R.interpolator.fast_out_slow_in);
179        mFastOutLinearInterpolator = AnimationUtils.loadInterpolator(getContext(),
180                android.R.interpolator.fast_out_linear_in);
181        mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
182        mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
183        mSecureCameraLaunchManager =
184                new SecureCameraLaunchManager(getContext(), mKeyguardBottomArea);
185    }
186
187    @Override
188    protected void loadDimens() {
189        super.loadDimens();
190        mNotificationTopPadding = getResources().getDimensionPixelSize(
191                R.dimen.notifications_top_padding);
192        mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f);
193        mStatusBarMinHeight = getResources().getDimensionPixelSize(
194                com.android.internal.R.dimen.status_bar_height);
195        mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height);
196        mNotificationsHeaderCollideDistance =
197                getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance);
198        mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance);
199        mClockPositionAlgorithm.loadDimens(getResources());
200        mNotificationScrimWaitDistance =
201                getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance);
202    }
203
204    @Override
205    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
206        super.onLayout(changed, left, top, right, bottom);
207
208        // Update Clock Pivot
209        mKeyguardStatusView.setPivotX(getWidth() / 2);
210        mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize());
211
212        // Calculate quick setting heights.
213        mQsMinExpansionHeight = mKeyguardShowing ? 0 : mHeader.getCollapsedHeight() + mQsPeekHeight;
214        mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight();
215        if (mQsExpanded) {
216            if (mQsFullyExpanded) {
217                mQsExpansionHeight = mQsMaxExpansionHeight;
218                requestScrollerTopPaddingUpdate(false /* animate */);
219            }
220        } else {
221            if (!mStackScrollerOverscrolling) {
222                setQsExpansion(mQsMinExpansionHeight);
223            }
224            positionClockAndNotifications();
225            mNotificationStackScroller.setStackHeight(getExpandedHeight());
226        }
227    }
228
229    @Override
230    public void onAttachedToWindow() {
231        mSecureCameraLaunchManager.create();
232    }
233
234    @Override
235    public void onDetachedFromWindow() {
236        mSecureCameraLaunchManager.destroy();
237    }
238
239    /**
240     * Positions the clock and notifications dynamically depending on how many notifications are
241     * showing.
242     */
243    private void positionClockAndNotifications() {
244        boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
245        int stackScrollerPadding;
246        if (mStatusBarState != StatusBarState.KEYGUARD) {
247            int bottom = mHeader.getCollapsedHeight();
248            stackScrollerPadding = mStatusBarState == StatusBarState.SHADE
249                    ? bottom + mQsPeekHeight + mNotificationTopPadding
250                    : mKeyguardStatusBar.getHeight() + mNotificationTopPadding;
251            mTopPaddingAdjustment = 0;
252        } else {
253            mClockPositionAlgorithm.setup(
254                    mStatusBar.getMaxKeyguardNotifications(),
255                    getMaxPanelHeight(),
256                    getExpandedHeight(),
257                    mNotificationStackScroller.getNotGoneChildCount(),
258                    getHeight(),
259                    mKeyguardStatusView.getHeight(),
260                    mEmptyDragAmount);
261            mClockPositionAlgorithm.run(mClockPositionResult);
262            if (animate || mClockAnimator != null) {
263                startClockAnimation(mClockPositionResult.clockY);
264            } else {
265                mKeyguardStatusView.setY(mClockPositionResult.clockY);
266            }
267            updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale);
268            stackScrollerPadding = mClockPositionResult.stackScrollerPadding;
269            mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment;
270        }
271        mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
272        requestScrollerTopPaddingUpdate(animate);
273    }
274
275    private void startClockAnimation(int y) {
276        if (mClockAnimationTarget == y) {
277            return;
278        }
279        mClockAnimationTarget = y;
280        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
281            @Override
282            public boolean onPreDraw() {
283                getViewTreeObserver().removeOnPreDrawListener(this);
284                if (mClockAnimator != null) {
285                    mClockAnimator.removeAllListeners();
286                    mClockAnimator.cancel();
287                }
288                mClockAnimator = ObjectAnimator
289                        .ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget);
290                mClockAnimator.setInterpolator(mFastOutSlowInInterpolator);
291                mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
292                mClockAnimator.addListener(new AnimatorListenerAdapter() {
293                    @Override
294                    public void onAnimationEnd(Animator animation) {
295                        mClockAnimator = null;
296                        mClockAnimationTarget = -1;
297                    }
298                });
299                mClockAnimator.start();
300                return true;
301            }
302        });
303    }
304
305    private void updateClock(float alpha, float scale) {
306        if (!mKeyguardStatusViewAnimating) {
307            mKeyguardStatusView.setAlpha(alpha);
308        }
309        mKeyguardStatusView.setScaleX(scale);
310        mKeyguardStatusView.setScaleY(scale);
311    }
312
313    public void animateToFullShade() {
314        mAnimateNextTopPaddingChange = true;
315        mNotificationStackScroller.goToFullShade();
316        requestLayout();
317    }
318
319    public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
320        mQsExpansionEnabled = qsExpansionEnabled;
321    }
322
323    @Override
324    public void resetViews() {
325        mIsLaunchTransitionFinished = false;
326        mBlockTouches = false;
327        mUnlockIconActive = false;
328        mAfforanceHelper.reset(true);
329        closeQs();
330        mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
331                true /* cancelAnimators */);
332    }
333
334    public void closeQs() {
335        cancelAnimation();
336        setQsExpansion(mQsMinExpansionHeight);
337    }
338
339    public void animateCloseQs() {
340        if (mQsExpansionAnimator != null) {
341            if (!mQsAnimatorExpand) {
342                return;
343            }
344            float height = mQsExpansionHeight;
345            mQsExpansionAnimator.cancel();
346            setQsExpansion(height);
347        }
348        flingSettings(0 /* vel */, false);
349    }
350
351    public void openQs() {
352        cancelAnimation();
353        if (mQsExpansionEnabled) {
354            setQsExpansion(mQsMaxExpansionHeight);
355        }
356    }
357
358    @Override
359    public void fling(float vel, boolean always) {
360        GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
361        if (gr != null) {
362            gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
363        }
364        super.fling(vel, always);
365    }
366
367    @Override
368    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
369        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
370            event.getText()
371                    .add(getContext().getString(R.string.accessibility_desc_notification_shade));
372            return true;
373        }
374
375        return super.dispatchPopulateAccessibilityEvent(event);
376    }
377
378    @Override
379    public boolean onInterceptTouchEvent(MotionEvent event) {
380        if (mBlockTouches) {
381            return false;
382        }
383        resetDownStates(event);
384        int pointerIndex = event.findPointerIndex(mTrackingPointer);
385        if (pointerIndex < 0) {
386            pointerIndex = 0;
387            mTrackingPointer = event.getPointerId(pointerIndex);
388        }
389        final float x = event.getX(pointerIndex);
390        final float y = event.getY(pointerIndex);
391
392        switch (event.getActionMasked()) {
393            case MotionEvent.ACTION_DOWN:
394                mIntercepting = true;
395                mInitialTouchY = y;
396                mInitialTouchX = x;
397                initVelocityTracker();
398                trackMovement(event);
399                if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
400                    getParent().requestDisallowInterceptTouchEvent(true);
401                }
402                if (mQsExpansionAnimator != null) {
403                    onQsExpansionStarted();
404                    mInitialHeightOnTouch = mQsExpansionHeight;
405                    mQsTracking = true;
406                    mIntercepting = false;
407                    mNotificationStackScroller.removeLongPressCallback();
408                }
409                break;
410            case MotionEvent.ACTION_POINTER_UP:
411                final int upPointer = event.getPointerId(event.getActionIndex());
412                if (mTrackingPointer == upPointer) {
413                    // gesture is ongoing, find a new pointer to track
414                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
415                    mTrackingPointer = event.getPointerId(newIndex);
416                    mInitialTouchX = event.getX(newIndex);
417                    mInitialTouchY = event.getY(newIndex);
418                }
419                break;
420
421            case MotionEvent.ACTION_MOVE:
422                final float h = y - mInitialTouchY;
423                trackMovement(event);
424                if (mQsTracking) {
425
426                    // Already tracking because onOverscrolled was called. We need to update here
427                    // so we don't stop for a frame until the next touch event gets handled in
428                    // onTouchEvent.
429                    setQsExpansion(h + mInitialHeightOnTouch);
430                    trackMovement(event);
431                    mIntercepting = false;
432                    return true;
433                }
434                if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
435                        && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
436                    onQsExpansionStarted();
437                    mInitialHeightOnTouch = mQsExpansionHeight;
438                    mInitialTouchY = y;
439                    mInitialTouchX = x;
440                    mQsTracking = true;
441                    mIntercepting = false;
442                    mNotificationStackScroller.removeLongPressCallback();
443                    return true;
444                }
445                break;
446
447            case MotionEvent.ACTION_CANCEL:
448            case MotionEvent.ACTION_UP:
449                trackMovement(event);
450                if (mQsTracking) {
451                    flingQsWithCurrentVelocity();
452                    mQsTracking = false;
453                }
454                mIntercepting = false;
455                break;
456        }
457        return !mQsExpanded && super.onInterceptTouchEvent(event);
458    }
459
460    private void resetDownStates(MotionEvent event) {
461        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
462            mOnlyAffordanceInThisMotion = false;
463        }
464    }
465
466    @Override
467    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
468
469        // Block request when interacting with the scroll view so we can still intercept the
470        // scrolling when QS is expanded.
471        if (mScrollView.isHandlingTouchEvent()) {
472            return;
473        }
474        super.requestDisallowInterceptTouchEvent(disallowIntercept);
475    }
476
477    private void flingQsWithCurrentVelocity() {
478        float vel = getCurrentVelocity();
479        flingSettings(vel, flingExpandsQs(vel));
480    }
481
482    private boolean flingExpandsQs(float vel) {
483        if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
484            return getQsExpansionFraction() > 0.5f;
485        } else {
486            return vel > 0;
487        }
488    }
489
490    private float getQsExpansionFraction() {
491        return (mQsExpansionHeight - mQsMinExpansionHeight)
492                / (getTempQsMaxExpansion() - mQsMinExpansionHeight);
493    }
494
495    @Override
496    public boolean onTouchEvent(MotionEvent event) {
497        if (mBlockTouches) {
498            return false;
499        }
500        resetDownStates(event);
501        if ((!mIsExpanding || mHintAnimationRunning)
502                && !mQsExpanded
503                && mStatusBar.getBarState() != StatusBarState.SHADE) {
504            mAfforanceHelper.onTouchEvent(event);
505        }
506        if (mOnlyAffordanceInThisMotion) {
507            return true;
508        }
509        if (event.getActionMasked() == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
510                && mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
511
512            // Down in the empty area while fully expanded - go to QS.
513            mQsTracking = true;
514            mConflictingQsExpansionGesture = true;
515            onQsExpansionStarted();
516            mInitialHeightOnTouch = mQsExpansionHeight;
517            mInitialTouchY = event.getX();
518            mInitialTouchX = event.getY();
519        }
520        if (mExpandedHeight != 0) {
521            handleQsDown(event);
522        }
523        if (!mTwoFingerQsExpand && (mQsTracking || mQsExpanded)) {
524            onQsTouch(event);
525            if (!mConflictingQsExpansionGesture) {
526                return true;
527            }
528        }
529        if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
530                || event.getActionMasked() == MotionEvent.ACTION_UP) {
531            mConflictingQsExpansionGesture = false;
532        }
533        if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mExpandedHeight == 0) {
534            mTwoFingerQsExpandPossible = true;
535        }
536        if (mTwoFingerQsExpandPossible && event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
537                && event.getPointerCount() == 2
538                && event.getY(event.getActionIndex()) < mStatusBarMinHeight) {
539            mTwoFingerQsExpand = true;
540            requestPanelHeightUpdate();
541        }
542        super.onTouchEvent(event);
543        return true;
544    }
545
546    private void handleQsDown(MotionEvent event) {
547        if (event.getActionMasked() == MotionEvent.ACTION_DOWN
548                && shouldQuickSettingsIntercept(event.getX(), event.getY(), 0)) {
549            mQsTracking = true;
550            onQsExpansionStarted();
551            mInitialHeightOnTouch = mQsExpansionHeight;
552            mInitialTouchY = event.getX();
553            mInitialTouchX = event.getY();
554        }
555    }
556
557    @Override
558    protected boolean flingExpands(float vel, float vectorVel) {
559        boolean expands = super.flingExpands(vel, vectorVel);
560
561        // If we are already running a QS expansion, make sure that we keep the panel open.
562        if (mQsExpansionAnimator != null) {
563            expands = true;
564        }
565        return expands;
566    }
567
568    @Override
569    protected boolean hasConflictingGestures() {
570        return mStatusBar.getBarState() != StatusBarState.SHADE;
571    }
572
573    private void onQsTouch(MotionEvent event) {
574        int pointerIndex = event.findPointerIndex(mTrackingPointer);
575        if (pointerIndex < 0) {
576            pointerIndex = 0;
577            mTrackingPointer = event.getPointerId(pointerIndex);
578        }
579        final float y = event.getY(pointerIndex);
580        final float x = event.getX(pointerIndex);
581
582        switch (event.getActionMasked()) {
583            case MotionEvent.ACTION_DOWN:
584                mQsTracking = true;
585                mInitialTouchY = y;
586                mInitialTouchX = x;
587                onQsExpansionStarted();
588                mInitialHeightOnTouch = mQsExpansionHeight;
589                initVelocityTracker();
590                trackMovement(event);
591                break;
592
593            case MotionEvent.ACTION_POINTER_UP:
594                final int upPointer = event.getPointerId(event.getActionIndex());
595                if (mTrackingPointer == upPointer) {
596                    // gesture is ongoing, find a new pointer to track
597                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
598                    final float newY = event.getY(newIndex);
599                    final float newX = event.getX(newIndex);
600                    mTrackingPointer = event.getPointerId(newIndex);
601                    mInitialHeightOnTouch = mQsExpansionHeight;
602                    mInitialTouchY = newY;
603                    mInitialTouchX = newX;
604                }
605                break;
606
607            case MotionEvent.ACTION_MOVE:
608                final float h = y - mInitialTouchY;
609                setQsExpansion(h + mInitialHeightOnTouch);
610                trackMovement(event);
611                break;
612
613            case MotionEvent.ACTION_UP:
614            case MotionEvent.ACTION_CANCEL:
615                mQsTracking = false;
616                mTrackingPointer = -1;
617                trackMovement(event);
618                float fraction = getQsExpansionFraction();
619                if ((fraction != 0f || y >= mInitialTouchY)
620                        && (fraction != 1f || y <= mInitialTouchY)) {
621                    flingQsWithCurrentVelocity();
622                } else {
623                    mScrollYOverride = -1;
624                }
625                if (mVelocityTracker != null) {
626                    mVelocityTracker.recycle();
627                    mVelocityTracker = null;
628                }
629                break;
630        }
631    }
632
633    @Override
634    public void onOverscrolled(int amount) {
635        if (mIntercepting) {
636            onQsExpansionStarted(amount);
637            mInitialHeightOnTouch = mQsExpansionHeight;
638            mInitialTouchY = mLastTouchY;
639            mInitialTouchX = mLastTouchX;
640            mQsTracking = true;
641        }
642    }
643
644    @Override
645    public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
646        cancelAnimation();
647        float rounded = amount >= 1f ? amount : 0f;
648        mStackScrollerOverscrolling = rounded != 0f && isRubberbanded;
649        mQsExpansionFromOverscroll = rounded != 0f;
650        updateQsState();
651        setQsExpansion(mQsMinExpansionHeight + rounded);
652    }
653
654    @Override
655    public void flingTopOverscroll(float velocity, boolean open) {
656        setQsExpansion(mQsExpansionHeight);
657        flingSettings(velocity, open, new Runnable() {
658            @Override
659            public void run() {
660                mStackScrollerOverscrolling = false;
661                mQsExpansionFromOverscroll = false;
662                updateQsState();
663            }
664        });
665    }
666
667    private void onQsExpansionStarted() {
668        onQsExpansionStarted(0);
669    }
670
671    private void onQsExpansionStarted(int overscrollAmount) {
672        cancelAnimation();
673
674        // Reset scroll position and apply that position to the expanded height.
675        float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount;
676        if (mScrollView.getScrollY() != 0) {
677            mScrollYOverride = mScrollView.getScrollY();
678        }
679        mScrollView.scrollTo(0, 0);
680        setQsExpansion(height);
681    }
682
683    private void setQsExpanded(boolean expanded) {
684        boolean changed = mQsExpanded != expanded;
685        if (changed) {
686            mQsExpanded = expanded;
687            updateQsState();
688            requestPanelHeightUpdate();
689            mNotificationStackScroller.setInterceptDelegateEnabled(expanded);
690            mStatusBar.setQsExpanded(expanded);
691        }
692    }
693
694    public void setBarState(int statusBarState, boolean keyguardFadingAway) {
695        boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD
696                || statusBarState == StatusBarState.SHADE_LOCKED;
697        mKeyguardStatusBar.setAlpha(1f);
698        mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
699        if (!mKeyguardShowing && keyguardShowing) {
700            setQsTranslation(mQsExpansionHeight);
701            mHeader.setTranslationY(0f);
702        }
703        setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway);
704        mStatusBarState = statusBarState;
705        mKeyguardShowing = keyguardShowing;
706        updateQsState();
707    }
708
709    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() {
710        @Override
711        public void run() {
712            mKeyguardStatusViewAnimating = false;
713            mKeyguardStatusView.setVisibility(View.GONE);
714        }
715    };
716
717    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() {
718        @Override
719        public void run() {
720            mKeyguardStatusViewAnimating = false;
721        }
722    };
723
724    private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway) {
725        if (!keyguardFadingAway && mStatusBarState == StatusBarState.KEYGUARD
726                && statusBarState != StatusBarState.KEYGUARD) {
727            mKeyguardStatusView.animate().cancel();
728            mKeyguardStatusViewAnimating = true;
729            mKeyguardStatusView.animate()
730                    .alpha(0f)
731                    .setDuration(160)
732                    .setInterpolator(PhoneStatusBar.ALPHA_OUT)
733                    .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable);
734        } else if (mStatusBarState == StatusBarState.SHADE_LOCKED
735                && statusBarState == StatusBarState.KEYGUARD) {
736            mKeyguardStatusView.animate().cancel();
737            mKeyguardStatusView.setVisibility(View.VISIBLE);
738            mKeyguardStatusViewAnimating = true;
739            mKeyguardStatusView.setAlpha(0f);
740            mKeyguardStatusView.animate()
741                    .alpha(1f)
742                    .setDuration(320)
743                    .setInterpolator(PhoneStatusBar.ALPHA_IN)
744                    .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
745        } else if (statusBarState == StatusBarState.KEYGUARD) {
746            mKeyguardStatusView.animate().cancel();
747            mKeyguardStatusView.setVisibility(View.VISIBLE);
748            mKeyguardStatusView.setAlpha(1f);
749        } else {
750            mKeyguardStatusView.animate().cancel();
751            mKeyguardStatusView.setVisibility(View.GONE);
752            mKeyguardStatusView.setAlpha(1f);
753        }
754    }
755
756    private void updateQsState() {
757        boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling;
758        mHeader.setVisibility((mQsExpanded || !mKeyguardShowing) ? View.VISIBLE : View.INVISIBLE);
759        mHeader.setExpanded(mKeyguardShowing || (mQsExpanded && !mStackScrollerOverscrolling));
760        mNotificationStackScroller.setScrollingEnabled(
761                mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded
762                        || mQsExpansionFromOverscroll));
763        mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
764        mQsContainer.setVisibility(
765                mKeyguardShowing && !expandVisually ? View.INVISIBLE : View.VISIBLE);
766        mScrollView.setTouchEnabled(mQsExpanded);
767    }
768
769    private void setQsExpansion(float height) {
770        height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
771        mQsFullyExpanded = height == mQsMaxExpansionHeight;
772        if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) {
773            setQsExpanded(true);
774        } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
775            setQsExpanded(false);
776        }
777        mQsExpansionHeight = height;
778        mHeader.setExpansion(getHeaderExpansionFraction());
779        setQsTranslation(height);
780        requestScrollerTopPaddingUpdate(false /* animate */);
781        updateNotificationScrim(height);
782        if (mKeyguardShowing) {
783            float alpha = getQsExpansionFraction();
784            alpha *= 2;
785            alpha = Math.min(1, alpha);
786            alpha = 1 - alpha;
787            if (alpha == 0f) {
788                mKeyguardStatusBar.setVisibility(View.INVISIBLE);
789            } else {
790                mKeyguardStatusBar.setVisibility(View.VISIBLE);
791                mKeyguardStatusBar.setAlpha(alpha);
792            }
793        }
794    }
795
796    private void updateNotificationScrim(float height) {
797        int startDistance = mQsMinExpansionHeight + mNotificationScrimWaitDistance;
798        float progress = (height - startDistance) / (mQsMaxExpansionHeight - startDistance);
799        progress = Math.max(0.0f, Math.min(progress, 1.0f));
800        mNotificationStackScroller.setScrimAlpha(progress);
801    }
802
803    private float getHeaderExpansionFraction() {
804        if (!mKeyguardShowing) {
805            return getQsExpansionFraction();
806        } else {
807            return 1f;
808        }
809    }
810
811    private void setQsTranslation(float height) {
812        mQsContainer.setY(height - mQsContainer.getHeight() + getHeaderTranslation());
813        if (mKeyguardShowing) {
814            mHeader.setY(interpolate(getQsExpansionFraction(), -mHeader.getHeight(), 0));
815        }
816    }
817
818    private float calculateQsTopPadding() {
819        if (mKeyguardShowing) {
820            return interpolate(getQsExpansionFraction(),
821                    mNotificationStackScroller.getIntrinsicPadding() - mNotificationTopPadding,
822                    mQsMaxExpansionHeight);
823        } else {
824            return mQsExpansionHeight;
825        }
826    }
827
828    private void requestScrollerTopPaddingUpdate(boolean animate) {
829        mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(),
830                mScrollView.getScrollY(),
831                mAnimateNextTopPaddingChange || animate);
832        mAnimateNextTopPaddingChange = false;
833    }
834
835    private void trackMovement(MotionEvent event) {
836        if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
837        mLastTouchX = event.getX();
838        mLastTouchY = event.getY();
839    }
840
841    private void initVelocityTracker() {
842        if (mVelocityTracker != null) {
843            mVelocityTracker.recycle();
844        }
845        mVelocityTracker = VelocityTracker.obtain();
846    }
847
848    private float getCurrentVelocity() {
849        if (mVelocityTracker == null) {
850            return 0;
851        }
852        mVelocityTracker.computeCurrentVelocity(1000);
853        return mVelocityTracker.getYVelocity();
854    }
855
856    private void cancelAnimation() {
857        if (mQsExpansionAnimator != null) {
858            mQsExpansionAnimator.cancel();
859        }
860    }
861
862    private void flingSettings(float vel, boolean expand) {
863        flingSettings(vel, expand, null);
864    }
865
866    private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable) {
867        float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
868        if (target == mQsExpansionHeight) {
869            mScrollYOverride = -1;
870            if (onFinishRunnable != null) {
871                onFinishRunnable.run();
872            }
873            return;
874        }
875        ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
876        mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
877        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
878            @Override
879            public void onAnimationUpdate(ValueAnimator animation) {
880                setQsExpansion((Float) animation.getAnimatedValue());
881            }
882        });
883        animator.addListener(new AnimatorListenerAdapter() {
884            @Override
885            public void onAnimationEnd(Animator animation) {
886                mScrollYOverride = -1;
887                mQsExpansionAnimator = null;
888                if (onFinishRunnable != null) {
889                    onFinishRunnable.run();
890                }
891            }
892        });
893        animator.start();
894        mQsExpansionAnimator = animator;
895        mQsAnimatorExpand = expand;
896    }
897
898    /**
899     * @return Whether we should intercept a gesture to open Quick Settings.
900     */
901    private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
902        if (!mQsExpansionEnabled) {
903            return false;
904        }
905        View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader;
906        boolean onHeader = x >= header.getLeft() && x <= header.getRight()
907                && y >= header.getTop() && y <= header.getBottom();
908        if (mQsExpanded) {
909            return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0);
910        } else {
911            return onHeader;
912        }
913    }
914
915    @Override
916    public void setVisibility(int visibility) {
917        int oldVisibility = getVisibility();
918        super.setVisibility(visibility);
919        if (visibility != oldVisibility) {
920            reparentStatusIcons(visibility == VISIBLE);
921        }
922    }
923
924    /**
925     * When the notification panel gets expanded, we need to move the status icons in the header
926     * card.
927     */
928    private void reparentStatusIcons(boolean toHeader) {
929        if (mStatusBar == null) {
930            return;
931        }
932        LinearLayout systemIcons = mStatusBar.getSystemIcons();
933        ViewGroup parent = ((ViewGroup) systemIcons.getParent());
934        if (toHeader) {
935            int index = parent.indexOfChild(systemIcons);
936            parent.removeView(systemIcons);
937            mSystemIconsCopy.setMirroredView(
938                    systemIcons, systemIcons.getWidth(), systemIcons.getHeight());
939            parent.addView(mSystemIconsCopy, index);
940            mHeader.attachSystemIcons(systemIcons);
941        } else {
942            ViewGroup newParent = mStatusBar.getSystemIconArea();
943            int index = newParent.indexOfChild(mSystemIconsCopy);
944            parent.removeView(systemIcons);
945            mHeader.onSystemIconsDetached();
946            mSystemIconsCopy.setMirroredView(null, 0, 0);
947            newParent.removeView(mSystemIconsCopy);
948            newParent.addView(systemIcons, index);
949        }
950    }
951
952    @Override
953    protected boolean isScrolledToBottom() {
954        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
955            return true;
956        }
957        if (!isInSettings()) {
958            return mNotificationStackScroller.isScrolledToBottom();
959        }
960        return super.isScrolledToBottom();
961    }
962
963    @Override
964    protected int getMaxPanelHeight() {
965        int min = mStatusBarMinHeight;
966        if (mStatusBar.getBarState() != StatusBarState.KEYGUARD
967                && mNotificationStackScroller.getNotGoneChildCount() == 0) {
968            int minHeight = (int) ((mQsMinExpansionHeight + getOverExpansionAmount())
969                    * HEADER_RUBBERBAND_FACTOR);
970            min = Math.max(min, minHeight);
971        }
972        int maxHeight;
973        if (mTwoFingerQsExpand || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) {
974            maxHeight = Math.max(calculatePanelHeightQsExpanded(), calculatePanelHeightShade());
975        } else {
976            maxHeight = calculatePanelHeightShade();
977        }
978        maxHeight = Math.max(maxHeight, min);
979        return maxHeight;
980    }
981
982    private boolean isInSettings() {
983        return mQsExpanded;
984    }
985
986    @Override
987    protected void onHeightUpdated(float expandedHeight) {
988        if (!mQsExpanded) {
989            positionClockAndNotifications();
990        }
991        if (mTwoFingerQsExpand || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
992                && !mQsExpansionFromOverscroll) {
993            float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
994                    + mNotificationStackScroller.getMinStackHeight()
995                    + mNotificationStackScroller.getNotificationTopPadding();
996            float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
997            float t = (expandedHeight - panelHeightQsCollapsed)
998                    / (panelHeightQsExpanded - panelHeightQsCollapsed);
999
1000            setQsExpansion(mQsMinExpansionHeight
1001                    + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
1002        }
1003        mNotificationStackScroller.setStackHeight(expandedHeight);
1004        updateHeader();
1005        updateUnlockIcon();
1006        updateNotificationTranslucency();
1007    }
1008
1009    /**
1010     * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when
1011     *         collapsing QS / the panel when QS was scrolled
1012     */
1013    private int getTempQsMaxExpansion() {
1014        int qsTempMaxExpansion = mQsMaxExpansionHeight;
1015        if (mScrollYOverride != -1) {
1016            qsTempMaxExpansion -= mScrollYOverride;
1017        }
1018        return qsTempMaxExpansion;
1019    }
1020
1021    private int calculatePanelHeightShade() {
1022        int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
1023        int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
1024                - mTopPaddingAdjustment;
1025        maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
1026        return maxHeight;
1027    }
1028
1029    private int calculatePanelHeightQsExpanded() {
1030        float notificationHeight = mNotificationStackScroller.getHeight()
1031                - mNotificationStackScroller.getEmptyBottomMargin()
1032                - mNotificationStackScroller.getTopPadding();
1033        float totalHeight = mQsMaxExpansionHeight + notificationHeight
1034                + mNotificationStackScroller.getNotificationTopPadding();
1035        if (totalHeight > mNotificationStackScroller.getHeight()) {
1036            float fullyCollapsedHeight = mQsMaxExpansionHeight
1037                    + mNotificationStackScroller.getMinStackHeight()
1038                    + mNotificationStackScroller.getNotificationTopPadding()
1039                    - getScrollViewScrollY();
1040            totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
1041        }
1042        return (int) totalHeight;
1043    }
1044
1045    private int getScrollViewScrollY() {
1046        if (mScrollYOverride != -1) {
1047            return mScrollYOverride;
1048        } else {
1049            return mScrollView.getScrollY();
1050        }
1051    }
1052    private void updateNotificationTranslucency() {
1053        float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight())
1054                / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize()
1055                        + mNotificationStackScroller.getCollapseSecondCardPadding());
1056        alpha = Math.max(0, Math.min(alpha, 1));
1057        alpha = (float) Math.pow(alpha, 0.75);
1058        if (alpha != 1f && mNotificationStackScroller.getLayerType() != LAYER_TYPE_HARDWARE) {
1059            mNotificationStackScroller.setLayerType(LAYER_TYPE_HARDWARE, null);
1060        } else if (alpha == 1f
1061                && mNotificationStackScroller.getLayerType() == LAYER_TYPE_HARDWARE) {
1062            mNotificationStackScroller.setLayerType(LAYER_TYPE_NONE, null);
1063        }
1064        mNotificationStackScroller.setAlpha(alpha);
1065    }
1066
1067    @Override
1068    protected float getOverExpansionAmount() {
1069        return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
1070    }
1071
1072    @Override
1073    protected float getOverExpansionPixels() {
1074        return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
1075    }
1076
1077    private void updateUnlockIcon() {
1078        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1079                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1080            boolean active = getMaxPanelHeight() - getExpandedHeight() > mUnlockMoveDistance;
1081            KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon();
1082            if (active && !mUnlockIconActive && mTracking) {
1083                lockIcon.setImageAlpha(1.0f, true, 150, mFastOutLinearInterpolator, null);
1084                lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150,
1085                        mFastOutLinearInterpolator);
1086            } else if (!active && mUnlockIconActive && mTracking) {
1087                lockIcon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT, true,
1088                        150, mFastOutLinearInterpolator, null);
1089                lockIcon.setImageScale(1.0f, true, 150,
1090                        mFastOutLinearInterpolator);
1091            }
1092            mUnlockIconActive = active;
1093        }
1094    }
1095
1096    /**
1097     * Hides the header when notifications are colliding with it.
1098     */
1099    private void updateHeader() {
1100        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1101                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1102            updateHeaderKeyguard();
1103        } else {
1104            updateHeaderShade();
1105        }
1106
1107    }
1108
1109    private void updateHeaderShade() {
1110        mHeader.setTranslationY(getHeaderTranslation());
1111        setQsTranslation(mQsExpansionHeight);
1112    }
1113
1114    private float getHeaderTranslation() {
1115        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1116                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1117            return 0;
1118        }
1119        if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
1120            if (mExpandedHeight / HEADER_RUBBERBAND_FACTOR >= mQsMinExpansionHeight) {
1121                return 0;
1122            } else {
1123                return mExpandedHeight / HEADER_RUBBERBAND_FACTOR - mQsMinExpansionHeight;
1124            }
1125        }
1126        return Math.min(0, mNotificationStackScroller.getTranslationY()) / HEADER_RUBBERBAND_FACTOR;
1127    }
1128
1129    private void updateHeaderKeyguard() {
1130        float alpha;
1131        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
1132
1133            // When on Keyguard, we hide the header as soon as the top card of the notification
1134            // stack scroller is close enough (collision distance) to the bottom of the header.
1135            alpha = getNotificationsTopY()
1136                    /
1137                    (mQsMinExpansionHeight + mNotificationsHeaderCollideDistance);
1138
1139        } else {
1140
1141            // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
1142            // soon as we start translating the stack.
1143            alpha = getNotificationsTopY() / mQsMinExpansionHeight;
1144        }
1145        alpha = Math.max(0, Math.min(alpha, 1));
1146        alpha = (float) Math.pow(alpha, 0.75);
1147        if (!mQsExpanded) {
1148            mKeyguardStatusBar.setAlpha(alpha);
1149        }
1150        mKeyguardBottomArea.setAlpha(alpha);
1151        setQsTranslation(mQsExpansionHeight);
1152    }
1153
1154    private float getNotificationsTopY() {
1155        if (mNotificationStackScroller.getNotGoneChildCount() == 0) {
1156            return getExpandedHeight();
1157        }
1158        return mNotificationStackScroller.getNotificationsTopY();
1159    }
1160
1161    @Override
1162    protected void onExpandingStarted() {
1163        super.onExpandingStarted();
1164        mNotificationStackScroller.onExpansionStarted();
1165        mIsExpanding = true;
1166        mQsExpandedWhenExpandingStarted = mQsExpanded;
1167        if (mQsExpanded) {
1168            onQsExpansionStarted();
1169        }
1170    }
1171
1172    @Override
1173    protected void onExpandingFinished() {
1174        super.onExpandingFinished();
1175        mNotificationStackScroller.onExpansionStopped();
1176        mIsExpanding = false;
1177        mScrollYOverride = -1;
1178        if (mExpandedHeight == 0f) {
1179            setListening(false);
1180        } else {
1181            setListening(true);
1182        }
1183        mTwoFingerQsExpand = false;
1184        mTwoFingerQsExpandPossible = false;
1185    }
1186
1187    private void setListening(boolean listening) {
1188        mHeader.setListening(listening);
1189        mKeyguardStatusBar.setListening(listening);
1190        mQsPanel.setListening(listening);
1191    }
1192
1193    @Override
1194    public void instantExpand() {
1195        super.instantExpand();
1196        setListening(true);
1197    }
1198
1199    @Override
1200    protected void setOverExpansion(float overExpansion, boolean isPixels) {
1201        if (mConflictingQsExpansionGesture || mTwoFingerQsExpand) {
1202            return;
1203        }
1204        if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
1205            mNotificationStackScroller.setOnHeightChangedListener(null);
1206            if (isPixels) {
1207                mNotificationStackScroller.setOverScrolledPixels(
1208                        overExpansion, true /* onTop */, false /* animate */);
1209            } else {
1210                mNotificationStackScroller.setOverScrollAmount(
1211                        overExpansion, true /* onTop */, false /* animate */);
1212            }
1213            mNotificationStackScroller.setOnHeightChangedListener(this);
1214        }
1215    }
1216
1217    @Override
1218    protected void onTrackingStarted() {
1219        super.onTrackingStarted();
1220        if (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1221                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) {
1222            mAfforanceHelper.animateHideLeftRightIcon();
1223        }
1224    }
1225
1226    @Override
1227    protected void onTrackingStopped(boolean expand) {
1228        super.onTrackingStopped(expand);
1229        if (expand) {
1230            mNotificationStackScroller.setOverScrolledPixels(
1231                    0.0f, true /* onTop */, true /* animate */);
1232        }
1233        if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1234                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
1235            if (!mHintAnimationRunning) {
1236                mAfforanceHelper.reset(true);
1237            }
1238        }
1239        if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD
1240                || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) {
1241            KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon();
1242            lockIcon.setImageAlpha(0.0f, true, 100, mFastOutLinearInterpolator, null);
1243            lockIcon.setImageScale(2.0f, true, 100, mFastOutLinearInterpolator);
1244        }
1245    }
1246
1247    @Override
1248    public void onHeightChanged(ExpandableView view) {
1249
1250        // Block update if we are in quick settings and just the top padding changed
1251        // (i.e. view == null).
1252        if (view == null && mQsExpanded) {
1253            return;
1254        }
1255        requestPanelHeightUpdate();
1256    }
1257
1258    @Override
1259    public void onScrollChanged() {
1260        if (mQsExpanded) {
1261            requestScrollerTopPaddingUpdate(false /* animate */);
1262            requestPanelHeightUpdate();
1263        }
1264    }
1265
1266    @Override
1267    protected void onConfigurationChanged(Configuration newConfig) {
1268        super.onConfigurationChanged(newConfig);
1269        mAfforanceHelper.onConfigurationChanged();
1270    }
1271
1272    @Override
1273    public void onClick(View v) {
1274        if (v == mHeader) {
1275            onQsExpansionStarted();
1276            if (mQsExpanded) {
1277                flingSettings(0 /* vel */, false /* expand */);
1278            } else if (mQsExpansionEnabled) {
1279                flingSettings(0 /* vel */, true /* expand */);
1280            }
1281        }
1282    }
1283
1284    @Override
1285    public void onAnimationToSideStarted(boolean rightPage) {
1286        boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage;
1287        mIsLaunchTransitionRunning = true;
1288        mLaunchAnimationEndRunnable = null;
1289        if (start) {
1290            mKeyguardBottomArea.launchPhone();
1291        } else {
1292            mSecureCameraLaunchManager.startSecureCameraLaunch();
1293        }
1294        mBlockTouches = true;
1295    }
1296
1297    @Override
1298    public void onAnimationToSideEnded() {
1299        mIsLaunchTransitionRunning = false;
1300        mIsLaunchTransitionFinished = true;
1301        if (mLaunchAnimationEndRunnable != null) {
1302            mLaunchAnimationEndRunnable.run();
1303            mLaunchAnimationEndRunnable = null;
1304        }
1305    }
1306
1307    @Override
1308    protected void onEdgeClicked(boolean right) {
1309        if ((right && getRightIcon().getVisibility() != View.VISIBLE)
1310                || (!right && getLeftIcon().getVisibility() != View.VISIBLE)) {
1311            return;
1312        }
1313        mHintAnimationRunning = true;
1314        mAfforanceHelper.startHintAnimation(right, new Runnable() {
1315            @Override
1316            public void run() {
1317                mHintAnimationRunning = false;
1318                mStatusBar.onHintFinished();
1319            }
1320        });
1321        boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? right : !right;
1322        if (start) {
1323            mStatusBar.onPhoneHintStarted();
1324        } else {
1325            mStatusBar.onCameraHintStarted();
1326        }
1327    }
1328
1329    @Override
1330    protected void startUnlockHintAnimation() {
1331        super.startUnlockHintAnimation();
1332        startHighlightIconAnimation(getCenterIcon());
1333    }
1334
1335    /**
1336     * Starts the highlight (making it fully opaque) animation on an icon.
1337     */
1338    private void startHighlightIconAnimation(final KeyguardAffordanceView icon) {
1339        icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
1340                mFastOutSlowInInterpolator, new Runnable() {
1341                    @Override
1342                    public void run() {
1343                        icon.setImageAlpha(KeyguardAffordanceHelper.SWIPE_RESTING_ALPHA_AMOUNT,
1344                                true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION,
1345                                mFastOutSlowInInterpolator, null);
1346                    }
1347                });
1348    }
1349
1350    @Override
1351    public float getPageWidth() {
1352        return getWidth();
1353    }
1354
1355    @Override
1356    public void onSwipingStarted() {
1357        mSecureCameraLaunchManager.onSwipingStarted();
1358        requestDisallowInterceptTouchEvent(true);
1359        mOnlyAffordanceInThisMotion = true;
1360    }
1361
1362    @Override
1363    public KeyguardAffordanceView getLeftIcon() {
1364        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1365                ? mKeyguardBottomArea.getCameraView()
1366                : mKeyguardBottomArea.getPhoneView();
1367    }
1368
1369    @Override
1370    public KeyguardAffordanceView getCenterIcon() {
1371        return mKeyguardBottomArea.getLockIcon();
1372    }
1373
1374    @Override
1375    public KeyguardAffordanceView getRightIcon() {
1376        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1377                ? mKeyguardBottomArea.getPhoneView()
1378                : mKeyguardBottomArea.getCameraView();
1379    }
1380
1381    @Override
1382    public View getLeftPreview() {
1383        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1384                ? mKeyguardBottomArea.getCameraPreview()
1385                : mKeyguardBottomArea.getPhonePreview();
1386    }
1387
1388    @Override
1389    public View getRightPreview() {
1390        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
1391                ? mKeyguardBottomArea.getPhonePreview()
1392                : mKeyguardBottomArea.getCameraPreview();
1393    }
1394
1395    @Override
1396    protected float getPeekHeight() {
1397        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
1398            return mNotificationStackScroller.getPeekHeight();
1399        } else {
1400            return mQsMinExpansionHeight * HEADER_RUBBERBAND_FACTOR;
1401        }
1402    }
1403
1404    @Override
1405    protected float getCannedFlingDurationFactor() {
1406        if (mQsExpanded) {
1407            return 0.7f;
1408        } else {
1409            return 0.6f;
1410        }
1411    }
1412
1413    @Override
1414    protected boolean fullyExpandedClearAllVisible() {
1415        return mNotificationStackScroller.isDismissViewNotGone()
1416                && mNotificationStackScroller.isScrolledToBottom();
1417    }
1418
1419    @Override
1420    protected boolean isClearAllVisible() {
1421        return mNotificationStackScroller.isDismissViewVisible();
1422    }
1423
1424    @Override
1425    protected int getClearAllHeight() {
1426        return mNotificationStackScroller.getDismissViewHeight();
1427    }
1428
1429    @Override
1430    protected boolean isTrackingBlocked() {
1431        return mConflictingQsExpansionGesture && mQsExpanded;
1432    }
1433
1434    public void notifyVisibleChildrenChanged() {
1435        if (mNotificationStackScroller.getNotGoneChildCount() != 0) {
1436            mReserveNotificationSpace.setVisibility(View.VISIBLE);
1437        } else {
1438            mReserveNotificationSpace.setVisibility(View.GONE);
1439        }
1440    }
1441
1442    public boolean isQsExpanded() {
1443        return mQsExpanded;
1444    }
1445
1446    public boolean isQsDetailShowing() {
1447        return mQsPanel.isShowingDetail();
1448    }
1449
1450    public void closeQsDetail() {
1451        mQsPanel.closeDetail();
1452    }
1453
1454    @Override
1455    public boolean shouldDelayChildPressedState() {
1456        return true;
1457    }
1458
1459    public boolean isLaunchTransitionFinished() {
1460        return mIsLaunchTransitionFinished;
1461    }
1462
1463    public boolean isLaunchTransitionRunning() {
1464        return mIsLaunchTransitionRunning;
1465    }
1466
1467    public void setLaunchTransitionEndRunnable(Runnable r) {
1468        mLaunchAnimationEndRunnable = r;
1469    }
1470
1471    public void setEmptyDragAmount(float amount) {
1472        float factor = 1f;
1473        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
1474            factor = 0.6f;
1475        } else if (!mStatusBar.hasActiveNotifications()) {
1476            factor = 0.4f;
1477        }
1478        mEmptyDragAmount = amount * factor;
1479        positionClockAndNotifications();
1480    }
1481
1482    private static float interpolate(float t, float start, float end) {
1483        return (1 - t) * start + t * end;
1484    }
1485}
1486