blob: 047983b1cf94d617a2d53de1d958bb77377ed0a6 [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;
21import android.animation.ValueAnimator;
Daniel Sandler08d05e32012-08-08 16:39:54 -040022import android.content.Context;
23import android.util.AttributeSet;
Daniel Sandler040c2e42012-10-17 00:56:33 -040024import android.view.MotionEvent;
Jorim Jaggid7daab72014-05-06 22:22:20 +020025import android.view.VelocityTracker;
Daniel Sandler13522a22012-09-27 14:46:58 -040026import android.view.View;
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +020027import android.view.ViewGroup;
Casey Burkhardt23b0a4e2013-04-29 12:18:32 -070028import android.view.accessibility.AccessibilityEvent;
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +020029import android.widget.LinearLayout;
Daniel Sandler13522a22012-09-27 14:46:58 -040030
Chet Haase4d179dc2012-08-22 07:14:42 -070031import com.android.systemui.R;
Jorim Jaggibe565df2014-04-28 17:51:23 +020032import com.android.systemui.statusbar.ExpandableView;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020033import com.android.systemui.statusbar.FlingAnimationUtils;
Daniel Sandler151f00d2012-10-02 22:33:08 -040034import com.android.systemui.statusbar.GestureRecorder;
Jorim Jaggiecbab362014-04-23 16:13:15 +020035import com.android.systemui.statusbar.StatusBarState;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010036import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
Daniel Sandler08d05e32012-08-08 16:39:54 -040037
Jorim Jaggibe565df2014-04-28 17:51:23 +020038public class NotificationPanelView extends PanelView implements
Jorim Jaggid7daab72014-05-06 22:22:20 +020039 ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
40 View.OnClickListener {
Chet Haase4d179dc2012-08-22 07:14:42 -070041
Daniel Sandler040c2e42012-10-17 00:56:33 -040042 PhoneStatusBar mStatusBar;
Jorim Jaggid7daab72014-05-06 22:22:20 +020043 private StatusBarHeaderView mHeader;
John Spurlockaf8d6c42014-05-07 17:49:08 -040044 private View mQsContainer;
John Spurlock73203eb2014-04-15 16:14:46 -040045 private View mKeyguardStatusView;
Jorim Jaggid7daab72014-05-06 22:22:20 +020046 private ObservableScrollView mScrollView;
47 private View mStackScrollerContainer;
John Spurlock73203eb2014-04-15 16:14:46 -040048
Selim Cinekb6d85eb2014-03-28 20:21:01 +010049 private NotificationStackScrollLayout mNotificationStackScroller;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +020050 private int mNotificationTopPadding;
Jorim Jaggi98fb09c2014-05-01 22:40:56 +020051 private boolean mAnimateNextTopPaddingChange;
Chet Haase4d179dc2012-08-22 07:14:42 -070052
Jorim Jaggid7daab72014-05-06 22:22:20 +020053 private int mTrackingPointer;
54 private VelocityTracker mVelocityTracker;
55 private boolean mTracking;
Jorim Jaggifc1a8242014-05-12 19:30:05 +020056
57 /**
58 * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't
59 * intercepted yet.
60 */
61 private boolean mIntercepting;
Jorim Jaggid7daab72014-05-06 22:22:20 +020062 private boolean mQsExpanded;
63 private float mInitialHeightOnTouch;
64 private float mInitialTouchX;
65 private float mInitialTouchY;
Jorim Jaggifc1a8242014-05-12 19:30:05 +020066 private float mLastTouchX;
67 private float mLastTouchY;
Jorim Jaggid7daab72014-05-06 22:22:20 +020068 private float mQsExpansionHeight;
69 private int mQsMinExpansionHeight;
70 private int mQsMaxExpansionHeight;
71 private int mMinStackHeight;
72 private float mNotificationTranslation;
73 private int mStackScrollerIntrinsicPadding;
74 private boolean mQsExpansionEnabled = true;
75 private ValueAnimator mQsExpansionAnimator;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020076 private FlingAnimationUtils mFlingAnimationUtils;
Selim Cinek9dbbca82014-05-15 15:23:37 +020077 private int mStatusBarMinHeight;
Jorim Jaggid7daab72014-05-06 22:22:20 +020078
Jorim Jaggi069cd032014-05-15 03:09:01 +020079 private int mClockNotificationsMarginMin;
80 private int mClockNotificationsMarginMax;
81 private float mClockYFractionMin;
82 private float mClockYFractionMax;
83
84 /**
85 * The number (fractional) of notifications the "more" card counts when calculating how many
86 * notifications are currently visible for the y positioning of the clock.
87 */
88 private float mMoreCardNotificationAmount;
89
Daniel Sandler08d05e32012-08-08 16:39:54 -040090 public NotificationPanelView(Context context, AttributeSet attrs) {
91 super(context, attrs);
Daniel Sandler13522a22012-09-27 14:46:58 -040092 }
Chet Haase4d179dc2012-08-22 07:14:42 -070093
Daniel Sandler040c2e42012-10-17 00:56:33 -040094 public void setStatusBar(PhoneStatusBar bar) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +010095 if (mStatusBar != null) {
96 mStatusBar.setOnFlipRunnable(null);
97 }
Daniel Sandler040c2e42012-10-17 00:56:33 -040098 mStatusBar = bar;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010099 if (bar != null) {
100 mStatusBar.setOnFlipRunnable(new Runnable() {
101 @Override
102 public void run() {
103 requestPanelHeightUpdate();
104 }
105 });
106 }
Daniel Sandler040c2e42012-10-17 00:56:33 -0400107 }
108
Daniel Sandler13522a22012-09-27 14:46:58 -0400109 @Override
110 protected void onFinishInflate() {
111 super.onFinishInflate();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200112 mHeader = (StatusBarHeaderView) findViewById(R.id.header);
113 mHeader.getBackgroundView().setOnClickListener(this);
Jorim Jaggi3d878be2014-05-10 03:22:32 +0200114 mHeader.setOverlayParent(this);
John Spurlock73203eb2014-04-15 16:14:46 -0400115 mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200116 mStackScrollerContainer = findViewById(R.id.notification_container_parent);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400117 mQsContainer = findViewById(R.id.quick_settings_container);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200118 mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
119 mScrollView.setListener(this);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100120 mNotificationStackScroller = (NotificationStackScrollLayout)
121 findViewById(R.id.notification_stack_scroller);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200122 mNotificationStackScroller.setOnHeightChangedListener(this);
Jorim Jaggi069cd032014-05-15 03:09:01 +0200123 }
124
125 @Override
126 protected void loadDimens() {
127 super.loadDimens();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200128 mNotificationTopPadding = getResources().getDimensionPixelSize(
129 R.dimen.notifications_top_padding);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200130 mMinStackHeight = getResources().getDimensionPixelSize(R.dimen.collapsed_stack_height);
Jorim Jaggi069cd032014-05-15 03:09:01 +0200131 mClockNotificationsMarginMin = getResources().getDimensionPixelSize(
132 R.dimen.keyguard_clock_notifications_margin_min);
133 mClockNotificationsMarginMax = getResources().getDimensionPixelSize(
134 R.dimen.keyguard_clock_notifications_margin_max);
135 mClockYFractionMin =
136 getResources().getFraction(R.fraction.keyguard_clock_y_fraction_min, 1, 1);
137 mClockYFractionMax =
138 getResources().getFraction(R.fraction.keyguard_clock_y_fraction_max, 1, 1);
139 mMoreCardNotificationAmount =
140 (float) getResources().getDimensionPixelSize(R.dimen.notification_summary_height) /
141 getResources().getDimensionPixelSize(R.dimen.notification_min_height);
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200142 mFlingAnimationUtils = new FlingAnimationUtils(getContext());
Selim Cinek9dbbca82014-05-15 15:23:37 +0200143 mStatusBarMinHeight = getResources().getDimensionPixelSize(
144 com.android.internal.R.dimen.status_bar_height);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200145 }
146
147 @Override
148 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
149 super.onLayout(changed, left, top, right, bottom);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200150 if (!mQsExpanded) {
Jorim Jaggi069cd032014-05-15 03:09:01 +0200151 positionClockAndNotifications();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200152 }
153
154 // Calculate quick setting heights.
155 mQsMinExpansionHeight = mHeader.getCollapsedHeight();
156 mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight();
157 if (mQsExpansionHeight == 0) {
158 mQsExpansionHeight = mQsMinExpansionHeight;
159 }
Jorim Jaggi98fb09c2014-05-01 22:40:56 +0200160 }
161
Jorim Jaggi069cd032014-05-15 03:09:01 +0200162 /**
163 * Positions the clock and notifications dynamically depending on how many notifications are
164 * showing.
165 */
166 private void positionClockAndNotifications() {
167 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
168 mStackScrollerIntrinsicPadding = mHeader.getBottom() + mNotificationTopPadding;
169 } else {
170 int notificationCount = mNotificationStackScroller.getNotGoneChildCount();
171 int y = getClockY(notificationCount);
172 int padding = getClockNotificationsPadding(notificationCount);
173 mKeyguardStatusView.setY(y - mKeyguardStatusView.getHeight()/2);
174 mStackScrollerIntrinsicPadding =
175 (int) (mKeyguardStatusView.getY() + mKeyguardStatusView.getHeight() + padding);
176 }
177 mNotificationStackScroller.setTopPadding(mStackScrollerIntrinsicPadding,
178 mAnimateNextTopPaddingChange);
179 mAnimateNextTopPaddingChange = false;
180 }
181
182 private int getClockNotificationsPadding(int notificationCount) {
183 float t = notificationCount
184 / (mStatusBar.getMaxKeyguardNotifications() + mMoreCardNotificationAmount);
185 t = Math.min(t, 1.0f);
186 return (int) (t * mClockNotificationsMarginMin + (1 - t) * mClockNotificationsMarginMax);
187 }
188
189 private float getClockYFraction(int notificationCount) {
190 float t = notificationCount
191 / (mStatusBar.getMaxKeyguardNotifications() + mMoreCardNotificationAmount);
192 t = Math.min(t, 1.0f);
193 return (1 - t) * mClockYFractionMax + t * mClockYFractionMin;
194 }
195
196 private int getClockY(int notificationCount) {
197 return (int) (getClockYFraction(notificationCount) * getHeight());
198 }
199
Selim Cinekc27437b2014-05-14 10:23:33 +0200200 public void animateToFullShade() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +0200201 mAnimateNextTopPaddingChange = true;
Selim Cinekc27437b2014-05-14 10:23:33 +0200202 mNotificationStackScroller.goToFullShade();
Jorim Jaggi98fb09c2014-05-01 22:40:56 +0200203 requestLayout();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400204 }
205
Jorim Jaggid7daab72014-05-06 22:22:20 +0200206 /**
207 * @return Whether Quick Settings are currently expanded.
208 */
209 public boolean isQsExpanded() {
210 return mQsExpanded;
211 }
212
213 public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
214 mQsExpansionEnabled = qsExpansionEnabled;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200215 }
216
217 public void closeQs() {
218 cancelAnimation();
219 setQsExpansion(mQsMinExpansionHeight);
220 }
221
222 public void openQs() {
223 cancelAnimation();
224 if (mQsExpansionEnabled) {
225 setQsExpansion(mQsMaxExpansionHeight);
226 }
227 }
228
Daniel Sandler08d05e32012-08-08 16:39:54 -0400229 @Override
230 public void fling(float vel, boolean always) {
Daniel Sandler151f00d2012-10-02 22:33:08 -0400231 GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
232 if (gr != null) {
233 gr.tag(
234 "fling " + ((vel > 0) ? "open" : "closed"),
235 "notifications,v=" + vel);
236 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400237 super.fling(vel, always);
238 }
Chet Haase4d179dc2012-08-22 07:14:42 -0700239
Casey Burkhardt23b0a4e2013-04-29 12:18:32 -0700240 @Override
241 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
242 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
243 event.getText()
244 .add(getContext().getString(R.string.accessibility_desc_notification_shade));
245 return true;
246 }
247
248 return super.dispatchPopulateAccessibilityEvent(event);
249 }
250
Daniel Sandler040c2e42012-10-17 00:56:33 -0400251 @Override
John Spurlock73203eb2014-04-15 16:14:46 -0400252 public boolean onInterceptTouchEvent(MotionEvent event) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200253 int pointerIndex = event.findPointerIndex(mTrackingPointer);
254 if (pointerIndex < 0) {
255 pointerIndex = 0;
256 mTrackingPointer = event.getPointerId(pointerIndex);
John Spurlock73203eb2014-04-15 16:14:46 -0400257 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200258 final float x = event.getX(pointerIndex);
259 final float y = event.getY(pointerIndex);
260
261 switch (event.getActionMasked()) {
262 case MotionEvent.ACTION_DOWN:
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200263 mIntercepting = true;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200264 mInitialTouchY = y;
265 mInitialTouchX = x;
266 initVelocityTracker();
267 trackMovement(event);
268 if (shouldIntercept(mInitialTouchX, mInitialTouchY, 0)) {
269 getParent().requestDisallowInterceptTouchEvent(true);
270 }
271 break;
272 case MotionEvent.ACTION_POINTER_UP:
273 final int upPointer = event.getPointerId(event.getActionIndex());
274 if (mTrackingPointer == upPointer) {
275 // gesture is ongoing, find a new pointer to track
276 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
277 mTrackingPointer = event.getPointerId(newIndex);
278 mInitialTouchX = event.getX(newIndex);
279 mInitialTouchY = event.getY(newIndex);
280 }
281 break;
282
283 case MotionEvent.ACTION_MOVE:
284 final float h = y - mInitialTouchY;
285 trackMovement(event);
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200286 if (mTracking) {
287
288 // Already tracking because onOverscrolled was called. We need to update here
289 // so we don't stop for a frame until the next touch event gets handled in
290 // onTouchEvent.
291 setQsExpansion(h + mInitialHeightOnTouch);
292 trackMovement(event);
293 mIntercepting = false;
294 return true;
295 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200296 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
297 && shouldIntercept(mInitialTouchX, mInitialTouchY, h)) {
298 onQsExpansionStarted();
299 mInitialHeightOnTouch = mQsExpansionHeight;
300 mInitialTouchY = y;
301 mInitialTouchX = x;
302 mTracking = true;
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200303 mIntercepting = false;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200304 return true;
305 }
306 break;
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200307
308 case MotionEvent.ACTION_CANCEL:
309 case MotionEvent.ACTION_UP:
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200310 trackMovement(event);
311 if (mTracking) {
312 flingWithCurrentVelocity();
313 mTracking = false;
314 }
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200315 mIntercepting = false;
316 break;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200317 }
318 return !mQsExpanded && super.onInterceptTouchEvent(event);
John Spurlock73203eb2014-04-15 16:14:46 -0400319 }
320
321 @Override
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200322 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
323
324 // Block request so we can still intercept the scrolling when QS is expanded.
325 if (!mQsExpanded) {
326 super.requestDisallowInterceptTouchEvent(disallowIntercept);
327 }
328 }
329
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200330 private void flingWithCurrentVelocity() {
331 float vel = getCurrentVelocity();
332
333 // TODO: Better logic whether we should expand or not.
334 flingSettings(vel, vel > 0);
335 }
336
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200337 @Override
Daniel Sandler040c2e42012-10-17 00:56:33 -0400338 public boolean onTouchEvent(MotionEvent event) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100339 // TODO: Handle doublefinger swipe to notifications again. Look at history for a reference
340 // implementation.
Jorim Jaggid7daab72014-05-06 22:22:20 +0200341 if (mTracking) {
342 int pointerIndex = event.findPointerIndex(mTrackingPointer);
343 if (pointerIndex < 0) {
344 pointerIndex = 0;
345 mTrackingPointer = event.getPointerId(pointerIndex);
346 }
347 final float y = event.getY(pointerIndex);
348 final float x = event.getX(pointerIndex);
349
350 switch (event.getActionMasked()) {
351 case MotionEvent.ACTION_DOWN:
352 mTracking = true;
353 mInitialTouchY = y;
354 mInitialTouchX = x;
355 onQsExpansionStarted();
356 mInitialHeightOnTouch = mQsExpansionHeight;
357 initVelocityTracker();
358 trackMovement(event);
359 break;
360
361 case MotionEvent.ACTION_POINTER_UP:
362 final int upPointer = event.getPointerId(event.getActionIndex());
363 if (mTrackingPointer == upPointer) {
364 // gesture is ongoing, find a new pointer to track
365 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
366 final float newY = event.getY(newIndex);
367 final float newX = event.getX(newIndex);
368 mTrackingPointer = event.getPointerId(newIndex);
369 mInitialHeightOnTouch = mQsExpansionHeight;
370 mInitialTouchY = newY;
371 mInitialTouchX = newX;
372 }
373 break;
374
375 case MotionEvent.ACTION_MOVE:
376 final float h = y - mInitialTouchY;
377 setQsExpansion(h + mInitialHeightOnTouch);
378 trackMovement(event);
379 break;
380
381 case MotionEvent.ACTION_UP:
382 case MotionEvent.ACTION_CANCEL:
383 mTracking = false;
384 mTrackingPointer = -1;
385 trackMovement(event);
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200386 flingWithCurrentVelocity();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200387 if (mVelocityTracker != null) {
388 mVelocityTracker.recycle();
389 mVelocityTracker = null;
390 }
391 break;
John Spurlock73203eb2014-04-15 16:14:46 -0400392 }
393 return true;
394 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200395
396 // Consume touch events when QS are expanded.
397 return mQsExpanded || super.onTouchEvent(event);
398 }
399
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200400 @Override
401 public void onOverscrolled(int amount) {
402 if (mIntercepting) {
403 onQsExpansionStarted(amount);
404 mInitialHeightOnTouch = mQsExpansionHeight;
405 mInitialTouchY = mLastTouchY;
406 mInitialTouchX = mLastTouchX;
407 mTracking = true;
408 }
409 }
410
Jorim Jaggid7daab72014-05-06 22:22:20 +0200411 private void onQsExpansionStarted() {
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200412 onQsExpansionStarted(0);
413 }
414
415 private void onQsExpansionStarted(int overscrollAmount) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200416 cancelAnimation();
417
418 // Reset scroll position and apply that position to the expanded height.
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200419 float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200420 mScrollView.scrollTo(0, 0);
421 setQsExpansion(height);
422 }
423
424 private void expandQs() {
425 mHeader.setExpanded(true);
426 mNotificationStackScroller.setEnabled(false);
427 mScrollView.setVisibility(View.VISIBLE);
428 mQsExpanded = true;
429 }
430
431 private void collapseQs() {
432 mHeader.setExpanded(false);
433 mNotificationStackScroller.setEnabled(true);
434 mScrollView.setVisibility(View.INVISIBLE);
435 mQsExpanded = false;
436 }
437
438 private void setQsExpansion(float height) {
439 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
440 if (height > mQsMinExpansionHeight && !mQsExpanded) {
441 expandQs();
442 } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
443 collapseQs();
John Spurlock73203eb2014-04-15 16:14:46 -0400444 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200445 mQsExpansionHeight = height;
446 mHeader.setExpansion(height);
447 setQsTranslation(height);
448 setQsStackScrollerPadding(height);
449 }
450
451 private void setQsTranslation(float height) {
452 mQsContainer.setY(height - mQsContainer.getHeight());
453 }
454
455 private void setQsStackScrollerPadding(float height) {
456 float start = height - mScrollView.getScrollY() + mNotificationTopPadding;
457 float stackHeight = mNotificationStackScroller.getHeight() - start;
458 if (stackHeight <= mMinStackHeight) {
459 float overflow = mMinStackHeight - stackHeight;
460 stackHeight = mMinStackHeight;
461 start = mNotificationStackScroller.getHeight() - stackHeight;
462 mNotificationStackScroller.setTranslationY(overflow);
463 mNotificationTranslation = overflow + mScrollView.getScrollY();
464 } else {
465 mNotificationStackScroller.setTranslationY(0);
466 mNotificationTranslation = mScrollView.getScrollY();
467 }
468 mNotificationStackScroller.setTopPadding(clampQsStackScrollerPadding((int) start), false);
469 }
470
471 private int clampQsStackScrollerPadding(int desiredPadding) {
472 return Math.max(desiredPadding, mStackScrollerIntrinsicPadding);
473 }
474
475 private void trackMovement(MotionEvent event) {
476 if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200477 mLastTouchX = event.getX();
478 mLastTouchY = event.getY();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200479 }
480
481 private void initVelocityTracker() {
482 if (mVelocityTracker != null) {
483 mVelocityTracker.recycle();
484 }
485 mVelocityTracker = VelocityTracker.obtain();
486 }
487
488 private float getCurrentVelocity() {
489 if (mVelocityTracker == null) {
490 return 0;
491 }
492 mVelocityTracker.computeCurrentVelocity(1000);
493 return mVelocityTracker.getYVelocity();
494 }
495
496 private void cancelAnimation() {
497 if (mQsExpansionAnimator != null) {
498 mQsExpansionAnimator.cancel();
499 }
500 }
501 private void flingSettings(float vel, boolean expand) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200502 float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
503 ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200504 mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200505 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
506 @Override
507 public void onAnimationUpdate(ValueAnimator animation) {
508 setQsExpansion((Float) animation.getAnimatedValue());
509 }
510 });
511 animator.addListener(new AnimatorListenerAdapter() {
512 @Override
513 public void onAnimationEnd(Animator animation) {
514 mQsExpansionAnimator = null;
515 }
516 });
517 animator.start();
518 mQsExpansionAnimator = animator;
519 }
520
521 /**
522 * @return Whether we should intercept a gesture to open Quick Settings.
523 */
524 private boolean shouldIntercept(float x, float y, float yDiff) {
525 if (!mQsExpansionEnabled) {
526 return false;
527 }
Jorim Jaggibc5f4962014-05-12 00:00:52 +0200528 boolean onHeader = x >= mHeader.getLeft() && x <= mHeader.getRight()
529 && y >= mHeader.getTop() && y <= mHeader.getBottom();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200530 if (mQsExpanded) {
531 return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0);
532 } else {
533 return onHeader;
534 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100535 }
536
537 @Override
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +0200538 public void setVisibility(int visibility) {
539 int oldVisibility = getVisibility();
540 super.setVisibility(visibility);
541 if (visibility != oldVisibility) {
542 reparentStatusIcons(visibility == VISIBLE);
543 }
544 }
545
546 /**
547 * When the notification panel gets expanded, we need to move the status icons in the header
548 * card.
549 */
550 private void reparentStatusIcons(boolean toHeader) {
551 if (mStatusBar == null) {
552 return;
553 }
554 LinearLayout systemIcons = mStatusBar.getSystemIcons();
555 if (systemIcons.getParent() != null) {
556 ((ViewGroup) systemIcons.getParent()).removeView(systemIcons);
557 }
558 if (toHeader) {
559 mHeader.attachSystemIcons(systemIcons);
560 } else {
Jorim Jaggi13c1b1f2014-05-11 21:55:00 +0200561 mHeader.onSystemIconsDetached();
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +0200562 mStatusBar.reattachSystemIcons();
563 }
564 }
565
566 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100567 protected boolean isScrolledToBottom() {
568 if (!isInSettings()) {
569 return mNotificationStackScroller.isScrolledToBottom();
Chris Wren64161cc2012-12-17 16:49:30 -0500570 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100571 return super.isScrolledToBottom();
572 }
573
574 @Override
575 protected int getMaxPanelHeight() {
576 if (!isInSettings()) {
577 int maxPanelHeight = super.getMaxPanelHeight();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200578 int notificationMarginBottom = mStackScrollerContainer.getPaddingBottom();
579 int emptyBottomMargin = notificationMarginBottom
580 + mNotificationStackScroller.getEmptyBottomMargin();
Selim Cinek9dbbca82014-05-15 15:23:37 +0200581 int maxHeight = maxPanelHeight - emptyBottomMargin;
582 maxHeight = Math.max(maxHeight, mStatusBarMinHeight);
583 return maxHeight;
Daniel Sandler040c2e42012-10-17 00:56:33 -0400584 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100585 return super.getMaxPanelHeight();
586 }
587
588 private boolean isInSettings() {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200589 return mQsExpanded;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100590 }
591
592 @Override
593 protected void onHeightUpdated(float expandedHeight) {
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200594 mNotificationStackScroller.setStackHeight(expandedHeight);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100595 }
596
597 @Override
598 protected int getDesiredMeasureHeight() {
599 return mMaxPanelHeight;
Daniel Sandler040c2e42012-10-17 00:56:33 -0400600 }
Selim Cinek1685e632014-04-08 02:27:49 +0200601
602 @Override
603 protected void onExpandingStarted() {
604 super.onExpandingStarted();
605 mNotificationStackScroller.onExpansionStarted();
606 }
607
608 @Override
609 protected void onExpandingFinished() {
610 super.onExpandingFinished();
611 mNotificationStackScroller.onExpansionStopped();
612 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200613
614 @Override
615 public void onHeightChanged(ExpandableView view) {
616 requestPanelHeightUpdate();
617 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200618
619 @Override
620 public void onScrollChanged() {
621 if (mQsExpanded) {
622 mNotificationStackScroller.setTranslationY(
623 mNotificationTranslation - mScrollView.getScrollY());
624 }
625 }
626
627 @Override
628 public void onClick(View v) {
629 if (v == mHeader.getBackgroundView()) {
630 onQsExpansionStarted();
631 if (mQsExpanded) {
632 flingSettings(0 /* vel */, false /* expand */);
633 } else if (mQsExpansionEnabled) {
634 flingSettings(0 /* vel */, true /* expand */);
635 }
636 }
637 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400638}