blob: 12aa0042c10c5620b003b74406179706f2e33660 [file] [log] [blame]
Daniel Sandler50a53132012-10-24 15:02:27 -04001/*
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
Daniel Sandler08d05e32012-08-08 16:39:54 -040017package com.android.systemui.statusbar.phone;
18
Jorim Jaggi1d480692014-05-20 19:41:58 +020019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040021import android.animation.ObjectAnimator;
Jorim Jaggi1d480692014-05-20 19:41:58 +020022import android.animation.ValueAnimator;
Daniel Sandler08d05e32012-08-08 16:39:54 -040023import android.content.Context;
Selim Cinekb84a1072014-05-15 19:10:18 +020024import android.content.res.Configuration;
Daniel Sandler08d05e32012-08-08 16:39:54 -040025import android.content.res.Resources;
26import android.util.AttributeSet;
John Spurlockcd686b52013-06-05 10:13:46 -040027import android.util.Log;
Daniel Sandler08d05e32012-08-08 16:39:54 -040028import android.view.MotionEvent;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010029import android.view.ViewConfiguration;
Jorim Jaggi0a27be82014-06-11 03:22:39 +020030import android.view.ViewTreeObserver;
Jorim Jaggi90129582014-06-02 14:44:49 +020031import android.view.animation.AnimationUtils;
32import android.view.animation.Interpolator;
Daniel Sandler08d05e32012-08-08 16:39:54 -040033import android.widget.FrameLayout;
34
35import com.android.systemui.R;
Jorim Jaggi1d480692014-05-20 19:41:58 +020036import com.android.systemui.statusbar.FlingAnimationUtils;
Jorim Jaggi90129582014-06-02 14:44:49 +020037import com.android.systemui.statusbar.StatusBarState;
Daniel Sandler08d05e32012-08-08 16:39:54 -040038
John Spurlockde84f0e2013-06-12 12:41:00 -040039import java.io.FileDescriptor;
40import java.io.PrintWriter;
John Spurlockde84f0e2013-06-12 12:41:00 -040041
Selim Cinek4c6969a2014-05-26 19:22:17 +020042public abstract class PanelView extends FrameLayout {
Daniel Sandler198a0302012-08-17 16:04:31 -040043 public static final boolean DEBUG = PanelBar.DEBUG;
Daniel Sandler08d05e32012-08-08 16:39:54 -040044 public static final String TAG = PanelView.class.getSimpleName();
Daniel Sandlere7c5bbb2013-03-05 13:36:21 -050045
John Spurlock97642182013-07-29 17:58:39 -040046 private final void logf(String fmt, Object... args) {
John Spurlockcd686b52013-06-05 10:13:46 -040047 Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
Daniel Sandler08d05e32012-08-08 16:39:54 -040048 }
49
Jorim Jaggi90129582014-06-02 14:44:49 +020050 protected PhoneStatusBar mStatusBar;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040051 private float mPeekHeight;
Jorim Jaggi90129582014-06-02 14:44:49 +020052 private float mHintDistance;
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +020053 private int mEdgeTapAreaWidth;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010054 private float mInitialOffsetOnTouch;
Daniel Sandler08d05e32012-08-08 16:39:54 -040055 private float mExpandedFraction = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +020056 protected float mExpandedHeight = 0;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040057 private boolean mJustPeeked;
Daniel Sandler50508132012-08-16 14:10:53 -040058 private boolean mClosing;
Jorim Jaggi8dd95e02014-06-03 16:19:33 +020059 protected boolean mTracking;
Jorim Jaggi90129582014-06-02 14:44:49 +020060 private boolean mTouchSlopExceeded;
John Spurlock48fa91a2013-08-15 09:29:31 -040061 private int mTrackingPointer;
Jorim Jaggid7daab72014-05-06 22:22:20 +020062 protected int mTouchSlop;
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +020063 protected boolean mHintAnimationRunning;
Jorim Jaggi47c85a32014-06-05 17:25:40 +020064 private boolean mOverExpandedBeforeFling;
Selim Cinekf99d0002014-06-13 07:36:01 +020065 private float mOriginalIndicationY;
Daniel Sandler08d05e32012-08-08 16:39:54 -040066
Jorim Jaggi1d480692014-05-20 19:41:58 +020067 private ValueAnimator mHeightAnimator;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040068 private ObjectAnimator mPeekAnimator;
Jorim Jaggib7b61dd2014-05-21 15:45:07 +020069 private VelocityTrackerInterface mVelocityTracker;
Jorim Jaggi1d480692014-05-20 19:41:58 +020070 private FlingAnimationUtils mFlingAnimationUtils;
Daniel Sandler08d05e32012-08-08 16:39:54 -040071
Jorim Jaggi0a27be82014-06-11 03:22:39 +020072 /**
73 * Whether an instant expand request is currently pending and we are just waiting for layout.
74 */
75 private boolean mInstantExpanding;
76
Daniel Sandler08d05e32012-08-08 16:39:54 -040077 PanelBar mBar;
78
Selim Cinekb84a1072014-05-15 19:10:18 +020079 protected int mMaxPanelHeight = -1;
Daniel Sandler50508132012-08-16 14:10:53 -040080 private String mViewName;
Jorim Jaggid7daab72014-05-06 22:22:20 +020081 private float mInitialTouchY;
82 private float mInitialTouchX;
Daniel Sandler08d05e32012-08-08 16:39:54 -040083
Jorim Jaggi90129582014-06-02 14:44:49 +020084 private Interpolator mLinearOutSlowInInterpolator;
85 private Interpolator mBounceInterpolator;
Selim Cinekf99d0002014-06-13 07:36:01 +020086 protected KeyguardBottomAreaView mKeyguardBottomArea;
Jorim Jaggi90129582014-06-02 14:44:49 +020087
Selim Cinek1685e632014-04-08 02:27:49 +020088 protected void onExpandingFinished() {
Jorim Jaggi2fbad7b2014-05-26 22:38:00 +020089 mBar.onExpandingFinished();
Selim Cinek1685e632014-04-08 02:27:49 +020090 }
91
92 protected void onExpandingStarted() {
93 }
94
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040095 private void runPeekAnimation() {
John Spurlock97642182013-07-29 17:58:39 -040096 if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
Jorim Jaggi1d480692014-05-20 19:41:58 +020097 if (mHeightAnimator != null) {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040098 return;
99 }
100 if (mPeekAnimator == null) {
John Spurlock209bede2013-07-17 12:23:27 -0400101 mPeekAnimator = ObjectAnimator.ofFloat(this,
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400102 "expandedHeight", mPeekHeight)
Daniel Sandler3679bf52012-10-16 21:30:28 -0400103 .setDuration(250);
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400104 }
105 mPeekAnimator.start();
106 }
107
Daniel Sandler08d05e32012-08-08 16:39:54 -0400108 public PanelView(Context context, AttributeSet attrs) {
109 super(context, attrs);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200110 mFlingAnimationUtils = new FlingAnimationUtils(context, 0.6f);
Jorim Jaggi90129582014-06-02 14:44:49 +0200111 mLinearOutSlowInInterpolator =
112 AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
113 mBounceInterpolator = new BounceInterpolator();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400114 }
115
Jorim Jaggi069cd032014-05-15 03:09:01 +0200116 protected void loadDimens() {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400117 final Resources res = getContext().getResources();
John Spurlock209bede2013-07-17 12:23:27 -0400118 mPeekHeight = res.getDimension(R.dimen.peek_height)
John Spurlock50728832014-04-17 19:05:28 -0400119 + getPaddingBottom(); // our window might have a dropshadow
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100120
121 final ViewConfiguration configuration = ViewConfiguration.get(getContext());
122 mTouchSlop = configuration.getScaledTouchSlop();
Jorim Jaggi90129582014-06-02 14:44:49 +0200123 mHintDistance = res.getDimension(R.dimen.hint_move_distance);
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200124 mEdgeTapAreaWidth = res.getDimensionPixelSize(R.dimen.edge_tap_area_width);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400125 }
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);
Daniel Sandlerb17a7262012-10-05 14:32:50 -0400133 if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400134 event.offsetLocation(-deltaX, -deltaY);
135 }
136
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400137 @Override
138 public boolean onTouchEvent(MotionEvent event) {
Jorim Jaggi0a27be82014-06-11 03:22:39 +0200139 if (mInstantExpanding) {
140 return false;
141 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100142
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);
Jorim Jaggia6310292014-04-16 14:11:52 +0200158 final float x = event.getX(pointerIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100159
Selim Cinek4c6969a2014-05-26 19:22:17 +0200160 boolean waitForTouchSlop = hasConflictingGestures();
161
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100162 switch (event.getActionMasked()) {
163 case MotionEvent.ACTION_DOWN:
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100164 mInitialTouchY = y;
Jorim Jaggia6310292014-04-16 14:11:52 +0200165 mInitialTouchX = x;
Selim Cinek4c6969a2014-05-26 19:22:17 +0200166 mInitialOffsetOnTouch = mExpandedHeight;
Jorim Jaggi90129582014-06-02 14:44:49 +0200167 mTouchSlopExceeded = false;
Jorim Jaggib7b61dd2014-05-21 15:45:07 +0200168 if (mVelocityTracker == null) {
169 initVelocityTracker();
170 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100171 trackMovement(event);
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200172 if (!waitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning)) {
Selim Cinek4c6969a2014-05-26 19:22:17 +0200173 if (mHeightAnimator != null) {
174 mHeightAnimator.cancel(); // end any outstanding animations
175 }
Jorim Jaggi787a0af2014-06-04 18:57:14 +0200176 mTouchSlopExceeded = (mHeightAnimator != null && !mHintAnimationRunning);
Selim Cinek4c6969a2014-05-26 19:22:17 +0200177 onTrackingStarted();
Jorim Jaggi1d480692014-05-20 19:41:58 +0200178 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100179 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);
Jorim Jaggia6310292014-04-16 14:11:52 +0200191 final float newX = event.getX(newIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100192 mTrackingPointer = event.getPointerId(newIndex);
193 mInitialOffsetOnTouch = mExpandedHeight;
194 mInitialTouchY = newY;
Jorim Jaggia6310292014-04-16 14:11:52 +0200195 mInitialTouchX = newX;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100196 }
197 break;
198
199 case MotionEvent.ACTION_MOVE:
Selim Cinek4c6969a2014-05-26 19:22:17 +0200200 float h = y - mInitialTouchY;
Jorim Jaggi90129582014-06-02 14:44:49 +0200201 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;
Selim Cinek4c6969a2014-05-26 19:22:17 +0200212 }
Selim Cinek4c6969a2014-05-26 19:22:17 +0200213 }
214 final float newHeight = h + mInitialOffsetOnTouch;
215 if (newHeight > mPeekHeight) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100216 if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
217 mPeekAnimator.cancel();
218 }
219 mJustPeeked = false;
220 }
Selim Cinek4c6969a2014-05-26 19:22:17 +0200221 if (!mJustPeeked && (!waitForTouchSlop || mTracking)) {
Jorim Jaggicc693242014-06-14 03:04:35 +0000222 setExpandedHeightInternal(newHeight);
223 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100224 }
225
226 trackMovement(event);
227 break;
228
229 case MotionEvent.ACTION_UP:
230 case MotionEvent.ACTION_CANCEL:
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100231 mTrackingPointer = -1;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100232 trackMovement(event);
Jorim Jaggi787a0af2014-06-04 18:57:14 +0200233 if ((mTracking && mTouchSlopExceeded)
234 || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
Jorim Jaggi90129582014-06-02 14:44:49 +0200235 float vel = getCurrentVelocity();
236 boolean expand = flingExpands(vel);
237 onTrackingStopped(expand);
238 fling(vel, expand);
239 } else {
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200240 boolean expands = onEmptySpaceClick(mInitialTouchX);
Jorim Jaggi90129582014-06-02 14:44:49 +0200241 onTrackingStopped(expands);
242 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100243 if (mVelocityTracker != null) {
244 mVelocityTracker.recycle();
245 mVelocityTracker = null;
246 }
247 break;
248 }
Selim Cinek4c6969a2014-05-26 19:22:17 +0200249 return !waitForTouchSlop || mTracking;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100250 }
251
Selim Cinek4c6969a2014-05-26 19:22:17 +0200252 protected abstract boolean hasConflictingGestures();
253
Jorim Jaggi2fbad7b2014-05-26 22:38:00 +0200254 protected void onTrackingStopped(boolean expand) {
Selim Cinek4c6969a2014-05-26 19:22:17 +0200255 mTracking = false;
Jorim Jaggi2fbad7b2014-05-26 22:38:00 +0200256 mBar.onTrackingStopped(PanelView.this, expand);
Selim Cinek1685e632014-04-08 02:27:49 +0200257 }
258
259 protected void onTrackingStarted() {
Selim Cinek4c6969a2014-05-26 19:22:17 +0200260 mTracking = true;
Selim Cinek1685e632014-04-08 02:27:49 +0200261 mBar.onTrackingStarted(PanelView.this);
262 onExpandingStarted();
263 }
264
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100265 private float getCurrentVelocity() {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100266
267 // the velocitytracker might be null if we got a bad input stream
268 if (mVelocityTracker == null) {
269 return 0;
270 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100271 mVelocityTracker.computeCurrentVelocity(1000);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200272 return mVelocityTracker.getYVelocity();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100273 }
274
275 @Override
276 public boolean onInterceptTouchEvent(MotionEvent event) {
Jorim Jaggi0a27be82014-06-11 03:22:39 +0200277 if (mInstantExpanding) {
278 return false;
279 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100280
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 }
Jorim Jaggia6310292014-04-16 14:11:52 +0200294 final float x = event.getX(pointerIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100295 final float y = event.getY(pointerIndex);
296 boolean scrolledToBottom = isScrolledToBottom();
297
298 switch (event.getActionMasked()) {
299 case MotionEvent.ACTION_DOWN:
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200300 if (mHeightAnimator != null && !mHintAnimationRunning) {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200301 mHeightAnimator.cancel(); // end any outstanding animations
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200302 mTouchSlopExceeded = true;
Selim Cinek172e9142014-05-07 19:38:00 +0200303 return true;
304 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100305 mInitialTouchY = y;
Jorim Jaggia6310292014-04-16 14:11:52 +0200306 mInitialTouchX = x;
Jorim Jaggi90129582014-06-02 14:44:49 +0200307 mTouchSlopExceeded = false;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100308 initVelocityTracker();
309 trackMovement(event);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100310 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);
Jorim Jaggia6310292014-04-16 14:11:52 +0200317 mInitialTouchX = event.getX(newIndex);
318 mInitialTouchY = event.getY(newIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100319 }
320 break;
321
322 case MotionEvent.ACTION_MOVE:
323 final float h = y - mInitialTouchY;
324 trackMovement(event);
325 if (scrolledToBottom) {
Jorim Jaggia6310292014-04-16 14:11:52 +0200326 if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) {
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200327 if (mHeightAnimator != null) {
328 mHeightAnimator.cancel();
329 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100330 mInitialOffsetOnTouch = mExpandedHeight;
331 mInitialTouchY = y;
Jorim Jaggia6310292014-04-16 14:11:52 +0200332 mInitialTouchX = x;
Selim Cinek91c39ef2014-04-07 21:18:09 +0200333 mTracking = true;
Jorim Jaggi90129582014-06-02 14:44:49 +0200334 mTouchSlopExceeded = true;
Selim Cinek1685e632014-04-08 02:27:49 +0200335 onTrackingStarted();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100336 return true;
337 }
338 }
339 break;
340 }
341 return false;
342 }
343
344 private void initVelocityTracker() {
345 if (mVelocityTracker != null) {
346 mVelocityTracker.recycle();
347 }
Jorim Jaggib7b61dd2014-05-21 15:45:07 +0200348 mVelocityTracker = VelocityTrackerFactory.obtain(getContext());
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100349 }
350
351 protected boolean isScrolledToBottom() {
Selim Cinek172e9142014-05-07 19:38:00 +0200352 return true;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100353 }
354
355 protected float getContentHeight() {
356 return mExpandedHeight;
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400357 }
358
Daniel Sandler08d05e32012-08-08 16:39:54 -0400359 @Override
360 protected void onFinishInflate() {
361 super.onFinishInflate();
362 loadDimens();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400363 }
364
Jorim Jaggi069cd032014-05-15 03:09:01 +0200365 @Override
366 protected void onConfigurationChanged(Configuration newConfig) {
367 super.onConfigurationChanged(newConfig);
368 loadDimens();
369 mMaxPanelHeight = -1;
370 }
371
Jorim Jaggi2fbad7b2014-05-26 22:38:00 +0200372 /**
Jorim Jaggie29b2db2014-05-30 23:17:03 +0200373 * @param vel the current velocity of the motion
374 * @return whether a fling should expands the panel; contracts otherwise
Jorim Jaggi2fbad7b2014-05-26 22:38:00 +0200375 */
Jorim Jaggie29b2db2014-05-30 23:17:03 +0200376 private boolean flingExpands(float vel) {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200377 if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
Jorim Jaggie29b2db2014-05-30 23:17:03 +0200378 return getExpandedFraction() > 0.5f;
Selim Cinek1685e632014-04-08 02:27:49 +0200379 } else {
Jorim Jaggie29b2db2014-05-30 23:17:03 +0200380 return vel > 0;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400381 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200382 }
383
384 protected void fling(float vel, boolean expand) {
385 cancelPeek();
386 float target = expand ? getMaxPanelHeight() : 0.0f;
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200387 if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) {
Jorim Jaggi2fbad7b2014-05-26 22:38:00 +0200388 onExpandingFinished();
Jorim Jaggie29b2db2014-05-30 23:17:03 +0200389 mBar.panelExpansionChanged(this, mExpandedFraction);
Jorim Jaggi2fbad7b2014-05-26 22:38:00 +0200390 return;
391 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200392 mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
Jorim Jaggi90129582014-06-02 14:44:49 +0200393 ValueAnimator animator = createHeightAnimator(target);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200394 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 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200405 animator.addListener(new AnimatorListenerAdapter() {
406 @Override
407 public void onAnimationEnd(Animator animation) {
408 mHeightAnimator = null;
409 onExpandingFinished();
410 }
411 });
Jorim Jaggi1d480692014-05-20 19:41:58 +0200412 mHeightAnimator = animator;
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200413 animator.start();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400414 }
415
416 @Override
417 protected void onAttachedToWindow() {
418 super.onAttachedToWindow();
Daniel Sandler978f8532012-08-15 15:48:16 -0400419 mViewName = getResources().getResourceName(getId());
420 }
421
422 public String getName() {
423 return mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400424 }
425
Daniel Sandler08d05e32012-08-08 16:39:54 -0400426 @Override
427 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
428 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
429
John Spurlock97642182013-07-29 17:58:39 -0400430 if (DEBUG) logf("onMeasure(%d, %d) -> (%d, %d)",
Daniel Sandler08d05e32012-08-08 16:39:54 -0400431 widthMeasureSpec, heightMeasureSpec, getMeasuredWidth(), getMeasuredHeight());
Daniel Sandler198a0302012-08-17 16:04:31 -0400432
433 // Did one of our children change size?
434 int newHeight = getMeasuredHeight();
Selim Cinekb84a1072014-05-15 19:10:18 +0200435 if (newHeight > mMaxPanelHeight) {
436 // we only adapt the max height if it's bigger
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100437 mMaxPanelHeight = newHeight;
Jorim Jaggi03c701e2014-04-02 12:39:51 +0200438 // If the user isn't actively poking us, let's rubberband to the content
Jorim Jaggi1d480692014-05-20 19:41:58 +0200439 if (!mTracking && mHeightAnimator == null
Jorim Jaggi03c701e2014-04-02 12:39:51 +0200440 && mExpandedHeight > 0 && mExpandedHeight != mMaxPanelHeight
441 && mMaxPanelHeight > 0) {
442 mExpandedHeight = mMaxPanelHeight;
443 }
Daniel Sandler50508132012-08-16 14:10:53 -0400444 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400445 }
446
Daniel Sandler08d05e32012-08-08 16:39:54 -0400447 public void setExpandedHeight(float height) {
John Spurlock97642182013-07-29 17:58:39 -0400448 if (DEBUG) logf("setExpandedHeight(%.1f)", height);
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200449 setExpandedHeightInternal(height + getOverExpansionPixels());
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400450 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400451 }
452
Daniel Sandler50508132012-08-16 14:10:53 -0400453 @Override
454 protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100455 if (DEBUG) logf("onLayout: changed=%s, bottom=%d eh=%d fh=%d", changed?"T":"f", bottom,
456 (int)mExpandedHeight, mMaxPanelHeight);
Daniel Sandler50508132012-08-16 14:10:53 -0400457 super.onLayout(changed, left, top, right, bottom);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100458 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
Jorim Jaggi1d480692014-05-20 19:41:58 +0200465 if (!mTracking && mHeightAnimator == null
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100466 && mExpandedHeight > 0 && currentMaxPanelHeight != mExpandedHeight) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200467 setExpandedHeight(currentMaxPanelHeight);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100468 }
Daniel Sandler50508132012-08-16 14:10:53 -0400469 }
470
Daniel Sandler08d05e32012-08-08 16:39:54 -0400471 public void setExpandedHeightInternal(float h) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200472 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 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100484 }
Daniel Sandler198a0302012-08-17 16:04:31 -0400485
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100486 onHeightUpdated(mExpandedHeight);
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200487 mExpandedFraction = Math.min(1f, fhWithoutOverExpansion == 0
488 ? 0
489 : mExpandedHeight / fhWithoutOverExpansion);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400490 }
491
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200492 protected abstract void setOverExpansion(float overExpansion, boolean isPixels);
Selim Cinek24120a52014-05-26 10:05:42 +0200493
Jorim Jaggi90129582014-06-02 14:44:49 +0200494 protected abstract void onHeightUpdated(float expandedHeight);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100495
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200496 protected abstract float getOverExpansionAmount();
497
498 protected abstract float getOverExpansionPixels();
499
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100500 /**
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() {
Selim Cinekb84a1072014-05-15 19:10:18 +0200507 mMaxPanelHeight = Math.max(mMaxPanelHeight, getHeight());
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100508 return mMaxPanelHeight;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400509 }
510
511 public void setExpandedFraction(float frac) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100512 setExpandedHeight(getMaxPanelHeight() * frac);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400513 }
514
515 public float getExpandedHeight() {
516 return mExpandedHeight;
517 }
518
519 public float getExpandedFraction() {
520 return mExpandedFraction;
521 }
522
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700523 public boolean isFullyExpanded() {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100524 return mExpandedHeight >= getMaxPanelHeight();
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700525 }
526
527 public boolean isFullyCollapsed() {
Daniel Sandler67eab792012-10-02 17:08:23 -0400528 return mExpandedHeight <= 0;
529 }
530
531 public boolean isCollapsing() {
532 return mClosing;
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700533 }
534
John Spurlocka4b70af2013-08-17 14:05:49 -0400535 public boolean isTracking() {
536 return mTracking;
537 }
538
Daniel Sandler08d05e32012-08-08 16:39:54 -0400539 public void setBar(PanelBar panelBar) {
540 mBar = panelBar;
541 }
542
Daniel Sandler08d05e32012-08-08 16:39:54 -0400543 public void collapse() {
544 // TODO: abort animation or ongoing touch
John Spurlock97642182013-07-29 17:58:39 -0400545 if (DEBUG) logf("collapse: " + this);
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700546 if (!isFullyCollapsed()) {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200547 if (mHeightAnimator != null) {
548 mHeightAnimator.cancel();
549 }
Daniel Sandler750bb9b2012-10-03 16:24:00 -0400550 mClosing = true;
Selim Cinek1685e632014-04-08 02:27:49 +0200551 onExpandingStarted();
Jorim Jaggi1d480692014-05-20 19:41:58 +0200552 fling(0, false /* expand */);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400553 }
554 }
555
556 public void expand() {
John Spurlock97642182013-07-29 17:58:39 -0400557 if (DEBUG) logf("expand: " + this);
Daniel Sandler198a0302012-08-17 16:04:31 -0400558 if (isFullyCollapsed()) {
559 mBar.startOpeningPanel(this);
Selim Cinek1685e632014-04-08 02:27:49 +0200560 onExpandingStarted();
Jorim Jaggi1d480692014-05-20 19:41:58 +0200561 fling(0, true /* expand */);
Daniel Sandler198a0302012-08-17 16:04:31 -0400562 } else if (DEBUG) {
John Spurlock97642182013-07-29 17:58:39 -0400563 if (DEBUG) logf("skipping expansion: is expanded");
564 }
565 }
566
567 public void cancelPeek() {
568 if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
569 mPeekAnimator.cancel();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400570 }
571 }
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500572
Jorim Jaggi0a27be82014-06-11 03:22:39 +0200573 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
Jorim Jaggi90129582014-06-02 14:44:49 +0200608 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();
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200617 startUnlockHintAnimationPhase1(new Runnable() {
618 @Override
619 public void run() {
620 onExpandingFinished();
621 mStatusBar.onHintFinished();
622 mHintAnimationRunning = false;
623 }
624 });
Jorim Jaggi90129582014-06-02 14:44:49 +0200625 mStatusBar.onUnlockHintStarted();
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200626 mHintAnimationRunning = true;
Jorim Jaggi90129582014-06-02 14:44:49 +0200627 }
628
629 /**
630 * Phase 1: Move everything upwards.
631 */
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200632 private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) {
Jorim Jaggi90129582014-06-02 14:44:49 +0200633 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;
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200649 onAnimationFinished.run();
Jorim Jaggi90129582014-06-02 14:44:49 +0200650 } else {
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200651 startUnlockHintAnimationPhase2(onAnimationFinished);
Jorim Jaggi90129582014-06-02 14:44:49 +0200652 }
653 }
654 });
655 animator.start();
656 mHeightAnimator = animator;
Selim Cinekf99d0002014-06-13 07:36:01 +0200657 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();
Jorim Jaggi90129582014-06-02 14:44:49 +0200673 }
674
675 /**
676 * Phase 2: Bounce down.
677 */
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200678 private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
Jorim Jaggi90129582014-06-02 14:44:49 +0200679 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;
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200686 onAnimationFinished.run();
Jorim Jaggi90129582014-06-02 14:44:49 +0200687 }
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) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200698 setExpandedHeightInternal((Float) animation.getAnimatedValue());
699 mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
Jorim Jaggi90129582014-06-02 14:44:49 +0200700 }
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 */
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200710 private boolean onEmptySpaceClick(float x) {
711 if (mHintAnimationRunning) {
712 return true;
713 }
Jorim Jaggi6539a832014-06-03 23:33:09 +0200714 if (x < mEdgeTapAreaWidth
715 && mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200716 onEdgeClicked(false /* right */);
717 return true;
Jorim Jaggi6539a832014-06-03 23:33:09 +0200718 } else if (x > getWidth() - mEdgeTapAreaWidth
719 && mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200720 onEdgeClicked(true /* right */);
721 return true;
722 } else {
723 return onMiddleClicked();
724 }
725 }
726
727 private boolean onMiddleClicked() {
Jorim Jaggi90129582014-06-02 14:44:49 +0200728 switch (mStatusBar.getBarState()) {
729 case StatusBarState.KEYGUARD:
730 startUnlockHintAnimation();
731 return true;
732 case StatusBarState.SHADE_LOCKED:
Jorim Jaggi6539a832014-06-03 23:33:09 +0200733 mStatusBar.goToKeyguard();
Jorim Jaggi90129582014-06-02 14:44:49 +0200734 return true;
735 case StatusBarState.SHADE:
736 collapse();
737 return false;
738 default:
739 return true;
740 }
741 }
742
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200743 protected abstract void onEdgeClicked(boolean right);
744
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500745 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Selim Cineka3e5bcb2014-04-07 20:07:22 +0200746 pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
John Spurlock50728832014-04-17 19:05:28 -0400747 + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s"
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500748 + "]",
749 this.getClass().getSimpleName(),
750 getExpandedHeight(),
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100751 getMaxPanelHeight(),
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500752 mClosing?"T":"f",
753 mTracking?"T":"f",
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500754 mJustPeeked?"T":"f",
755 mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
Jorim Jaggi1d480692014-05-20 19:41:58 +0200756 mHeightAnimator, ((mHeightAnimator !=null && mHeightAnimator.isStarted())?" (started)":"")
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500757 ));
758 }
Selim Cinek3c4635c2014-05-29 02:12:47 +0200759
760 public abstract void resetViews();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400761}