PanelView.java revision f99d0007646781b99a63bc0d2103a45f0f72e724
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.content.res.Resources;
26import android.util.AttributeSet;
27import android.util.Log;
28import android.view.MotionEvent;
29import android.view.ViewConfiguration;
30import android.view.ViewTreeObserver;
31import android.view.animation.AnimationUtils;
32import android.view.animation.Interpolator;
33import android.widget.FrameLayout;
34
35import com.android.systemui.R;
36import com.android.systemui.statusbar.FlingAnimationUtils;
37import com.android.systemui.statusbar.StatusBarState;
38
39import java.io.FileDescriptor;
40import java.io.PrintWriter;
41
42public abstract class PanelView extends FrameLayout {
43    public static final boolean DEBUG = PanelBar.DEBUG;
44    public static final String TAG = PanelView.class.getSimpleName();
45
46    private final void logf(String fmt, Object... args) {
47        Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
48    }
49
50    protected PhoneStatusBar mStatusBar;
51    private float mPeekHeight;
52    private float mHintDistance;
53    private int mEdgeTapAreaWidth;
54    private float mInitialOffsetOnTouch;
55    private float mExpandedFraction = 0;
56    protected float mExpandedHeight = 0;
57    private boolean mJustPeeked;
58    private boolean mClosing;
59    protected boolean mTracking;
60    private boolean mTouchSlopExceeded;
61    private int mTrackingPointer;
62    protected int mTouchSlop;
63    protected boolean mHintAnimationRunning;
64    private boolean mOverExpandedBeforeFling;
65    private float mOriginalIndicationY;
66
67    private ValueAnimator mHeightAnimator;
68    private ObjectAnimator mPeekAnimator;
69    private VelocityTrackerInterface mVelocityTracker;
70    private FlingAnimationUtils mFlingAnimationUtils;
71
72    /**
73     * Whether an instant expand request is currently pending and we are just waiting for layout.
74     */
75    private boolean mInstantExpanding;
76
77    PanelBar mBar;
78
79    protected int mMaxPanelHeight = -1;
80    private String mViewName;
81    private float mInitialTouchY;
82    private float mInitialTouchX;
83
84    private Interpolator mLinearOutSlowInInterpolator;
85    private Interpolator mBounceInterpolator;
86    protected KeyguardBottomAreaView mKeyguardBottomArea;
87
88    protected void onExpandingFinished() {
89        mBar.onExpandingFinished();
90    }
91
92    protected void onExpandingStarted() {
93    }
94
95    private void runPeekAnimation() {
96        if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
97        if (mHeightAnimator != null) {
98            return;
99        }
100        if (mPeekAnimator == null) {
101            mPeekAnimator = ObjectAnimator.ofFloat(this,
102                    "expandedHeight", mPeekHeight)
103                .setDuration(250);
104        }
105        mPeekAnimator.start();
106    }
107
108    public PanelView(Context context, AttributeSet attrs) {
109        super(context, attrs);
110        mFlingAnimationUtils = new FlingAnimationUtils(context, 0.6f);
111        mLinearOutSlowInInterpolator =
112                AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
113        mBounceInterpolator = new BounceInterpolator();
114    }
115
116    protected void loadDimens() {
117        final Resources res = getContext().getResources();
118        mPeekHeight = res.getDimension(R.dimen.peek_height)
119            + getPaddingBottom(); // our window might have a dropshadow
120
121        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
122        mTouchSlop = configuration.getScaledTouchSlop();
123        mHintDistance = res.getDimension(R.dimen.hint_move_distance);
124        mEdgeTapAreaWidth = res.getDimensionPixelSize(R.dimen.edge_tap_area_width);
125    }
126
127    private void trackMovement(MotionEvent event) {
128        // Add movement to velocity tracker using raw screen X and Y coordinates instead
129        // of window coordinates because the window frame may be moving at the same time.
130        float deltaX = event.getRawX() - event.getX();
131        float deltaY = event.getRawY() - event.getY();
132        event.offsetLocation(deltaX, deltaY);
133        if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
134        event.offsetLocation(-deltaX, -deltaY);
135    }
136
137    @Override
138    public boolean onTouchEvent(MotionEvent event) {
139        if (mInstantExpanding) {
140            return false;
141        }
142
143        /*
144         * We capture touch events here and update the expand height here in case according to
145         * the users fingers. This also handles multi-touch.
146         *
147         * If the user just clicks shortly, we give him a quick peek of the shade.
148         *
149         * Flinging is also enabled in order to open or close the shade.
150         */
151
152        int pointerIndex = event.findPointerIndex(mTrackingPointer);
153        if (pointerIndex < 0) {
154            pointerIndex = 0;
155            mTrackingPointer = event.getPointerId(pointerIndex);
156        }
157        final float y = event.getY(pointerIndex);
158        final float x = event.getX(pointerIndex);
159
160        boolean waitForTouchSlop = hasConflictingGestures();
161
162        switch (event.getActionMasked()) {
163            case MotionEvent.ACTION_DOWN:
164                mInitialTouchY = y;
165                mInitialTouchX = x;
166                mInitialOffsetOnTouch = mExpandedHeight;
167                mTouchSlopExceeded = false;
168                if (mVelocityTracker == null) {
169                    initVelocityTracker();
170                }
171                trackMovement(event);
172                if (!waitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning)) {
173                    if (mHeightAnimator != null) {
174                        mHeightAnimator.cancel(); // end any outstanding animations
175                    }
176                    mTouchSlopExceeded = (mHeightAnimator != null && !mHintAnimationRunning);
177                    onTrackingStarted();
178                }
179                if (mExpandedHeight == 0) {
180                    mJustPeeked = true;
181                    runPeekAnimation();
182                }
183                break;
184
185            case MotionEvent.ACTION_POINTER_UP:
186                final int upPointer = event.getPointerId(event.getActionIndex());
187                if (mTrackingPointer == upPointer) {
188                    // gesture is ongoing, find a new pointer to track
189                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
190                    final float newY = event.getY(newIndex);
191                    final float newX = event.getX(newIndex);
192                    mTrackingPointer = event.getPointerId(newIndex);
193                    mInitialOffsetOnTouch = mExpandedHeight;
194                    mInitialTouchY = newY;
195                    mInitialTouchX = newX;
196                }
197                break;
198
199            case MotionEvent.ACTION_MOVE:
200                float h = y - mInitialTouchY;
201                if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)) {
202                    mTouchSlopExceeded = true;
203                    if (waitForTouchSlop && !mTracking) {
204                        mInitialOffsetOnTouch = mExpandedHeight;
205                        mInitialTouchX = x;
206                        mInitialTouchY = y;
207                        if (mHeightAnimator != null) {
208                            mHeightAnimator.cancel(); // end any outstanding animations
209                        }
210                        onTrackingStarted();
211                        h = 0;
212                    }
213                }
214                final float newHeight = h + mInitialOffsetOnTouch;
215                if (newHeight > mPeekHeight) {
216                    if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
217                        mPeekAnimator.cancel();
218                    }
219                    mJustPeeked = false;
220                }
221                if (!mJustPeeked && (!waitForTouchSlop || mTracking)) {
222                    setExpandedHeightInternal(newHeight);
223                    mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
224                }
225
226                trackMovement(event);
227                break;
228
229            case MotionEvent.ACTION_UP:
230            case MotionEvent.ACTION_CANCEL:
231                mTrackingPointer = -1;
232                trackMovement(event);
233                if ((mTracking && mTouchSlopExceeded)
234                        || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
235                    float vel = getCurrentVelocity();
236                    boolean expand = flingExpands(vel);
237                    onTrackingStopped(expand);
238                    fling(vel, expand);
239                } else {
240                    boolean expands = onEmptySpaceClick(mInitialTouchX);
241                    onTrackingStopped(expands);
242                }
243                if (mVelocityTracker != null) {
244                    mVelocityTracker.recycle();
245                    mVelocityTracker = null;
246                }
247                break;
248        }
249        return !waitForTouchSlop || mTracking;
250    }
251
252    protected abstract boolean hasConflictingGestures();
253
254    protected void onTrackingStopped(boolean expand) {
255        mTracking = false;
256        mBar.onTrackingStopped(PanelView.this, expand);
257    }
258
259    protected void onTrackingStarted() {
260        mTracking = true;
261        mBar.onTrackingStarted(PanelView.this);
262        onExpandingStarted();
263    }
264
265    private float getCurrentVelocity() {
266
267        // the velocitytracker might be null if we got a bad input stream
268        if (mVelocityTracker == null) {
269            return 0;
270        }
271        mVelocityTracker.computeCurrentVelocity(1000);
272        return mVelocityTracker.getYVelocity();
273    }
274
275    @Override
276    public boolean onInterceptTouchEvent(MotionEvent event) {
277        if (mInstantExpanding) {
278            return false;
279        }
280
281        /*
282         * If the user drags anywhere inside the panel we intercept it if he moves his finger
283         * upwards. This allows closing the shade from anywhere inside the panel.
284         *
285         * We only do this if the current content is scrolled to the bottom,
286         * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling gesture
287         * possible.
288         */
289        int pointerIndex = event.findPointerIndex(mTrackingPointer);
290        if (pointerIndex < 0) {
291            pointerIndex = 0;
292            mTrackingPointer = event.getPointerId(pointerIndex);
293        }
294        final float x = event.getX(pointerIndex);
295        final float y = event.getY(pointerIndex);
296        boolean scrolledToBottom = isScrolledToBottom();
297
298        switch (event.getActionMasked()) {
299            case MotionEvent.ACTION_DOWN:
300                if (mHeightAnimator != null && !mHintAnimationRunning) {
301                    mHeightAnimator.cancel(); // end any outstanding animations
302                    mTouchSlopExceeded = true;
303                    return true;
304                }
305                mInitialTouchY = y;
306                mInitialTouchX = x;
307                mTouchSlopExceeded = false;
308                initVelocityTracker();
309                trackMovement(event);
310                break;
311            case MotionEvent.ACTION_POINTER_UP:
312                final int upPointer = event.getPointerId(event.getActionIndex());
313                if (mTrackingPointer == upPointer) {
314                    // gesture is ongoing, find a new pointer to track
315                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
316                    mTrackingPointer = event.getPointerId(newIndex);
317                    mInitialTouchX = event.getX(newIndex);
318                    mInitialTouchY = event.getY(newIndex);
319                }
320                break;
321
322            case MotionEvent.ACTION_MOVE:
323                final float h = y - mInitialTouchY;
324                trackMovement(event);
325                if (scrolledToBottom) {
326                    if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) {
327                        if (mHeightAnimator != null) {
328                            mHeightAnimator.cancel();
329                        }
330                        mInitialOffsetOnTouch = mExpandedHeight;
331                        mInitialTouchY = y;
332                        mInitialTouchX = x;
333                        mTracking = true;
334                        mTouchSlopExceeded = true;
335                        onTrackingStarted();
336                        return true;
337                    }
338                }
339                break;
340        }
341        return false;
342    }
343
344    private void initVelocityTracker() {
345        if (mVelocityTracker != null) {
346            mVelocityTracker.recycle();
347        }
348        mVelocityTracker = VelocityTrackerFactory.obtain(getContext());
349    }
350
351    protected boolean isScrolledToBottom() {
352        return true;
353    }
354
355    protected float getContentHeight() {
356        return mExpandedHeight;
357    }
358
359    @Override
360    protected void onFinishInflate() {
361        super.onFinishInflate();
362        loadDimens();
363    }
364
365    @Override
366    protected void onConfigurationChanged(Configuration newConfig) {
367        super.onConfigurationChanged(newConfig);
368        loadDimens();
369        mMaxPanelHeight = -1;
370    }
371
372    /**
373     * @param vel the current velocity of the motion
374     * @return whether a fling should expands the panel; contracts otherwise
375     */
376    private boolean flingExpands(float vel) {
377        if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
378            return getExpandedFraction() > 0.5f;
379        } else {
380            return vel > 0;
381        }
382    }
383
384    protected void fling(float vel, boolean expand) {
385        cancelPeek();
386        float target = expand ? getMaxPanelHeight() : 0.0f;
387        if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) {
388            onExpandingFinished();
389            mBar.panelExpansionChanged(this, mExpandedFraction);
390            return;
391        }
392        mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
393        ValueAnimator animator = createHeightAnimator(target);
394        if (expand) {
395            mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight());
396        } else {
397            mFlingAnimationUtils.applyDismissing(animator, mExpandedHeight, target, vel,
398                    getHeight());
399
400            // Make it shorter if we run a canned animation
401            if (vel == 0) {
402                animator.setDuration((long) (animator.getDuration() / 1.75f));
403            }
404        }
405        animator.addListener(new AnimatorListenerAdapter() {
406            @Override
407            public void onAnimationEnd(Animator animation) {
408                mHeightAnimator = null;
409                onExpandingFinished();
410            }
411        });
412        mHeightAnimator = animator;
413        animator.start();
414    }
415
416    @Override
417    protected void onAttachedToWindow() {
418        super.onAttachedToWindow();
419        mViewName = getResources().getResourceName(getId());
420    }
421
422    public String getName() {
423        return mViewName;
424    }
425
426    @Override
427    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
428        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
429
430        if (DEBUG) logf("onMeasure(%d, %d) -> (%d, %d)",
431                widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
432
433        // Did one of our children change size?
434        int newHeight = getMeasuredHeight();
435        if (newHeight > mMaxPanelHeight) {
436            // we only adapt the max height if it's bigger
437            mMaxPanelHeight = newHeight;
438            // If the user isn't actively poking us, let's rubberband to the content
439            if (!mTracking && mHeightAnimator == null
440                    && mExpandedHeight > 0 && mExpandedHeight != mMaxPanelHeight
441                    && mMaxPanelHeight > 0) {
442                mExpandedHeight = mMaxPanelHeight;
443            }
444        }
445    }
446
447    public void setExpandedHeight(float height) {
448        if (DEBUG) logf("setExpandedHeight(%.1f)", height);
449        setExpandedHeightInternal(height + getOverExpansionPixels());
450        mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
451    }
452
453    @Override
454    protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
455        if (DEBUG) logf("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom,
456                (int)mExpandedHeight, mMaxPanelHeight);
457        super.onLayout(changed, left, top, right, bottom);
458        requestPanelHeightUpdate();
459    }
460
461    protected void requestPanelHeightUpdate() {
462        float currentMaxPanelHeight = getMaxPanelHeight();
463
464        // If the user isn't actively poking us, let's update the height
465        if (!mTracking && mHeightAnimator == null
466                && mExpandedHeight > 0 && currentMaxPanelHeight != mExpandedHeight) {
467            setExpandedHeight(currentMaxPanelHeight);
468        }
469    }
470
471    public void setExpandedHeightInternal(float h) {
472        float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount();
473        if (mHeightAnimator == null) {
474            float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion);
475            if (getOverExpansionPixels() != overExpansionPixels && mTracking) {
476                setOverExpansion(overExpansionPixels, true /* isPixels */);
477            }
478            mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount();
479        } else {
480            mExpandedHeight = h;
481            if (mOverExpandedBeforeFling) {
482                setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */);
483            }
484        }
485
486        onHeightUpdated(mExpandedHeight);
487        mExpandedFraction = Math.min(1f, fhWithoutOverExpansion == 0
488                ? 0
489                : mExpandedHeight / fhWithoutOverExpansion);
490    }
491
492    protected abstract void setOverExpansion(float overExpansion, boolean isPixels);
493
494    protected abstract void onHeightUpdated(float expandedHeight);
495
496    protected abstract float getOverExpansionAmount();
497
498    protected abstract float getOverExpansionPixels();
499
500    /**
501     * This returns the maximum height of the panel. Children should override this if their
502     * desired height is not the full height.
503     *
504     * @return the default implementation simply returns the maximum height.
505     */
506    protected int getMaxPanelHeight() {
507        mMaxPanelHeight = Math.max(mMaxPanelHeight, getHeight());
508        return mMaxPanelHeight;
509    }
510
511    public void setExpandedFraction(float frac) {
512        setExpandedHeight(getMaxPanelHeight() * frac);
513    }
514
515    public float getExpandedHeight() {
516        return mExpandedHeight;
517    }
518
519    public float getExpandedFraction() {
520        return mExpandedFraction;
521    }
522
523    public boolean isFullyExpanded() {
524        return mExpandedHeight >= getMaxPanelHeight();
525    }
526
527    public boolean isFullyCollapsed() {
528        return mExpandedHeight <= 0;
529    }
530
531    public boolean isCollapsing() {
532        return mClosing;
533    }
534
535    public boolean isTracking() {
536        return mTracking;
537    }
538
539    public void setBar(PanelBar panelBar) {
540        mBar = panelBar;
541    }
542
543    public void collapse() {
544        // TODO: abort animation or ongoing touch
545        if (DEBUG) logf("collapse: " + this);
546        if (!isFullyCollapsed()) {
547            if (mHeightAnimator != null) {
548                mHeightAnimator.cancel();
549            }
550            mClosing = true;
551            onExpandingStarted();
552            fling(0, false /* expand */);
553        }
554    }
555
556    public void expand() {
557        if (DEBUG) logf("expand: " + this);
558        if (isFullyCollapsed()) {
559            mBar.startOpeningPanel(this);
560            onExpandingStarted();
561            fling(0, true /* expand */);
562        } else if (DEBUG) {
563            if (DEBUG) logf("skipping expansion: is expanded");
564        }
565    }
566
567    public void cancelPeek() {
568        if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
569            mPeekAnimator.cancel();
570        }
571    }
572
573    public void instantExpand() {
574        mInstantExpanding = true;
575        abortAnimations();
576        if (mTracking) {
577            onTrackingStopped(true /* expands */); // The panel is expanded after this call.
578            onExpandingFinished();
579        }
580        setVisibility(VISIBLE);
581
582        // Wait for window manager to pickup the change, so we know the maximum height of the panel
583        // then.
584        getViewTreeObserver().addOnGlobalLayoutListener(
585                new ViewTreeObserver.OnGlobalLayoutListener() {
586                    @Override
587                    public void onGlobalLayout() {
588                        if (mStatusBar.getStatusBarWindow().getHeight()
589                                != mStatusBar.getStatusBarHeight()) {
590                            getViewTreeObserver().removeOnGlobalLayoutListener(this);
591                            setExpandedFraction(1f);
592                            mInstantExpanding = false;
593                        }
594                    }
595                });
596
597        // Make sure a layout really happens.
598        requestLayout();
599    }
600
601    private void abortAnimations() {
602        cancelPeek();
603        if (mHeightAnimator != null) {
604            mHeightAnimator.cancel();
605        }
606    }
607
608    protected void startUnlockHintAnimation() {
609
610        // We don't need to hint the user if an animation is already running or the user is changing
611        // the expansion.
612        if (mHeightAnimator != null || mTracking) {
613            return;
614        }
615        cancelPeek();
616        onExpandingStarted();
617        startUnlockHintAnimationPhase1(new Runnable() {
618            @Override
619            public void run() {
620                onExpandingFinished();
621                mStatusBar.onHintFinished();
622                mHintAnimationRunning = false;
623            }
624        });
625        mStatusBar.onUnlockHintStarted();
626        mHintAnimationRunning = true;
627    }
628
629    /**
630     * Phase 1: Move everything upwards.
631     */
632    private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) {
633        float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
634        ValueAnimator animator = createHeightAnimator(target);
635        animator.setDuration(250);
636        animator.setInterpolator(mLinearOutSlowInInterpolator);
637        animator.addListener(new AnimatorListenerAdapter() {
638            private boolean mCancelled;
639
640            @Override
641            public void onAnimationCancel(Animator animation) {
642                mCancelled = true;
643            }
644
645            @Override
646            public void onAnimationEnd(Animator animation) {
647                if (mCancelled) {
648                    mHeightAnimator = null;
649                    onAnimationFinished.run();
650                } else {
651                    startUnlockHintAnimationPhase2(onAnimationFinished);
652                }
653            }
654        });
655        animator.start();
656        mHeightAnimator = animator;
657        mOriginalIndicationY = mKeyguardBottomArea.getIndicationView().getY();
658        mKeyguardBottomArea.getIndicationView().animate()
659                .y(mOriginalIndicationY - mHintDistance)
660                .setDuration(250)
661                .setInterpolator(mLinearOutSlowInInterpolator)
662                .withEndAction(new Runnable() {
663                    @Override
664                    public void run() {
665                        mKeyguardBottomArea.getIndicationView().animate()
666                                .y(mOriginalIndicationY)
667                                .setDuration(450)
668                                .setInterpolator(mBounceInterpolator)
669                                .start();
670                    }
671                })
672                .start();
673    }
674
675    /**
676     * Phase 2: Bounce down.
677     */
678    private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
679        ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
680        animator.setDuration(450);
681        animator.setInterpolator(mBounceInterpolator);
682        animator.addListener(new AnimatorListenerAdapter() {
683            @Override
684            public void onAnimationEnd(Animator animation) {
685                mHeightAnimator = null;
686                onAnimationFinished.run();
687            }
688        });
689        animator.start();
690        mHeightAnimator = animator;
691    }
692
693    private ValueAnimator createHeightAnimator(float targetHeight) {
694        ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
695        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
696            @Override
697            public void onAnimationUpdate(ValueAnimator animation) {
698                setExpandedHeightInternal((Float) animation.getAnimatedValue());
699                mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
700            }
701        });
702        return animator;
703    }
704
705    /**
706     * Gets called when the user performs a click anywhere in the empty area of the panel.
707     *
708     * @return whether the panel will be expanded after the action performed by this method
709     */
710    private boolean onEmptySpaceClick(float x) {
711        if (mHintAnimationRunning) {
712            return true;
713        }
714        if (x < mEdgeTapAreaWidth
715                && mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
716            onEdgeClicked(false /* right */);
717            return true;
718        } else if (x > getWidth() - mEdgeTapAreaWidth
719                && mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
720            onEdgeClicked(true /* right */);
721            return true;
722        } else {
723            return onMiddleClicked();
724        }
725    }
726
727    private boolean onMiddleClicked() {
728        switch (mStatusBar.getBarState()) {
729            case StatusBarState.KEYGUARD:
730                startUnlockHintAnimation();
731                return true;
732            case StatusBarState.SHADE_LOCKED:
733                mStatusBar.goToKeyguard();
734                return true;
735            case StatusBarState.SHADE:
736                collapse();
737                return false;
738            default:
739                return true;
740        }
741    }
742
743    protected abstract void onEdgeClicked(boolean right);
744
745    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
746        pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
747                + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s"
748                + "]",
749                this.getClass().getSimpleName(),
750                getExpandedHeight(),
751                getMaxPanelHeight(),
752                mClosing?"T":"f",
753                mTracking?"T":"f",
754                mJustPeeked?"T":"f",
755                mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
756                mHeightAnimator, ((mHeightAnimator !=null && mHeightAnimator.isStarted())?" (started)":"")
757        ));
758    }
759
760    public abstract void resetViews();
761}
762