blob: 123a4f08589ace49be8ed62324009838e2a38996 [file] [log] [blame]
Daniel Sandler08d05e32012-08-08 16:39:54 -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
17package com.android.systemui.statusbar.phone;
18
Jorim Jaggid7daab72014-05-06 22:22:20 +020019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
Jorim Jaggi75c95042014-05-16 19:09:59 +020021import android.animation.ObjectAnimator;
22import android.animation.PropertyValuesHolder;
Jorim Jaggid7daab72014-05-06 22:22:20 +020023import android.animation.ValueAnimator;
Daniel Sandler08d05e32012-08-08 16:39:54 -040024import android.content.Context;
25import android.util.AttributeSet;
Daniel Sandler040c2e42012-10-17 00:56:33 -040026import android.view.MotionEvent;
Jorim Jaggid7daab72014-05-06 22:22:20 +020027import android.view.VelocityTracker;
Daniel Sandler13522a22012-09-27 14:46:58 -040028import android.view.View;
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +020029import android.view.ViewGroup;
Jorim Jaggi75c95042014-05-16 19:09:59 +020030import android.view.ViewTreeObserver;
Casey Burkhardt23b0a4e2013-04-29 12:18:32 -070031import android.view.accessibility.AccessibilityEvent;
Jorim Jaggi75c95042014-05-16 19:09:59 +020032import android.view.animation.AnimationUtils;
33import android.view.animation.Interpolator;
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +020034import android.widget.LinearLayout;
Daniel Sandler13522a22012-09-27 14:46:58 -040035
Chet Haase4d179dc2012-08-22 07:14:42 -070036import com.android.systemui.R;
Jorim Jaggibe565df2014-04-28 17:51:23 +020037import com.android.systemui.statusbar.ExpandableView;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020038import com.android.systemui.statusbar.FlingAnimationUtils;
Daniel Sandler151f00d2012-10-02 22:33:08 -040039import com.android.systemui.statusbar.GestureRecorder;
Jorim Jaggiecbab362014-04-23 16:13:15 +020040import com.android.systemui.statusbar.StatusBarState;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010041import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
Jorim Jaggi75c95042014-05-16 19:09:59 +020042import com.android.systemui.statusbar.stack.StackStateAnimator;
Daniel Sandler08d05e32012-08-08 16:39:54 -040043
Jorim Jaggibe565df2014-04-28 17:51:23 +020044public class NotificationPanelView extends PanelView implements
Jorim Jaggid7daab72014-05-06 22:22:20 +020045 ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
46 View.OnClickListener {
Chet Haase4d179dc2012-08-22 07:14:42 -070047
Daniel Sandler040c2e42012-10-17 00:56:33 -040048 PhoneStatusBar mStatusBar;
Jorim Jaggid7daab72014-05-06 22:22:20 +020049 private StatusBarHeaderView mHeader;
John Spurlockaf8d6c42014-05-07 17:49:08 -040050 private View mQsContainer;
John Spurlock73203eb2014-04-15 16:14:46 -040051 private View mKeyguardStatusView;
Jorim Jaggid7daab72014-05-06 22:22:20 +020052 private ObservableScrollView mScrollView;
53 private View mStackScrollerContainer;
John Spurlock73203eb2014-04-15 16:14:46 -040054
Selim Cinekb6d85eb2014-03-28 20:21:01 +010055 private NotificationStackScrollLayout mNotificationStackScroller;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +020056 private int mNotificationTopPadding;
Jorim Jaggi98fb09c2014-05-01 22:40:56 +020057 private boolean mAnimateNextTopPaddingChange;
Chet Haase4d179dc2012-08-22 07:14:42 -070058
Jorim Jaggid7daab72014-05-06 22:22:20 +020059 private int mTrackingPointer;
60 private VelocityTracker mVelocityTracker;
61 private boolean mTracking;
Jorim Jaggifc1a8242014-05-12 19:30:05 +020062
63 /**
64 * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't
65 * intercepted yet.
66 */
67 private boolean mIntercepting;
Jorim Jaggid7daab72014-05-06 22:22:20 +020068 private boolean mQsExpanded;
69 private float mInitialHeightOnTouch;
70 private float mInitialTouchX;
71 private float mInitialTouchY;
Jorim Jaggifc1a8242014-05-12 19:30:05 +020072 private float mLastTouchX;
73 private float mLastTouchY;
Jorim Jaggid7daab72014-05-06 22:22:20 +020074 private float mQsExpansionHeight;
75 private int mQsMinExpansionHeight;
76 private int mQsMaxExpansionHeight;
77 private int mMinStackHeight;
78 private float mNotificationTranslation;
79 private int mStackScrollerIntrinsicPadding;
80 private boolean mQsExpansionEnabled = true;
81 private ValueAnimator mQsExpansionAnimator;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020082 private FlingAnimationUtils mFlingAnimationUtils;
Selim Cinek9dbbca82014-05-15 15:23:37 +020083 private int mStatusBarMinHeight;
Jorim Jaggid7daab72014-05-06 22:22:20 +020084
Jorim Jaggi069cd032014-05-15 03:09:01 +020085 private int mClockNotificationsMarginMin;
86 private int mClockNotificationsMarginMax;
87 private float mClockYFractionMin;
88 private float mClockYFractionMax;
Jorim Jaggi75c95042014-05-16 19:09:59 +020089 private Interpolator mFastOutSlowInInterpolator;
90 private ObjectAnimator mClockAnimator;
91 private int mClockAnimationTarget = -1;
Jorim Jaggi069cd032014-05-15 03:09:01 +020092
93 /**
94 * The number (fractional) of notifications the "more" card counts when calculating how many
95 * notifications are currently visible for the y positioning of the clock.
96 */
97 private float mMoreCardNotificationAmount;
98
Daniel Sandler08d05e32012-08-08 16:39:54 -040099 public NotificationPanelView(Context context, AttributeSet attrs) {
100 super(context, attrs);
Daniel Sandler13522a22012-09-27 14:46:58 -0400101 }
Chet Haase4d179dc2012-08-22 07:14:42 -0700102
Daniel Sandler040c2e42012-10-17 00:56:33 -0400103 public void setStatusBar(PhoneStatusBar bar) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100104 if (mStatusBar != null) {
105 mStatusBar.setOnFlipRunnable(null);
106 }
Daniel Sandler040c2e42012-10-17 00:56:33 -0400107 mStatusBar = bar;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100108 if (bar != null) {
109 mStatusBar.setOnFlipRunnable(new Runnable() {
110 @Override
111 public void run() {
112 requestPanelHeightUpdate();
113 }
114 });
115 }
Daniel Sandler040c2e42012-10-17 00:56:33 -0400116 }
117
Daniel Sandler13522a22012-09-27 14:46:58 -0400118 @Override
119 protected void onFinishInflate() {
120 super.onFinishInflate();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200121 mHeader = (StatusBarHeaderView) findViewById(R.id.header);
122 mHeader.getBackgroundView().setOnClickListener(this);
Jorim Jaggi3d878be2014-05-10 03:22:32 +0200123 mHeader.setOverlayParent(this);
John Spurlock73203eb2014-04-15 16:14:46 -0400124 mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200125 mStackScrollerContainer = findViewById(R.id.notification_container_parent);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400126 mQsContainer = findViewById(R.id.quick_settings_container);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200127 mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
128 mScrollView.setListener(this);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100129 mNotificationStackScroller = (NotificationStackScrollLayout)
130 findViewById(R.id.notification_stack_scroller);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200131 mNotificationStackScroller.setOnHeightChangedListener(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200132 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
133 android.R.interpolator.fast_out_slow_in);
Jorim Jaggi069cd032014-05-15 03:09:01 +0200134 }
135
136 @Override
137 protected void loadDimens() {
138 super.loadDimens();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200139 mNotificationTopPadding = getResources().getDimensionPixelSize(
140 R.dimen.notifications_top_padding);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200141 mMinStackHeight = getResources().getDimensionPixelSize(R.dimen.collapsed_stack_height);
Jorim Jaggi069cd032014-05-15 03:09:01 +0200142 mClockNotificationsMarginMin = getResources().getDimensionPixelSize(
143 R.dimen.keyguard_clock_notifications_margin_min);
144 mClockNotificationsMarginMax = getResources().getDimensionPixelSize(
145 R.dimen.keyguard_clock_notifications_margin_max);
146 mClockYFractionMin =
147 getResources().getFraction(R.fraction.keyguard_clock_y_fraction_min, 1, 1);
148 mClockYFractionMax =
149 getResources().getFraction(R.fraction.keyguard_clock_y_fraction_max, 1, 1);
150 mMoreCardNotificationAmount =
151 (float) getResources().getDimensionPixelSize(R.dimen.notification_summary_height) /
152 getResources().getDimensionPixelSize(R.dimen.notification_min_height);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200153 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f);
Selim Cinek9dbbca82014-05-15 15:23:37 +0200154 mStatusBarMinHeight = getResources().getDimensionPixelSize(
155 com.android.internal.R.dimen.status_bar_height);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200156 }
157
158 @Override
159 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
160 super.onLayout(changed, left, top, right, bottom);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200161 if (!mQsExpanded) {
Jorim Jaggi069cd032014-05-15 03:09:01 +0200162 positionClockAndNotifications();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200163 }
164
165 // Calculate quick setting heights.
166 mQsMinExpansionHeight = mHeader.getCollapsedHeight();
167 mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight();
168 if (mQsExpansionHeight == 0) {
169 mQsExpansionHeight = mQsMinExpansionHeight;
170 }
Jorim Jaggi98fb09c2014-05-01 22:40:56 +0200171 }
172
Jorim Jaggi069cd032014-05-15 03:09:01 +0200173 /**
174 * Positions the clock and notifications dynamically depending on how many notifications are
175 * showing.
176 */
177 private void positionClockAndNotifications() {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200178 boolean animateClock = mNotificationStackScroller.isAddOrRemoveAnimationPending();
Jorim Jaggi069cd032014-05-15 03:09:01 +0200179 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
180 mStackScrollerIntrinsicPadding = mHeader.getBottom() + mNotificationTopPadding;
181 } else {
182 int notificationCount = mNotificationStackScroller.getNotGoneChildCount();
Jorim Jaggi75c95042014-05-16 19:09:59 +0200183 int y = getClockY(notificationCount) - mKeyguardStatusView.getHeight()/2;
Jorim Jaggi069cd032014-05-15 03:09:01 +0200184 int padding = getClockNotificationsPadding(notificationCount);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200185 if (animateClock || mClockAnimator != null) {
186 startClockAnimation(y);
187 } else {
188 mKeyguardStatusView.setY(y);
189 }
190 mStackScrollerIntrinsicPadding = y + mKeyguardStatusView.getHeight() + padding;
Jorim Jaggi069cd032014-05-15 03:09:01 +0200191 }
192 mNotificationStackScroller.setTopPadding(mStackScrollerIntrinsicPadding,
Jorim Jaggi75c95042014-05-16 19:09:59 +0200193 mAnimateNextTopPaddingChange || animateClock);
Jorim Jaggi069cd032014-05-15 03:09:01 +0200194 mAnimateNextTopPaddingChange = false;
195 }
196
Jorim Jaggi75c95042014-05-16 19:09:59 +0200197 private void startClockAnimation(int y) {
198 if (mClockAnimationTarget == y) {
199 return;
200 }
201 mClockAnimationTarget = y;
202 getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
203 @Override
204 public boolean onPreDraw() {
205 getViewTreeObserver().removeOnPreDrawListener(this);
206 if (mClockAnimator != null) {
207 mClockAnimator.removeAllListeners();
208 mClockAnimator.cancel();
209 }
210 mClockAnimator =
211 ObjectAnimator.ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget);
212 mClockAnimator.setInterpolator(mFastOutSlowInInterpolator);
213 mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
214 mClockAnimator.addListener(new AnimatorListenerAdapter() {
215 @Override
216 public void onAnimationEnd(Animator animation) {
217 mClockAnimator = null;
218 mClockAnimationTarget = -1;
219 }
220 });
221 StackStateAnimator.startInstantly(mClockAnimator);
222 return true;
223 }
224 });
225 }
226
Jorim Jaggi069cd032014-05-15 03:09:01 +0200227 private int getClockNotificationsPadding(int notificationCount) {
228 float t = notificationCount
229 / (mStatusBar.getMaxKeyguardNotifications() + mMoreCardNotificationAmount);
230 t = Math.min(t, 1.0f);
231 return (int) (t * mClockNotificationsMarginMin + (1 - t) * mClockNotificationsMarginMax);
232 }
233
234 private float getClockYFraction(int notificationCount) {
235 float t = notificationCount
236 / (mStatusBar.getMaxKeyguardNotifications() + mMoreCardNotificationAmount);
237 t = Math.min(t, 1.0f);
238 return (1 - t) * mClockYFractionMax + t * mClockYFractionMin;
239 }
240
241 private int getClockY(int notificationCount) {
242 return (int) (getClockYFraction(notificationCount) * getHeight());
243 }
244
Selim Cinekc27437b2014-05-14 10:23:33 +0200245 public void animateToFullShade() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +0200246 mAnimateNextTopPaddingChange = true;
Selim Cinekc27437b2014-05-14 10:23:33 +0200247 mNotificationStackScroller.goToFullShade();
Jorim Jaggi98fb09c2014-05-01 22:40:56 +0200248 requestLayout();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400249 }
250
Jorim Jaggid7daab72014-05-06 22:22:20 +0200251 /**
252 * @return Whether Quick Settings are currently expanded.
253 */
254 public boolean isQsExpanded() {
255 return mQsExpanded;
256 }
257
258 public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
259 mQsExpansionEnabled = qsExpansionEnabled;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200260 }
261
262 public void closeQs() {
263 cancelAnimation();
264 setQsExpansion(mQsMinExpansionHeight);
265 }
266
267 public void openQs() {
268 cancelAnimation();
269 if (mQsExpansionEnabled) {
270 setQsExpansion(mQsMaxExpansionHeight);
271 }
272 }
273
Daniel Sandler08d05e32012-08-08 16:39:54 -0400274 @Override
275 public void fling(float vel, boolean always) {
Daniel Sandler151f00d2012-10-02 22:33:08 -0400276 GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
277 if (gr != null) {
278 gr.tag(
279 "fling " + ((vel > 0) ? "open" : "closed"),
280 "notifications,v=" + vel);
281 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400282 super.fling(vel, always);
283 }
Chet Haase4d179dc2012-08-22 07:14:42 -0700284
Casey Burkhardt23b0a4e2013-04-29 12:18:32 -0700285 @Override
286 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
287 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
288 event.getText()
289 .add(getContext().getString(R.string.accessibility_desc_notification_shade));
290 return true;
291 }
292
293 return super.dispatchPopulateAccessibilityEvent(event);
294 }
295
Daniel Sandler040c2e42012-10-17 00:56:33 -0400296 @Override
John Spurlock73203eb2014-04-15 16:14:46 -0400297 public boolean onInterceptTouchEvent(MotionEvent event) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200298 int pointerIndex = event.findPointerIndex(mTrackingPointer);
299 if (pointerIndex < 0) {
300 pointerIndex = 0;
301 mTrackingPointer = event.getPointerId(pointerIndex);
John Spurlock73203eb2014-04-15 16:14:46 -0400302 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200303 final float x = event.getX(pointerIndex);
304 final float y = event.getY(pointerIndex);
305
306 switch (event.getActionMasked()) {
307 case MotionEvent.ACTION_DOWN:
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200308 mIntercepting = true;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200309 mInitialTouchY = y;
310 mInitialTouchX = x;
311 initVelocityTracker();
312 trackMovement(event);
313 if (shouldIntercept(mInitialTouchX, mInitialTouchY, 0)) {
314 getParent().requestDisallowInterceptTouchEvent(true);
315 }
316 break;
317 case MotionEvent.ACTION_POINTER_UP:
318 final int upPointer = event.getPointerId(event.getActionIndex());
319 if (mTrackingPointer == upPointer) {
320 // gesture is ongoing, find a new pointer to track
321 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
322 mTrackingPointer = event.getPointerId(newIndex);
323 mInitialTouchX = event.getX(newIndex);
324 mInitialTouchY = event.getY(newIndex);
325 }
326 break;
327
328 case MotionEvent.ACTION_MOVE:
329 final float h = y - mInitialTouchY;
330 trackMovement(event);
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200331 if (mTracking) {
332
333 // Already tracking because onOverscrolled was called. We need to update here
334 // so we don't stop for a frame until the next touch event gets handled in
335 // onTouchEvent.
336 setQsExpansion(h + mInitialHeightOnTouch);
337 trackMovement(event);
338 mIntercepting = false;
339 return true;
340 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200341 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
342 && shouldIntercept(mInitialTouchX, mInitialTouchY, h)) {
343 onQsExpansionStarted();
344 mInitialHeightOnTouch = mQsExpansionHeight;
345 mInitialTouchY = y;
346 mInitialTouchX = x;
347 mTracking = true;
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200348 mIntercepting = false;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200349 return true;
350 }
351 break;
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200352
353 case MotionEvent.ACTION_CANCEL:
354 case MotionEvent.ACTION_UP:
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200355 trackMovement(event);
356 if (mTracking) {
357 flingWithCurrentVelocity();
358 mTracking = false;
359 }
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200360 mIntercepting = false;
361 break;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200362 }
363 return !mQsExpanded && super.onInterceptTouchEvent(event);
John Spurlock73203eb2014-04-15 16:14:46 -0400364 }
365
366 @Override
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200367 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
368
369 // Block request so we can still intercept the scrolling when QS is expanded.
370 if (!mQsExpanded) {
371 super.requestDisallowInterceptTouchEvent(disallowIntercept);
372 }
373 }
374
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200375 private void flingWithCurrentVelocity() {
376 float vel = getCurrentVelocity();
377
378 // TODO: Better logic whether we should expand or not.
379 flingSettings(vel, vel > 0);
380 }
381
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200382 @Override
Daniel Sandler040c2e42012-10-17 00:56:33 -0400383 public boolean onTouchEvent(MotionEvent event) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100384 // TODO: Handle doublefinger swipe to notifications again. Look at history for a reference
385 // implementation.
Jorim Jaggid7daab72014-05-06 22:22:20 +0200386 if (mTracking) {
387 int pointerIndex = event.findPointerIndex(mTrackingPointer);
388 if (pointerIndex < 0) {
389 pointerIndex = 0;
390 mTrackingPointer = event.getPointerId(pointerIndex);
391 }
392 final float y = event.getY(pointerIndex);
393 final float x = event.getX(pointerIndex);
394
395 switch (event.getActionMasked()) {
396 case MotionEvent.ACTION_DOWN:
397 mTracking = true;
398 mInitialTouchY = y;
399 mInitialTouchX = x;
400 onQsExpansionStarted();
401 mInitialHeightOnTouch = mQsExpansionHeight;
402 initVelocityTracker();
403 trackMovement(event);
404 break;
405
406 case MotionEvent.ACTION_POINTER_UP:
407 final int upPointer = event.getPointerId(event.getActionIndex());
408 if (mTrackingPointer == upPointer) {
409 // gesture is ongoing, find a new pointer to track
410 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
411 final float newY = event.getY(newIndex);
412 final float newX = event.getX(newIndex);
413 mTrackingPointer = event.getPointerId(newIndex);
414 mInitialHeightOnTouch = mQsExpansionHeight;
415 mInitialTouchY = newY;
416 mInitialTouchX = newX;
417 }
418 break;
419
420 case MotionEvent.ACTION_MOVE:
421 final float h = y - mInitialTouchY;
422 setQsExpansion(h + mInitialHeightOnTouch);
423 trackMovement(event);
424 break;
425
426 case MotionEvent.ACTION_UP:
427 case MotionEvent.ACTION_CANCEL:
428 mTracking = false;
429 mTrackingPointer = -1;
430 trackMovement(event);
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200431 flingWithCurrentVelocity();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200432 if (mVelocityTracker != null) {
433 mVelocityTracker.recycle();
434 mVelocityTracker = null;
435 }
436 break;
John Spurlock73203eb2014-04-15 16:14:46 -0400437 }
438 return true;
439 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200440
441 // Consume touch events when QS are expanded.
442 return mQsExpanded || super.onTouchEvent(event);
443 }
444
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200445 @Override
446 public void onOverscrolled(int amount) {
447 if (mIntercepting) {
448 onQsExpansionStarted(amount);
449 mInitialHeightOnTouch = mQsExpansionHeight;
450 mInitialTouchY = mLastTouchY;
451 mInitialTouchX = mLastTouchX;
452 mTracking = true;
453 }
454 }
455
Jorim Jaggid7daab72014-05-06 22:22:20 +0200456 private void onQsExpansionStarted() {
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200457 onQsExpansionStarted(0);
458 }
459
460 private void onQsExpansionStarted(int overscrollAmount) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200461 cancelAnimation();
462
463 // Reset scroll position and apply that position to the expanded height.
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200464 float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200465 mScrollView.scrollTo(0, 0);
466 setQsExpansion(height);
467 }
468
469 private void expandQs() {
470 mHeader.setExpanded(true);
471 mNotificationStackScroller.setEnabled(false);
472 mScrollView.setVisibility(View.VISIBLE);
473 mQsExpanded = true;
474 }
475
476 private void collapseQs() {
477 mHeader.setExpanded(false);
478 mNotificationStackScroller.setEnabled(true);
479 mScrollView.setVisibility(View.INVISIBLE);
480 mQsExpanded = false;
481 }
482
483 private void setQsExpansion(float height) {
484 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
485 if (height > mQsMinExpansionHeight && !mQsExpanded) {
486 expandQs();
487 } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
488 collapseQs();
John Spurlock73203eb2014-04-15 16:14:46 -0400489 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200490 mQsExpansionHeight = height;
491 mHeader.setExpansion(height);
492 setQsTranslation(height);
493 setQsStackScrollerPadding(height);
494 }
495
496 private void setQsTranslation(float height) {
497 mQsContainer.setY(height - mQsContainer.getHeight());
498 }
499
500 private void setQsStackScrollerPadding(float height) {
501 float start = height - mScrollView.getScrollY() + mNotificationTopPadding;
502 float stackHeight = mNotificationStackScroller.getHeight() - start;
503 if (stackHeight <= mMinStackHeight) {
504 float overflow = mMinStackHeight - stackHeight;
505 stackHeight = mMinStackHeight;
506 start = mNotificationStackScroller.getHeight() - stackHeight;
507 mNotificationStackScroller.setTranslationY(overflow);
508 mNotificationTranslation = overflow + mScrollView.getScrollY();
509 } else {
510 mNotificationStackScroller.setTranslationY(0);
511 mNotificationTranslation = mScrollView.getScrollY();
512 }
513 mNotificationStackScroller.setTopPadding(clampQsStackScrollerPadding((int) start), false);
514 }
515
516 private int clampQsStackScrollerPadding(int desiredPadding) {
517 return Math.max(desiredPadding, mStackScrollerIntrinsicPadding);
518 }
519
520 private void trackMovement(MotionEvent event) {
521 if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200522 mLastTouchX = event.getX();
523 mLastTouchY = event.getY();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200524 }
525
526 private void initVelocityTracker() {
527 if (mVelocityTracker != null) {
528 mVelocityTracker.recycle();
529 }
530 mVelocityTracker = VelocityTracker.obtain();
531 }
532
533 private float getCurrentVelocity() {
534 if (mVelocityTracker == null) {
535 return 0;
536 }
537 mVelocityTracker.computeCurrentVelocity(1000);
538 return mVelocityTracker.getYVelocity();
539 }
540
541 private void cancelAnimation() {
542 if (mQsExpansionAnimator != null) {
543 mQsExpansionAnimator.cancel();
544 }
545 }
546 private void flingSettings(float vel, boolean expand) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200547 float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
Jorim Jaggi9cd731a2014-05-22 14:26:06 +0200548 if (target == mQsExpansionHeight) {
549 return;
550 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200551 ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200552 mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200553 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
554 @Override
555 public void onAnimationUpdate(ValueAnimator animation) {
556 setQsExpansion((Float) animation.getAnimatedValue());
557 }
558 });
559 animator.addListener(new AnimatorListenerAdapter() {
560 @Override
561 public void onAnimationEnd(Animator animation) {
562 mQsExpansionAnimator = null;
563 }
564 });
565 animator.start();
566 mQsExpansionAnimator = animator;
567 }
568
569 /**
570 * @return Whether we should intercept a gesture to open Quick Settings.
571 */
572 private boolean shouldIntercept(float x, float y, float yDiff) {
573 if (!mQsExpansionEnabled) {
574 return false;
575 }
Jorim Jaggibc5f4962014-05-12 00:00:52 +0200576 boolean onHeader = x >= mHeader.getLeft() && x <= mHeader.getRight()
577 && y >= mHeader.getTop() && y <= mHeader.getBottom();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200578 if (mQsExpanded) {
579 return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0);
580 } else {
581 return onHeader;
582 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100583 }
584
585 @Override
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +0200586 public void setVisibility(int visibility) {
587 int oldVisibility = getVisibility();
588 super.setVisibility(visibility);
589 if (visibility != oldVisibility) {
590 reparentStatusIcons(visibility == VISIBLE);
591 }
592 }
593
594 /**
595 * When the notification panel gets expanded, we need to move the status icons in the header
596 * card.
597 */
598 private void reparentStatusIcons(boolean toHeader) {
599 if (mStatusBar == null) {
600 return;
601 }
602 LinearLayout systemIcons = mStatusBar.getSystemIcons();
603 if (systemIcons.getParent() != null) {
604 ((ViewGroup) systemIcons.getParent()).removeView(systemIcons);
605 }
606 if (toHeader) {
607 mHeader.attachSystemIcons(systemIcons);
608 } else {
Jorim Jaggi13c1b1f2014-05-11 21:55:00 +0200609 mHeader.onSystemIconsDetached();
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +0200610 mStatusBar.reattachSystemIcons();
611 }
612 }
613
614 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100615 protected boolean isScrolledToBottom() {
616 if (!isInSettings()) {
617 return mNotificationStackScroller.isScrolledToBottom();
Chris Wren64161cc2012-12-17 16:49:30 -0500618 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100619 return super.isScrolledToBottom();
620 }
621
622 @Override
623 protected int getMaxPanelHeight() {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200624 // TODO: Figure out transition for collapsing when QS is open, adjust height here.
625 int maxPanelHeight = super.getMaxPanelHeight();
626 int emptyBottomMargin = mStackScrollerContainer.getHeight()
627 - mNotificationStackScroller.getHeight()
628 + mNotificationStackScroller.getEmptyBottomMargin();
629 int maxHeight = maxPanelHeight - emptyBottomMargin;
630 maxHeight = Math.max(maxHeight, mStatusBarMinHeight);
631 return maxHeight;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100632 }
633
634 private boolean isInSettings() {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200635 return mQsExpanded;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100636 }
637
638 @Override
639 protected void onHeightUpdated(float expandedHeight) {
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200640 mNotificationStackScroller.setStackHeight(expandedHeight);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100641 }
642
643 @Override
Selim Cinek1685e632014-04-08 02:27:49 +0200644 protected void onExpandingStarted() {
645 super.onExpandingStarted();
646 mNotificationStackScroller.onExpansionStarted();
647 }
648
649 @Override
650 protected void onExpandingFinished() {
651 super.onExpandingFinished();
652 mNotificationStackScroller.onExpansionStopped();
653 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200654
655 @Override
656 public void onHeightChanged(ExpandableView view) {
657 requestPanelHeightUpdate();
658 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200659
660 @Override
661 public void onScrollChanged() {
662 if (mQsExpanded) {
663 mNotificationStackScroller.setTranslationY(
664 mNotificationTranslation - mScrollView.getScrollY());
665 }
666 }
667
668 @Override
669 public void onClick(View v) {
670 if (v == mHeader.getBackgroundView()) {
671 onQsExpansionStarted();
672 if (mQsExpanded) {
673 flingSettings(0 /* vel */, false /* expand */);
674 } else if (mQsExpansionEnabled) {
675 flingSettings(0 /* vel */, true /* expand */);
676 }
677 }
678 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400679}