blob: 13d0e1e0fbc38f39cbe3d3aaa8077106cab271a1 [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
Christoph Studerb0183992014-12-22 21:02:26 +010035import com.android.systemui.EventLogConstants;
36import com.android.systemui.EventLogTags;
Daniel Sandler08d05e32012-08-08 16:39:54 -040037import com.android.systemui.R;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070038import com.android.systemui.classifier.Classifier;
39import com.android.systemui.classifier.FalsingManager;
40import com.android.systemui.classifier.HumanInteractionClassifier;
John Spurlock813552c2014-09-19 08:30:21 -040041import com.android.systemui.doze.DozeLog;
Jorim Jaggi1d480692014-05-20 19:41:58 +020042import com.android.systemui.statusbar.FlingAnimationUtils;
Jorim Jaggi90129582014-06-02 14:44:49 +020043import com.android.systemui.statusbar.StatusBarState;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070044import com.android.systemui.statusbar.policy.HeadsUpManager;
Daniel Sandler08d05e32012-08-08 16:39:54 -040045
John Spurlockde84f0e2013-06-12 12:41:00 -040046import java.io.FileDescriptor;
47import java.io.PrintWriter;
John Spurlockde84f0e2013-06-12 12:41:00 -040048
Selim Cinek4c6969a2014-05-26 19:22:17 +020049public abstract class PanelView extends FrameLayout {
Daniel Sandler198a0302012-08-17 16:04:31 -040050 public static final boolean DEBUG = PanelBar.DEBUG;
Daniel Sandler08d05e32012-08-08 16:39:54 -040051 public static final String TAG = PanelView.class.getSimpleName();
Selim Cinek173f2d02015-04-29 15:36:56 -070052
John Spurlock97642182013-07-29 17:58:39 -040053 private final void logf(String fmt, Object... args) {
John Spurlockcd686b52013-06-05 10:13:46 -040054 Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
Daniel Sandler08d05e32012-08-08 16:39:54 -040055 }
56
Jorim Jaggi90129582014-06-02 14:44:49 +020057 protected PhoneStatusBar mStatusBar;
Selim Cinek684a4422015-04-15 16:18:39 -070058 protected HeadsUpManager mHeadsUpManager;
59
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040060 private float mPeekHeight;
Jorim Jaggi90129582014-06-02 14:44:49 +020061 private float mHintDistance;
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +020062 private int mEdgeTapAreaWidth;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010063 private float mInitialOffsetOnTouch;
Selim Cinek79d79c42015-05-21 16:14:45 -070064 private boolean mCollapsedAndHeadsUpOnDown;
Daniel Sandler08d05e32012-08-08 16:39:54 -040065 private float mExpandedFraction = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +020066 protected float mExpandedHeight = 0;
Selim Cinek31094df2014-08-14 19:28:15 +020067 private boolean mPanelClosedOnDown;
68 private boolean mHasLayoutedSinceDown;
69 private float mUpdateFlingVelocity;
70 private boolean mUpdateFlingOnLayout;
Jorim Jaggib7a33032014-08-20 16:21:36 +020071 private boolean mPeekTouching;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040072 private boolean mJustPeeked;
Daniel Sandler50508132012-08-16 14:10:53 -040073 private boolean mClosing;
Jorim Jaggi8dd95e02014-06-03 16:19:33 +020074 protected boolean mTracking;
Jorim Jaggi90129582014-06-02 14:44:49 +020075 private boolean mTouchSlopExceeded;
John Spurlock48fa91a2013-08-15 09:29:31 -040076 private int mTrackingPointer;
Jorim Jaggid7daab72014-05-06 22:22:20 +020077 protected int mTouchSlop;
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +020078 protected boolean mHintAnimationRunning;
Jorim Jaggi47c85a32014-06-05 17:25:40 +020079 private boolean mOverExpandedBeforeFling;
Selim Cinek19c8c702014-08-25 22:09:19 +020080 private boolean mTouchAboveFalsingThreshold;
81 private int mUnlockFalsingThreshold;
Selim Cinekab1dc952014-10-30 20:20:29 +010082 private boolean mTouchStartedInEmptyArea;
Selim Cinek547a06b2014-11-27 14:06:04 +010083 private boolean mMotionAborted;
Selim Cinek9db71052015-04-24 18:54:30 -070084 private boolean mUpwardsWhenTresholdReached;
Selim Cinek173f2d02015-04-29 15:36:56 -070085 private boolean mAnimatingOnDown;
Daniel Sandler08d05e32012-08-08 16:39:54 -040086
Jorim Jaggi1d480692014-05-20 19:41:58 +020087 private ValueAnimator mHeightAnimator;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -040088 private ObjectAnimator mPeekAnimator;
Jorim Jaggib7b61dd2014-05-21 15:45:07 +020089 private VelocityTrackerInterface mVelocityTracker;
Jorim Jaggi1d480692014-05-20 19:41:58 +020090 private FlingAnimationUtils mFlingAnimationUtils;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070091 private FalsingManager mFalsingManager;
Daniel Sandler08d05e32012-08-08 16:39:54 -040092
Jorim Jaggi0a27be82014-06-11 03:22:39 +020093 /**
94 * Whether an instant expand request is currently pending and we are just waiting for layout.
95 */
96 private boolean mInstantExpanding;
97
Daniel Sandler08d05e32012-08-08 16:39:54 -040098 PanelBar mBar;
99
Daniel Sandler50508132012-08-16 14:10:53 -0400100 private String mViewName;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200101 private float mInitialTouchY;
102 private float mInitialTouchX;
Jorim Jaggid41083a2014-09-12 02:54:40 +0200103 private boolean mTouchDisabled;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400104
Jorim Jaggi90129582014-06-02 14:44:49 +0200105 private Interpolator mLinearOutSlowInInterpolator;
Jorim Jaggib472b3472014-06-30 19:56:24 +0200106 private Interpolator mFastOutSlowInInterpolator;
Jorim Jaggi90129582014-06-02 14:44:49 +0200107 private Interpolator mBounceInterpolator;
Selim Cinekf99d0002014-06-13 07:36:01 +0200108 protected KeyguardBottomAreaView mKeyguardBottomArea;
Jorim Jaggi90129582014-06-02 14:44:49 +0200109
Jorim Jaggib472b3472014-06-30 19:56:24 +0200110 private boolean mPeekPending;
111 private boolean mCollapseAfterPeek;
Jorim Jaggif3b3bee2015-04-16 14:57:34 -0700112
113 /**
114 * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time.
115 */
116 private float mNextCollapseSpeedUpFactor = 1.0f;
117
Jorim Jaggi1d071ce2015-07-22 14:05:06 -0700118 protected boolean mExpanding;
Jorim Jaggib472b3472014-06-30 19:56:24 +0200119 private boolean mGestureWaitForTouchSlop;
Selim Cinek9db71052015-04-24 18:54:30 -0700120 private boolean mIgnoreXTouchSlop;
Jorim Jaggib472b3472014-06-30 19:56:24 +0200121 private Runnable mPeekRunnable = new Runnable() {
122 @Override
123 public void run() {
124 mPeekPending = false;
125 runPeekAnimation();
126 }
127 };
128
Selim Cinek1685e632014-04-08 02:27:49 +0200129 protected void onExpandingFinished() {
Selim Cinekdbbcfbe2014-10-24 17:52:35 +0200130 endClosing();
Jorim Jaggi2fbad7b2014-05-26 22:38:00 +0200131 mBar.onExpandingFinished();
Selim Cinek1685e632014-04-08 02:27:49 +0200132 }
133
134 protected void onExpandingStarted() {
135 }
136
Jorim Jaggib472b3472014-06-30 19:56:24 +0200137 private void notifyExpandingStarted() {
138 if (!mExpanding) {
139 mExpanding = true;
140 onExpandingStarted();
141 }
142 }
143
Jorim Jaggi1d071ce2015-07-22 14:05:06 -0700144 protected final void notifyExpandingFinished() {
Jorim Jaggib472b3472014-06-30 19:56:24 +0200145 if (mExpanding) {
146 mExpanding = false;
147 onExpandingFinished();
148 }
149 }
150
151 private void schedulePeek() {
152 mPeekPending = true;
153 long timeout = ViewConfiguration.getTapTimeout();
154 postOnAnimationDelayed(mPeekRunnable, timeout);
155 notifyBarPanelExpansionChanged();
156 }
157
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400158 private void runPeekAnimation() {
Jorim Jaggi2580a9762014-06-25 03:08:25 +0200159 mPeekHeight = getPeekHeight();
John Spurlock97642182013-07-29 17:58:39 -0400160 if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200161 if (mHeightAnimator != null) {
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400162 return;
163 }
Jorim Jaggib472b3472014-06-30 19:56:24 +0200164 mPeekAnimator = ObjectAnimator.ofFloat(this, "expandedHeight", mPeekHeight)
165 .setDuration(250);
166 mPeekAnimator.setInterpolator(mLinearOutSlowInInterpolator);
167 mPeekAnimator.addListener(new AnimatorListenerAdapter() {
168 private boolean mCancelled;
169
170 @Override
171 public void onAnimationCancel(Animator animation) {
172 mCancelled = true;
173 }
174
175 @Override
176 public void onAnimationEnd(Animator animation) {
177 mPeekAnimator = null;
178 if (mCollapseAfterPeek && !mCancelled) {
Selim Cineka0f5c762015-06-22 14:44:46 -0400179 postOnAnimation(mPostCollapseRunnable);
Jorim Jaggib472b3472014-06-30 19:56:24 +0200180 }
181 mCollapseAfterPeek = false;
182 }
183 });
184 notifyExpandingStarted();
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400185 mPeekAnimator.start();
Jorim Jaggib472b3472014-06-30 19:56:24 +0200186 mJustPeeked = true;
Daniel Sandler0c1b75c2012-10-04 12:08:54 -0400187 }
188
Daniel Sandler08d05e32012-08-08 16:39:54 -0400189 public PanelView(Context context, AttributeSet attrs) {
190 super(context, attrs);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200191 mFlingAnimationUtils = new FlingAnimationUtils(context, 0.6f);
Jorim Jaggib472b3472014-06-30 19:56:24 +0200192 mFastOutSlowInInterpolator =
Jorim Jaggi90129582014-06-02 14:44:49 +0200193 AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
Jorim Jaggib472b3472014-06-30 19:56:24 +0200194 mLinearOutSlowInInterpolator =
195 AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
Jorim Jaggi90129582014-06-02 14:44:49 +0200196 mBounceInterpolator = new BounceInterpolator();
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700197 mFalsingManager = FalsingManager.getInstance(context);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400198 }
199
Jorim Jaggi069cd032014-05-15 03:09:01 +0200200 protected void loadDimens() {
Daniel Sandler08d05e32012-08-08 16:39:54 -0400201 final Resources res = getContext().getResources();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100202 final ViewConfiguration configuration = ViewConfiguration.get(getContext());
203 mTouchSlop = configuration.getScaledTouchSlop();
Jorim Jaggi90129582014-06-02 14:44:49 +0200204 mHintDistance = res.getDimension(R.dimen.hint_move_distance);
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200205 mEdgeTapAreaWidth = res.getDimensionPixelSize(R.dimen.edge_tap_area_width);
Selim Cinek19c8c702014-08-25 22:09:19 +0200206 mUnlockFalsingThreshold = res.getDimensionPixelSize(R.dimen.unlock_falsing_threshold);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400207 }
208
209 private void trackMovement(MotionEvent event) {
210 // Add movement to velocity tracker using raw screen X and Y coordinates instead
211 // of window coordinates because the window frame may be moving at the same time.
212 float deltaX = event.getRawX() - event.getX();
213 float deltaY = event.getRawY() - event.getY();
214 event.offsetLocation(deltaX, deltaY);
Daniel Sandlerb17a7262012-10-05 14:32:50 -0400215 if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400216 event.offsetLocation(-deltaX, -deltaY);
217 }
218
Jorim Jaggid41083a2014-09-12 02:54:40 +0200219 public void setTouchDisabled(boolean disabled) {
220 mTouchDisabled = disabled;
221 }
222
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400223 @Override
224 public boolean onTouchEvent(MotionEvent event) {
Selim Cinek547a06b2014-11-27 14:06:04 +0100225 if (mInstantExpanding || mTouchDisabled
226 || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
Jorim Jaggi0a27be82014-06-11 03:22:39 +0200227 return false;
228 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100229
Jun Mukaicf9933c2015-07-08 17:34:26 -0700230 // On expanding, single mouse click expands the panel instead of dragging.
231 if (isFullyCollapsed() && event.getPointerCount() == 1
232 && event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
233 if (event.getAction() == MotionEvent.ACTION_UP) {
234 expand(true);
235 }
236 return true;
237 }
238
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100239 /*
240 * We capture touch events here and update the expand height here in case according to
241 * the users fingers. This also handles multi-touch.
242 *
Chris Wren5d53df42015-06-26 11:26:03 -0400243 * If the user just clicks shortly, we show a quick peek of the shade.
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100244 *
245 * Flinging is also enabled in order to open or close the shade.
246 */
247
248 int pointerIndex = event.findPointerIndex(mTrackingPointer);
249 if (pointerIndex < 0) {
250 pointerIndex = 0;
251 mTrackingPointer = event.getPointerId(pointerIndex);
252 }
Jorim Jaggia6310292014-04-16 14:11:52 +0200253 final float x = event.getX(pointerIndex);
Selim Cinek547a06b2014-11-27 14:06:04 +0100254 final float y = event.getY(pointerIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100255
Jorim Jaggib472b3472014-06-30 19:56:24 +0200256 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
Selim Cinek684a4422015-04-15 16:18:39 -0700257 mGestureWaitForTouchSlop = isFullyCollapsed() || hasConflictingGestures();
258 mIgnoreXTouchSlop = isFullyCollapsed() || shouldGestureIgnoreXTouchSlop(x, y);
Jorim Jaggib472b3472014-06-30 19:56:24 +0200259 }
Selim Cinek4c6969a2014-05-26 19:22:17 +0200260
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100261 switch (event.getActionMasked()) {
262 case MotionEvent.ACTION_DOWN:
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700263 startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
Jorim Jaggi3857ac42014-06-27 18:01:12 +0200264 mJustPeeked = false;
Selim Cinek684a4422015-04-15 16:18:39 -0700265 mPanelClosedOnDown = isFullyCollapsed();
Selim Cinek31094df2014-08-14 19:28:15 +0200266 mHasLayoutedSinceDown = false;
267 mUpdateFlingOnLayout = false;
Selim Cinek547a06b2014-11-27 14:06:04 +0100268 mMotionAborted = false;
Jorim Jaggib7a33032014-08-20 16:21:36 +0200269 mPeekTouching = mPanelClosedOnDown;
Selim Cinek19c8c702014-08-25 22:09:19 +0200270 mTouchAboveFalsingThreshold = false;
Selim Cinek79d79c42015-05-21 16:14:45 -0700271 mCollapsedAndHeadsUpOnDown = isFullyCollapsed()
272 && mHeadsUpManager.hasPinnedHeadsUp();
Jorim Jaggib7b61dd2014-05-21 15:45:07 +0200273 if (mVelocityTracker == null) {
274 initVelocityTracker();
275 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100276 trackMovement(event);
Selim Cinek9db71052015-04-24 18:54:30 -0700277 if (!mGestureWaitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning) ||
Jorim Jaggib472b3472014-06-30 19:56:24 +0200278 mPeekPending || mPeekAnimator != null) {
Selim Cinekdbbcfbe2014-10-24 17:52:35 +0200279 cancelHeightAnimator();
Jorim Jaggib472b3472014-06-30 19:56:24 +0200280 cancelPeek();
281 mTouchSlopExceeded = (mHeightAnimator != null && !mHintAnimationRunning)
282 || mPeekPending || mPeekAnimator != null;
Selim Cinek4c6969a2014-05-26 19:22:17 +0200283 onTrackingStarted();
Jorim Jaggi1d480692014-05-20 19:41:58 +0200284 }
Selim Cinek79d79c42015-05-21 16:14:45 -0700285 if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()) {
Jorim Jaggib472b3472014-06-30 19:56:24 +0200286 schedulePeek();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100287 }
288 break;
289
290 case MotionEvent.ACTION_POINTER_UP:
291 final int upPointer = event.getPointerId(event.getActionIndex());
292 if (mTrackingPointer == upPointer) {
293 // gesture is ongoing, find a new pointer to track
294 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
295 final float newY = event.getY(newIndex);
Jorim Jaggia6310292014-04-16 14:11:52 +0200296 final float newX = event.getX(newIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100297 mTrackingPointer = event.getPointerId(newIndex);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700298 startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100299 }
300 break;
Selim Cinek547a06b2014-11-27 14:06:04 +0100301 case MotionEvent.ACTION_POINTER_DOWN:
302 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
303 mMotionAborted = true;
304 endMotionEvent(event, x, y, true /* forceCancel */);
305 return false;
306 }
307 break;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100308 case MotionEvent.ACTION_MOVE:
Selim Cinek4c6969a2014-05-26 19:22:17 +0200309 float h = y - mInitialTouchY;
Jorim Jaggib7240132014-06-30 01:39:07 +0200310
311 // If the panel was collapsed when touching, we only need to check for the
312 // y-component of the gesture, as we have no conflicting horizontal gesture.
313 if (Math.abs(h) > mTouchSlop
314 && (Math.abs(h) > Math.abs(x - mInitialTouchX)
Selim Cinek9db71052015-04-24 18:54:30 -0700315 || mIgnoreXTouchSlop)) {
Jorim Jaggi90129582014-06-02 14:44:49 +0200316 mTouchSlopExceeded = true;
Selim Cinek79d79c42015-05-21 16:14:45 -0700317 if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
Jorim Jaggi0b1528a2014-10-28 22:47:46 +0100318 if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700319 startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
Jorim Jaggib472b3472014-06-30 19:56:24 +0200320 h = 0;
321 }
Selim Cinekdbbcfbe2014-10-24 17:52:35 +0200322 cancelHeightAnimator();
Jorim Jaggib472b3472014-06-30 19:56:24 +0200323 removeCallbacks(mPeekRunnable);
324 mPeekPending = false;
Jorim Jaggi90129582014-06-02 14:44:49 +0200325 onTrackingStarted();
Selim Cinek4c6969a2014-05-26 19:22:17 +0200326 }
Selim Cinek4c6969a2014-05-26 19:22:17 +0200327 }
Jorim Jaggi93439da2014-06-30 23:53:39 +0200328 final float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
Selim Cinek4c6969a2014-05-26 19:22:17 +0200329 if (newHeight > mPeekHeight) {
Jorim Jaggib472b3472014-06-30 19:56:24 +0200330 if (mPeekAnimator != null) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100331 mPeekAnimator.cancel();
332 }
333 mJustPeeked = false;
334 }
Selim Cinek29ed3c92014-09-23 20:44:35 +0200335 if (-h >= getFalsingThreshold()) {
Selim Cinek19c8c702014-08-25 22:09:19 +0200336 mTouchAboveFalsingThreshold = true;
Selim Cinek9db71052015-04-24 18:54:30 -0700337 mUpwardsWhenTresholdReached = isDirectionUpwards(x, y);
Selim Cinek19c8c702014-08-25 22:09:19 +0200338 }
Selim Cinek9db71052015-04-24 18:54:30 -0700339 if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) {
Jorim Jaggicc693242014-06-14 03:04:35 +0000340 setExpandedHeightInternal(newHeight);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100341 }
342
343 trackMovement(event);
344 break;
345
346 case MotionEvent.ACTION_UP:
347 case MotionEvent.ACTION_CANCEL:
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100348 trackMovement(event);
Selim Cinek547a06b2014-11-27 14:06:04 +0100349 endMotionEvent(event, x, y, false /* forceCancel */);
350 break;
351 }
Selim Cinek9db71052015-04-24 18:54:30 -0700352 return !mGestureWaitForTouchSlop || mTracking;
353 }
354
355 /**
356 * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
357 * horizontal direction
358 */
359 private boolean isDirectionUpwards(float x, float y) {
360 float xDiff = x - mInitialTouchX;
361 float yDiff = y - mInitialTouchY;
362 if (yDiff >= 0) {
363 return false;
364 }
365 return Math.abs(yDiff) >= Math.abs(xDiff);
Selim Cinek547a06b2014-11-27 14:06:04 +0100366 }
367
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700368 protected void startExpandMotion(float newX, float newY, boolean startTracking,
369 float expandedHeight) {
370 mInitialOffsetOnTouch = expandedHeight;
371 mInitialTouchY = newY;
372 mInitialTouchX = newX;
373 if (startTracking) {
374 mTouchSlopExceeded = true;
375 onTrackingStarted();
376 }
377 }
378
Selim Cinek547a06b2014-11-27 14:06:04 +0100379 private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
380 mTrackingPointer = -1;
381 if ((mTracking && mTouchSlopExceeded)
382 || Math.abs(x - mInitialTouchX) > mTouchSlop
383 || Math.abs(y - mInitialTouchY) > mTouchSlop
384 || event.getActionMasked() == MotionEvent.ACTION_CANCEL
385 || forceCancel) {
386 float vel = 0f;
387 float vectorVel = 0f;
388 if (mVelocityTracker != null) {
389 mVelocityTracker.computeCurrentVelocity(1000);
390 vel = mVelocityTracker.getYVelocity();
391 vectorVel = (float) Math.hypot(
392 mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
393 }
Selim Cinek9db71052015-04-24 18:54:30 -0700394 boolean expand = flingExpands(vel, vectorVel, x, y)
Selim Cinek547a06b2014-11-27 14:06:04 +0100395 || event.getActionMasked() == MotionEvent.ACTION_CANCEL
396 || forceCancel;
Selim Cinek547a06b2014-11-27 14:06:04 +0100397 DozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
398 mStatusBar.isFalsingThresholdNeeded(),
Jorim Jaggi50ff3af2015-08-12 18:35:42 -0700399 mStatusBar.isWakeUpComingFromTouch());
Christoph Studerb0183992014-12-22 21:02:26 +0100400 // Log collapse gesture if on lock screen.
401 if (!expand && mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
402 float displayDensity = mStatusBar.getDisplayDensity();
403 int heightDp = (int) Math.abs((y - mInitialTouchY) / displayDensity);
404 int velocityDp = (int) Math.abs(vel / displayDensity);
405 EventLogTags.writeSysuiLockscreenGesture(
406 EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_UP_UNLOCK,
407 heightDp, velocityDp);
408 }
Selim Cinek9db71052015-04-24 18:54:30 -0700409 fling(vel, expand, isFalseTouch(x, y));
Jorim Jaggieb8f11a2015-05-20 18:42:16 -0700410 onTrackingStopped(expand);
Selim Cinek547a06b2014-11-27 14:06:04 +0100411 mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
412 if (mUpdateFlingOnLayout) {
413 mUpdateFlingVelocity = vel;
414 }
415 } else {
416 boolean expands = onEmptySpaceClick(mInitialTouchX);
417 onTrackingStopped(expands);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100418 }
Selim Cinek547a06b2014-11-27 14:06:04 +0100419
420 if (mVelocityTracker != null) {
421 mVelocityTracker.recycle();
422 mVelocityTracker = null;
423 }
424 mPeekTouching = false;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100425 }
426
Selim Cinek29ed3c92014-09-23 20:44:35 +0200427 private int getFalsingThreshold() {
Jorim Jaggi50ff3af2015-08-12 18:35:42 -0700428 float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
Selim Cinek29ed3c92014-09-23 20:44:35 +0200429 return (int) (mUnlockFalsingThreshold * factor);
430 }
431
Selim Cinek4c6969a2014-05-26 19:22:17 +0200432 protected abstract boolean hasConflictingGestures();
433
Selim Cinek9db71052015-04-24 18:54:30 -0700434 protected abstract boolean shouldGestureIgnoreXTouchSlop(float x, float y);
435
Jorim Jaggi2fbad7b2014-05-26 22:38:00 +0200436 protected void onTrackingStopped(boolean expand) {
Selim Cinek4c6969a2014-05-26 19:22:17 +0200437 mTracking = false;
Jorim Jaggi2fbad7b2014-05-26 22:38:00 +0200438 mBar.onTrackingStopped(PanelView.this, expand);
Jorim Jaggieb8f11a2015-05-20 18:42:16 -0700439 notifyBarPanelExpansionChanged();
Selim Cinek1685e632014-04-08 02:27:49 +0200440 }
441
442 protected void onTrackingStarted() {
Selim Cinekdbbcfbe2014-10-24 17:52:35 +0200443 endClosing();
Selim Cinek4c6969a2014-05-26 19:22:17 +0200444 mTracking = true;
Jorim Jaggib472b3472014-06-30 19:56:24 +0200445 mCollapseAfterPeek = false;
Selim Cinek1685e632014-04-08 02:27:49 +0200446 mBar.onTrackingStarted(PanelView.this);
Jorim Jaggib472b3472014-06-30 19:56:24 +0200447 notifyExpandingStarted();
Jorim Jaggieb8f11a2015-05-20 18:42:16 -0700448 notifyBarPanelExpansionChanged();
Selim Cinek1685e632014-04-08 02:27:49 +0200449 }
450
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100451 @Override
452 public boolean onInterceptTouchEvent(MotionEvent event) {
Selim Cinek547a06b2014-11-27 14:06:04 +0100453 if (mInstantExpanding
454 || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
Jorim Jaggi0a27be82014-06-11 03:22:39 +0200455 return false;
456 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100457
458 /*
Chris Wren5d53df42015-06-26 11:26:03 -0400459 * If the user drags anywhere inside the panel we intercept it if the movement is
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100460 * upwards. This allows closing the shade from anywhere inside the panel.
461 *
462 * We only do this if the current content is scrolled to the bottom,
463 * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling gesture
464 * possible.
465 */
466 int pointerIndex = event.findPointerIndex(mTrackingPointer);
467 if (pointerIndex < 0) {
468 pointerIndex = 0;
469 mTrackingPointer = event.getPointerId(pointerIndex);
470 }
Jorim Jaggia6310292014-04-16 14:11:52 +0200471 final float x = event.getX(pointerIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100472 final float y = event.getY(pointerIndex);
473 boolean scrolledToBottom = isScrolledToBottom();
474
475 switch (event.getActionMasked()) {
476 case MotionEvent.ACTION_DOWN:
Jorim Jaggib690f0d2014-07-03 23:25:44 +0200477 mStatusBar.userActivity();
Selim Cinek173f2d02015-04-29 15:36:56 -0700478 mAnimatingOnDown = mHeightAnimator != null;
479 if (mAnimatingOnDown && mClosing && !mHintAnimationRunning || mPeekPending || mPeekAnimator != null) {
Selim Cinekdbbcfbe2014-10-24 17:52:35 +0200480 cancelHeightAnimator();
Jorim Jaggib472b3472014-06-30 19:56:24 +0200481 cancelPeek();
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200482 mTouchSlopExceeded = true;
Selim Cinek172e9142014-05-07 19:38:00 +0200483 return true;
484 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100485 mInitialTouchY = y;
Jorim Jaggia6310292014-04-16 14:11:52 +0200486 mInitialTouchX = x;
Selim Cinekab1dc952014-10-30 20:20:29 +0100487 mTouchStartedInEmptyArea = !isInContentBounds(x, y);
Jorim Jaggi90129582014-06-02 14:44:49 +0200488 mTouchSlopExceeded = false;
Jorim Jaggi3857ac42014-06-27 18:01:12 +0200489 mJustPeeked = false;
Selim Cinek547a06b2014-11-27 14:06:04 +0100490 mMotionAborted = false;
Selim Cinek684a4422015-04-15 16:18:39 -0700491 mPanelClosedOnDown = isFullyCollapsed();
Selim Cinek79d79c42015-05-21 16:14:45 -0700492 mCollapsedAndHeadsUpOnDown = false;
Selim Cinek31094df2014-08-14 19:28:15 +0200493 mHasLayoutedSinceDown = false;
494 mUpdateFlingOnLayout = false;
Selim Cinek19c8c702014-08-25 22:09:19 +0200495 mTouchAboveFalsingThreshold = false;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100496 initVelocityTracker();
497 trackMovement(event);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100498 break;
499 case MotionEvent.ACTION_POINTER_UP:
500 final int upPointer = event.getPointerId(event.getActionIndex());
501 if (mTrackingPointer == upPointer) {
502 // gesture is ongoing, find a new pointer to track
503 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
504 mTrackingPointer = event.getPointerId(newIndex);
Jorim Jaggia6310292014-04-16 14:11:52 +0200505 mInitialTouchX = event.getX(newIndex);
506 mInitialTouchY = event.getY(newIndex);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100507 }
508 break;
Selim Cinek547a06b2014-11-27 14:06:04 +0100509 case MotionEvent.ACTION_POINTER_DOWN:
510 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
511 mMotionAborted = true;
512 if (mVelocityTracker != null) {
513 mVelocityTracker.recycle();
514 mVelocityTracker = null;
515 }
516 }
517 break;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100518 case MotionEvent.ACTION_MOVE:
519 final float h = y - mInitialTouchY;
520 trackMovement(event);
Selim Cinek173f2d02015-04-29 15:36:56 -0700521 if (scrolledToBottom || mTouchStartedInEmptyArea || mAnimatingOnDown) {
522 float hAbs = Math.abs(h);
523 if ((h < -mTouchSlop || (mAnimatingOnDown && hAbs > mTouchSlop))
524 && hAbs > Math.abs(x - mInitialTouchX)) {
Selim Cinekdbbcfbe2014-10-24 17:52:35 +0200525 cancelHeightAnimator();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700526 startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100527 return true;
528 }
529 }
530 break;
Selim Cinek31094df2014-08-14 19:28:15 +0200531 case MotionEvent.ACTION_CANCEL:
532 case MotionEvent.ACTION_UP:
Selim Cinek547a06b2014-11-27 14:06:04 +0100533 if (mVelocityTracker != null) {
534 mVelocityTracker.recycle();
535 mVelocityTracker = null;
536 }
Selim Cinek31094df2014-08-14 19:28:15 +0200537 break;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100538 }
539 return false;
540 }
541
Selim Cinekab1dc952014-10-30 20:20:29 +0100542 /**
543 * @return Whether a pair of coordinates are inside the visible view content bounds.
544 */
545 protected abstract boolean isInContentBounds(float x, float y);
546
Selim Cinek831941f2015-06-17 15:09:30 -0700547 protected void cancelHeightAnimator() {
Selim Cinekdbbcfbe2014-10-24 17:52:35 +0200548 if (mHeightAnimator != null) {
549 mHeightAnimator.cancel();
550 }
551 endClosing();
552 }
553
554 private void endClosing() {
555 if (mClosing) {
556 mClosing = false;
557 onClosingFinished();
558 }
559 }
560
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100561 private void initVelocityTracker() {
562 if (mVelocityTracker != null) {
563 mVelocityTracker.recycle();
564 }
Jorim Jaggib7b61dd2014-05-21 15:45:07 +0200565 mVelocityTracker = VelocityTrackerFactory.obtain(getContext());
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100566 }
567
568 protected boolean isScrolledToBottom() {
Selim Cinek172e9142014-05-07 19:38:00 +0200569 return true;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100570 }
571
572 protected float getContentHeight() {
573 return mExpandedHeight;
Daniel Sandlerbf526d12012-09-04 22:56:44 -0400574 }
575
Daniel Sandler08d05e32012-08-08 16:39:54 -0400576 @Override
577 protected void onFinishInflate() {
578 super.onFinishInflate();
579 loadDimens();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400580 }
581
Jorim Jaggi069cd032014-05-15 03:09:01 +0200582 @Override
583 protected void onConfigurationChanged(Configuration newConfig) {
584 super.onConfigurationChanged(newConfig);
585 loadDimens();
Jorim Jaggi069cd032014-05-15 03:09:01 +0200586 }
587
Jorim Jaggi2fbad7b2014-05-26 22:38:00 +0200588 /**
Jorim Jaggib7240132014-06-30 01:39:07 +0200589 * @param vel the current vertical velocity of the motion
590 * @param vectorVel the length of the vectorial velocity
Jorim Jaggie29b2db2014-05-30 23:17:03 +0200591 * @return whether a fling should expands the panel; contracts otherwise
Jorim Jaggi2fbad7b2014-05-26 22:38:00 +0200592 */
Selim Cinek9db71052015-04-24 18:54:30 -0700593 protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
594 if (isFalseTouch(x, y)) {
Selim Cinek19c8c702014-08-25 22:09:19 +0200595 return true;
596 }
Jorim Jaggib7240132014-06-30 01:39:07 +0200597 if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
Jorim Jaggie29b2db2014-05-30 23:17:03 +0200598 return getExpandedFraction() > 0.5f;
Selim Cinek1685e632014-04-08 02:27:49 +0200599 } else {
Jorim Jaggie29b2db2014-05-30 23:17:03 +0200600 return vel > 0;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400601 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200602 }
603
Selim Cinek9db71052015-04-24 18:54:30 -0700604 /**
605 * @param x the final x-coordinate when the finger was lifted
606 * @param y the final y-coordinate when the finger was lifted
607 * @return whether this motion should be regarded as a false touch
608 */
609 private boolean isFalseTouch(float x, float y) {
610 if (!mStatusBar.isFalsingThresholdNeeded()) {
611 return false;
612 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700613 if (mFalsingManager.isFalseTouch(Classifier.UNLOCK)) {
614 return true;
615 }
Selim Cinek9db71052015-04-24 18:54:30 -0700616 if (!mTouchAboveFalsingThreshold) {
617 return true;
618 }
619 if (mUpwardsWhenTresholdReached) {
620 return false;
621 }
622 return !isDirectionUpwards(x, y);
Selim Cinek5386fb32014-09-03 16:37:36 +0200623 }
624
Jorim Jaggi1d480692014-05-20 19:41:58 +0200625 protected void fling(float vel, boolean expand) {
Selim Cinek9db71052015-04-24 18:54:30 -0700626 fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700627 }
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +0200628
Selim Cinek9db71052015-04-24 18:54:30 -0700629 protected void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) {
630 fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing);
631 }
632
633 protected void fling(float vel, boolean expand, float collapseSpeedUpFactor,
634 boolean expandBecauseOfFalsing) {
Jorim Jaggif3b3bee2015-04-16 14:57:34 -0700635 cancelPeek();
636 float target = expand ? getMaxPanelHeight() : 0.0f;
Jorim Jaggi3e02adb2015-05-13 17:50:26 -0700637 if (!expand) {
638 mClosing = true;
639 }
Selim Cinek9db71052015-04-24 18:54:30 -0700640 flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
Jorim Jaggif3b3bee2015-04-16 14:57:34 -0700641 }
642
643 protected void flingToHeight(float vel, boolean expand, float target,
Selim Cinek9db71052015-04-24 18:54:30 -0700644 float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +0200645 // Hack to make the expand transition look nice when clear all button is visible - we make
646 // the animation only to the last notification, and then jump to the maximum panel height so
647 // clear all just fades in and the decelerating motion is towards the last notification.
648 final boolean clearAllExpandHack = expand && fullyExpandedClearAllVisible()
649 && mExpandedHeight < getMaxPanelHeight() - getClearAllHeight()
650 && !isClearAllVisible();
651 if (clearAllExpandHack) {
652 target = getMaxPanelHeight() - getClearAllHeight();
653 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200654 if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) {
Jorim Jaggib472b3472014-06-30 19:56:24 +0200655 notifyExpandingFinished();
Jorim Jaggi2fbad7b2014-05-26 22:38:00 +0200656 return;
657 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200658 mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
Jorim Jaggi90129582014-06-02 14:44:49 +0200659 ValueAnimator animator = createHeightAnimator(target);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200660 if (expand) {
Selim Cinek9db71052015-04-24 18:54:30 -0700661 if (expandBecauseOfFalsing) {
Selim Cinek5386fb32014-09-03 16:37:36 +0200662 vel = 0;
663 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200664 mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight());
Selim Cinek9db71052015-04-24 18:54:30 -0700665 if (expandBecauseOfFalsing) {
Selim Cinek5386fb32014-09-03 16:37:36 +0200666 animator.setDuration(350);
667 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200668 } else {
669 mFlingAnimationUtils.applyDismissing(animator, mExpandedHeight, target, vel,
670 getHeight());
671
672 // Make it shorter if we run a canned animation
673 if (vel == 0) {
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200674 animator.setDuration((long)
Jorim Jaggif3b3bee2015-04-16 14:57:34 -0700675 (animator.getDuration() * getCannedFlingDurationFactor()
676 / collapseSpeedUpFactor));
Jorim Jaggi1d480692014-05-20 19:41:58 +0200677 }
678 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200679 animator.addListener(new AnimatorListenerAdapter() {
Jorim Jaggib472b3472014-06-30 19:56:24 +0200680 private boolean mCancelled;
681
682 @Override
683 public void onAnimationCancel(Animator animation) {
684 mCancelled = true;
685 }
686
Jorim Jaggi1d480692014-05-20 19:41:58 +0200687 @Override
688 public void onAnimationEnd(Animator animation) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +0200689 if (clearAllExpandHack && !mCancelled) {
Jorim Jaggi2ae259d2014-08-04 23:35:47 +0200690 setExpandedHeightInternal(getMaxPanelHeight());
691 }
692 mHeightAnimator = null;
693 if (!mCancelled) {
694 notifyExpandingFinished();
Jorim Jaggib472b3472014-06-30 19:56:24 +0200695 }
Jorim Jaggieb8f11a2015-05-20 18:42:16 -0700696 notifyBarPanelExpansionChanged();
Jorim Jaggi1d480692014-05-20 19:41:58 +0200697 }
698 });
Jorim Jaggi1d480692014-05-20 19:41:58 +0200699 mHeightAnimator = animator;
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200700 animator.start();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400701 }
702
703 @Override
704 protected void onAttachedToWindow() {
705 super.onAttachedToWindow();
Daniel Sandler978f8532012-08-15 15:48:16 -0400706 mViewName = getResources().getResourceName(getId());
707 }
708
709 public String getName() {
710 return mViewName;
Daniel Sandler08d05e32012-08-08 16:39:54 -0400711 }
712
Daniel Sandler08d05e32012-08-08 16:39:54 -0400713 public void setExpandedHeight(float height) {
John Spurlock97642182013-07-29 17:58:39 -0400714 if (DEBUG) logf("setExpandedHeight(%.1f)", height);
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200715 setExpandedHeightInternal(height + getOverExpansionPixels());
Daniel Sandler08d05e32012-08-08 16:39:54 -0400716 }
717
Daniel Sandler50508132012-08-16 14:10:53 -0400718 @Override
719 protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
Daniel Sandler50508132012-08-16 14:10:53 -0400720 super.onLayout(changed, left, top, right, bottom);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100721 requestPanelHeightUpdate();
Selim Cinek31094df2014-08-14 19:28:15 +0200722 mHasLayoutedSinceDown = true;
723 if (mUpdateFlingOnLayout) {
724 abortAnimations();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700725 fling(mUpdateFlingVelocity, true /* expands */);
Selim Cinek31094df2014-08-14 19:28:15 +0200726 mUpdateFlingOnLayout = false;
727 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100728 }
729
730 protected void requestPanelHeightUpdate() {
731 float currentMaxPanelHeight = getMaxPanelHeight();
732
733 // If the user isn't actively poking us, let's update the height
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200734 if ((!mTracking || isTrackingBlocked())
735 && mHeightAnimator == null
Selim Cinek684a4422015-04-15 16:18:39 -0700736 && !isFullyCollapsed()
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200737 && currentMaxPanelHeight != mExpandedHeight
738 && !mPeekPending
Selim Cinek31094df2014-08-14 19:28:15 +0200739 && mPeekAnimator == null
Jorim Jaggib7a33032014-08-20 16:21:36 +0200740 && !mPeekTouching) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200741 setExpandedHeight(currentMaxPanelHeight);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100742 }
Daniel Sandler50508132012-08-16 14:10:53 -0400743 }
744
Daniel Sandler08d05e32012-08-08 16:39:54 -0400745 public void setExpandedHeightInternal(float h) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200746 float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount();
747 if (mHeightAnimator == null) {
748 float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion);
749 if (getOverExpansionPixels() != overExpansionPixels && mTracking) {
750 setOverExpansion(overExpansionPixels, true /* isPixels */);
751 }
752 mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount();
753 } else {
754 mExpandedHeight = h;
755 if (mOverExpandedBeforeFling) {
756 setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */);
757 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100758 }
Daniel Sandler198a0302012-08-17 16:04:31 -0400759
Jorim Jaggi93439da2014-06-30 23:53:39 +0200760 mExpandedHeight = Math.max(0, mExpandedHeight);
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200761 mExpandedFraction = Math.min(1f, fhWithoutOverExpansion == 0
762 ? 0
763 : mExpandedHeight / fhWithoutOverExpansion);
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100764 onHeightUpdated(mExpandedHeight);
Jorim Jaggib472b3472014-06-30 19:56:24 +0200765 notifyBarPanelExpansionChanged();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400766 }
767
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200768 /**
769 * @return true if the panel tracking should be temporarily blocked; this is used when a
770 * conflicting gesture (opening QS) is happening
771 */
772 protected abstract boolean isTrackingBlocked();
773
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200774 protected abstract void setOverExpansion(float overExpansion, boolean isPixels);
Selim Cinek24120a52014-05-26 10:05:42 +0200775
Jorim Jaggi90129582014-06-02 14:44:49 +0200776 protected abstract void onHeightUpdated(float expandedHeight);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100777
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200778 protected abstract float getOverExpansionAmount();
779
780 protected abstract float getOverExpansionPixels();
781
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100782 /**
783 * This returns the maximum height of the panel. Children should override this if their
784 * desired height is not the full height.
785 *
786 * @return the default implementation simply returns the maximum height.
787 */
Selim Cinek31094df2014-08-14 19:28:15 +0200788 protected abstract int getMaxPanelHeight();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400789
790 public void setExpandedFraction(float frac) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100791 setExpandedHeight(getMaxPanelHeight() * frac);
Daniel Sandler08d05e32012-08-08 16:39:54 -0400792 }
793
794 public float getExpandedHeight() {
795 return mExpandedHeight;
796 }
797
798 public float getExpandedFraction() {
799 return mExpandedFraction;
800 }
801
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700802 public boolean isFullyExpanded() {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100803 return mExpandedHeight >= getMaxPanelHeight();
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700804 }
805
806 public boolean isFullyCollapsed() {
Daniel Sandler67eab792012-10-02 17:08:23 -0400807 return mExpandedHeight <= 0;
808 }
809
810 public boolean isCollapsing() {
811 return mClosing;
Daniel Sandlerb4e56ed2012-09-12 23:07:44 -0700812 }
813
John Spurlocka4b70af2013-08-17 14:05:49 -0400814 public boolean isTracking() {
815 return mTracking;
816 }
817
Daniel Sandler08d05e32012-08-08 16:39:54 -0400818 public void setBar(PanelBar panelBar) {
819 mBar = panelBar;
820 }
821
Jorim Jaggif3b3bee2015-04-16 14:57:34 -0700822 public void collapse(boolean delayed, float speedUpFactor) {
John Spurlock97642182013-07-29 17:58:39 -0400823 if (DEBUG) logf("collapse: " + this);
Jorim Jaggib472b3472014-06-30 19:56:24 +0200824 if (mPeekPending || mPeekAnimator != null) {
825 mCollapseAfterPeek = true;
826 if (mPeekPending) {
827
828 // We know that the whole gesture is just a peek triggered by a simple click, so
829 // better start it now.
830 removeCallbacks(mPeekRunnable);
831 mPeekRunnable.run();
832 }
Jorim Jaggi98139552014-09-10 17:04:27 +0200833 } else if (!isFullyCollapsed() && !mTracking && !mClosing) {
Selim Cinekdbbcfbe2014-10-24 17:52:35 +0200834 cancelHeightAnimator();
Jorim Jaggib472b3472014-06-30 19:56:24 +0200835 notifyExpandingStarted();
Jorim Jaggi3b866be2015-06-30 11:31:10 -0700836
837 // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
838 mClosing = true;
Jorim Jaggi8de4311c2014-08-11 22:36:20 +0200839 if (delayed) {
Jorim Jaggif3b3bee2015-04-16 14:57:34 -0700840 mNextCollapseSpeedUpFactor = speedUpFactor;
Jorim Jaggi5cbfe542014-09-10 22:23:08 +0200841 postDelayed(mFlingCollapseRunnable, 120);
Jorim Jaggi8de4311c2014-08-11 22:36:20 +0200842 } else {
Selim Cinek9db71052015-04-24 18:54:30 -0700843 fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */);
Jorim Jaggi8de4311c2014-08-11 22:36:20 +0200844 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400845 }
846 }
847
Jorim Jaggi5cbfe542014-09-10 22:23:08 +0200848 private final Runnable mFlingCollapseRunnable = new Runnable() {
849 @Override
850 public void run() {
Selim Cinek9db71052015-04-24 18:54:30 -0700851 fling(0, false /* expand */, mNextCollapseSpeedUpFactor,
852 false /* expandBecauseOfFalsing */);
Jorim Jaggi5cbfe542014-09-10 22:23:08 +0200853 }
854 };
855
John Spurlock97642182013-07-29 17:58:39 -0400856 public void cancelPeek() {
Jorim Jaggib472b3472014-06-30 19:56:24 +0200857 if (mPeekAnimator != null) {
John Spurlock97642182013-07-29 17:58:39 -0400858 mPeekAnimator.cancel();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400859 }
Jorim Jaggib472b3472014-06-30 19:56:24 +0200860 removeCallbacks(mPeekRunnable);
861 mPeekPending = false;
862
863 // When peeking, we already tell mBar that we expanded ourselves. Make sure that we also
864 // notify mBar that we might have closed ourselves.
865 notifyBarPanelExpansionChanged();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400866 }
Daniel Sandler37a38aa2013-02-13 17:15:57 -0500867
Oren Blasberg8d3fea12015-07-10 14:21:44 -0700868 public void expand(final boolean animate) {
869 if (!isFullyCollapsed() && !isCollapsing()) {
870 return;
871 }
872
Jorim Jaggi0a27be82014-06-11 03:22:39 +0200873 mInstantExpanding = true;
Jorim Jaggi5cbfe542014-09-10 22:23:08 +0200874 mUpdateFlingOnLayout = false;
Jorim Jaggi0a27be82014-06-11 03:22:39 +0200875 abortAnimations();
Jorim Jaggi5cbfe542014-09-10 22:23:08 +0200876 cancelPeek();
Jorim Jaggi0a27be82014-06-11 03:22:39 +0200877 if (mTracking) {
878 onTrackingStopped(true /* expands */); // The panel is expanded after this call.
Jorim Jaggie62d5892014-09-02 16:31:19 +0200879 }
880 if (mExpanding) {
Jorim Jaggib472b3472014-06-30 19:56:24 +0200881 notifyExpandingFinished();
Jorim Jaggi0a27be82014-06-11 03:22:39 +0200882 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700883 notifyBarPanelExpansionChanged();
Jorim Jaggi0a27be82014-06-11 03:22:39 +0200884
885 // Wait for window manager to pickup the change, so we know the maximum height of the panel
886 // then.
887 getViewTreeObserver().addOnGlobalLayoutListener(
888 new ViewTreeObserver.OnGlobalLayoutListener() {
889 @Override
890 public void onGlobalLayout() {
891 if (mStatusBar.getStatusBarWindow().getHeight()
892 != mStatusBar.getStatusBarHeight()) {
893 getViewTreeObserver().removeOnGlobalLayoutListener(this);
Oren Blasberg8d3fea12015-07-10 14:21:44 -0700894 if (animate) {
895 mBar.startOpeningPanel(PanelView.this);
896 notifyExpandingStarted();
897 fling(0, true /* expand */);
898 } else {
899 setExpandedFraction(1f);
900 }
Jorim Jaggi0a27be82014-06-11 03:22:39 +0200901 mInstantExpanding = false;
902 }
903 }
904 });
905
906 // Make sure a layout really happens.
907 requestLayout();
908 }
909
Selim Cinek6bb4a9b2014-10-09 17:48:05 -0700910 public void instantCollapse() {
911 abortAnimations();
912 setExpandedFraction(0f);
913 if (mExpanding) {
914 notifyExpandingFinished();
915 }
916 }
917
Jorim Jaggi0a27be82014-06-11 03:22:39 +0200918 private void abortAnimations() {
919 cancelPeek();
Selim Cinekdbbcfbe2014-10-24 17:52:35 +0200920 cancelHeightAnimator();
Jorim Jaggi5cbfe542014-09-10 22:23:08 +0200921 removeCallbacks(mPostCollapseRunnable);
922 removeCallbacks(mFlingCollapseRunnable);
Jorim Jaggi0a27be82014-06-11 03:22:39 +0200923 }
924
Selim Cinekdbbcfbe2014-10-24 17:52:35 +0200925 protected void onClosingFinished() {
926 mBar.onClosingFinished();
927 }
928
929
Jorim Jaggi90129582014-06-02 14:44:49 +0200930 protected void startUnlockHintAnimation() {
931
932 // We don't need to hint the user if an animation is already running or the user is changing
933 // the expansion.
934 if (mHeightAnimator != null || mTracking) {
935 return;
936 }
937 cancelPeek();
Jorim Jaggib472b3472014-06-30 19:56:24 +0200938 notifyExpandingStarted();
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200939 startUnlockHintAnimationPhase1(new Runnable() {
940 @Override
941 public void run() {
Jorim Jaggib472b3472014-06-30 19:56:24 +0200942 notifyExpandingFinished();
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200943 mStatusBar.onHintFinished();
944 mHintAnimationRunning = false;
945 }
946 });
Jorim Jaggi90129582014-06-02 14:44:49 +0200947 mStatusBar.onUnlockHintStarted();
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200948 mHintAnimationRunning = true;
Jorim Jaggi90129582014-06-02 14:44:49 +0200949 }
950
951 /**
952 * Phase 1: Move everything upwards.
953 */
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200954 private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) {
Jorim Jaggi90129582014-06-02 14:44:49 +0200955 float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
956 ValueAnimator animator = createHeightAnimator(target);
957 animator.setDuration(250);
Jorim Jaggib472b3472014-06-30 19:56:24 +0200958 animator.setInterpolator(mFastOutSlowInInterpolator);
Jorim Jaggi90129582014-06-02 14:44:49 +0200959 animator.addListener(new AnimatorListenerAdapter() {
960 private boolean mCancelled;
961
962 @Override
963 public void onAnimationCancel(Animator animation) {
964 mCancelled = true;
965 }
966
967 @Override
968 public void onAnimationEnd(Animator animation) {
969 if (mCancelled) {
970 mHeightAnimator = null;
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200971 onAnimationFinished.run();
Jorim Jaggi90129582014-06-02 14:44:49 +0200972 } else {
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200973 startUnlockHintAnimationPhase2(onAnimationFinished);
Jorim Jaggi90129582014-06-02 14:44:49 +0200974 }
975 }
976 });
977 animator.start();
978 mHeightAnimator = animator;
Selim Cinekf99d0002014-06-13 07:36:01 +0200979 mKeyguardBottomArea.getIndicationView().animate()
Selim Cinek5256d932014-10-22 20:05:03 +0200980 .translationY(-mHintDistance)
Selim Cinekf99d0002014-06-13 07:36:01 +0200981 .setDuration(250)
Jorim Jaggib472b3472014-06-30 19:56:24 +0200982 .setInterpolator(mFastOutSlowInInterpolator)
Selim Cinekf99d0002014-06-13 07:36:01 +0200983 .withEndAction(new Runnable() {
984 @Override
985 public void run() {
986 mKeyguardBottomArea.getIndicationView().animate()
Selim Cinek5256d932014-10-22 20:05:03 +0200987 .translationY(0)
Selim Cinekf99d0002014-06-13 07:36:01 +0200988 .setDuration(450)
989 .setInterpolator(mBounceInterpolator)
990 .start();
991 }
992 })
993 .start();
Jorim Jaggi90129582014-06-02 14:44:49 +0200994 }
995
996 /**
997 * Phase 2: Bounce down.
998 */
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +0200999 private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
Jorim Jaggi90129582014-06-02 14:44:49 +02001000 ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
1001 animator.setDuration(450);
1002 animator.setInterpolator(mBounceInterpolator);
1003 animator.addListener(new AnimatorListenerAdapter() {
1004 @Override
1005 public void onAnimationEnd(Animator animation) {
1006 mHeightAnimator = null;
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +02001007 onAnimationFinished.run();
Jorim Jaggieb8f11a2015-05-20 18:42:16 -07001008 notifyBarPanelExpansionChanged();
Jorim Jaggi90129582014-06-02 14:44:49 +02001009 }
1010 });
1011 animator.start();
1012 mHeightAnimator = animator;
1013 }
1014
1015 private ValueAnimator createHeightAnimator(float targetHeight) {
1016 ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
1017 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
1018 @Override
1019 public void onAnimationUpdate(ValueAnimator animation) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +02001020 setExpandedHeightInternal((Float) animation.getAnimatedValue());
Jorim Jaggi90129582014-06-02 14:44:49 +02001021 }
1022 });
1023 return animator;
1024 }
1025
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001026 protected void notifyBarPanelExpansionChanged() {
Jorim Jaggib472b3472014-06-30 19:56:24 +02001027 mBar.panelExpansionChanged(this, mExpandedFraction, mExpandedFraction > 0f || mPeekPending
Selim Cinek0fccc722015-07-29 17:04:36 -07001028 || mPeekAnimator != null || mInstantExpanding || isPanelVisibleBecauseOfHeadsUp()
Jorim Jaggieb8f11a2015-05-20 18:42:16 -07001029 || mTracking || mHeightAnimator != null);
Jorim Jaggib472b3472014-06-30 19:56:24 +02001030 }
1031
Selim Cinek0fccc722015-07-29 17:04:36 -07001032 protected abstract boolean isPanelVisibleBecauseOfHeadsUp();
1033
Jorim Jaggi90129582014-06-02 14:44:49 +02001034 /**
1035 * Gets called when the user performs a click anywhere in the empty area of the panel.
1036 *
1037 * @return whether the panel will be expanded after the action performed by this method
1038 */
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001039 protected boolean onEmptySpaceClick(float x) {
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +02001040 if (mHintAnimationRunning) {
1041 return true;
1042 }
Selim Cinek6746c282015-04-21 19:58:31 -07001043 return onMiddleClicked();
Jorim Jaggib3f0a2f2014-06-02 19:29:39 +02001044 }
1045
Jorim Jaggi1cf6e102015-01-20 16:45:05 +01001046 protected final Runnable mPostCollapseRunnable = new Runnable() {
Jorim Jaggi488b7922014-08-05 21:12:02 +02001047 @Override
1048 public void run() {
Jorim Jaggif3b3bee2015-04-16 14:57:34 -07001049 collapse(false /* delayed */, 1.0f /* speedUpFactor */);
Jorim Jaggi488b7922014-08-05 21:12:02 +02001050 }
1051 };
Jorim Jaggi488b7922014-08-05 21:12:02 +02001052
Jorim Jaggi1cf6e102015-01-20 16:45:05 +01001053 protected abstract boolean onMiddleClicked();
Jorim Jaggi90129582014-06-02 14:44:49 +02001054
Jorim Jaggid7912d22014-09-30 17:38:19 +02001055 protected abstract boolean isDozing();
1056
Daniel Sandler37a38aa2013-02-13 17:15:57 -05001057 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Selim Cineka3e5bcb2014-04-07 20:07:22 +02001058 pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
Jorim Jaggi50d87a72014-09-12 21:43:35 +02001059 + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s touchDisabled=%s"
Daniel Sandler37a38aa2013-02-13 17:15:57 -05001060 + "]",
1061 this.getClass().getSimpleName(),
1062 getExpandedHeight(),
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001063 getMaxPanelHeight(),
Daniel Sandler37a38aa2013-02-13 17:15:57 -05001064 mClosing?"T":"f",
1065 mTracking?"T":"f",
Daniel Sandler37a38aa2013-02-13 17:15:57 -05001066 mJustPeeked?"T":"f",
1067 mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
Jorim Jaggi50d87a72014-09-12 21:43:35 +02001068 mHeightAnimator, ((mHeightAnimator !=null && mHeightAnimator.isStarted())?" (started)":""),
1069 mTouchDisabled?"T":"f"
Daniel Sandler37a38aa2013-02-13 17:15:57 -05001070 ));
1071 }
Selim Cinek3c4635c2014-05-29 02:12:47 +02001072
1073 public abstract void resetViews();
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001074
1075 protected abstract float getPeekHeight();
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001076
1077 protected abstract float getCannedFlingDurationFactor();
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02001078
1079 /**
1080 * @return whether "Clear all" button will be visible when the panel is fully expanded
1081 */
1082 protected abstract boolean fullyExpandedClearAllVisible();
1083
1084 protected abstract boolean isClearAllVisible();
1085
1086 /**
1087 * @return the height of the clear all button, in pixels
1088 */
1089 protected abstract int getClearAllHeight();
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001090
1091 public void setHeadsUpManager(HeadsUpManager headsUpManager) {
1092 mHeadsUpManager = headsUpManager;
1093 }
Daniel Sandler08d05e32012-08-08 16:39:54 -04001094}