blob: 50ea4bcd848c00416280366d0d9435662aa72d0c [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;
Jorim Jaggid7daab72014-05-06 22:22:20 +020022import android.animation.ValueAnimator;
Daniel Sandler08d05e32012-08-08 16:39:54 -040023import android.content.Context;
Jorim Jaggi7d16bb12014-05-22 04:24:50 +020024import android.graphics.Path;
Daniel Sandler08d05e32012-08-08 16:39:54 -040025import android.util.AttributeSet;
Jorim Jaggi7d16bb12014-05-22 04:24:50 +020026import android.util.Log;
Daniel Sandler040c2e42012-10-17 00:56:33 -040027import android.view.MotionEvent;
Jorim Jaggid7daab72014-05-06 22:22:20 +020028import android.view.VelocityTracker;
Daniel Sandler13522a22012-09-27 14:46:58 -040029import android.view.View;
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +020030import android.view.ViewGroup;
Jorim Jaggi75c95042014-05-16 19:09:59 +020031import android.view.ViewTreeObserver;
Casey Burkhardt23b0a4e2013-04-29 12:18:32 -070032import android.view.accessibility.AccessibilityEvent;
Jorim Jaggi75c95042014-05-16 19:09:59 +020033import android.view.animation.AnimationUtils;
34import android.view.animation.Interpolator;
Jorim Jaggi7d16bb12014-05-22 04:24:50 +020035import android.view.animation.PathInterpolator;
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +020036import android.widget.LinearLayout;
Daniel Sandler13522a22012-09-27 14:46:58 -040037
Chet Haase4d179dc2012-08-22 07:14:42 -070038import com.android.systemui.R;
Jorim Jaggibe565df2014-04-28 17:51:23 +020039import com.android.systemui.statusbar.ExpandableView;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020040import com.android.systemui.statusbar.FlingAnimationUtils;
Daniel Sandler151f00d2012-10-02 22:33:08 -040041import com.android.systemui.statusbar.GestureRecorder;
Jorim Jaggiecbab362014-04-23 16:13:15 +020042import com.android.systemui.statusbar.StatusBarState;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010043import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
Jorim Jaggi75c95042014-05-16 19:09:59 +020044import com.android.systemui.statusbar.stack.StackStateAnimator;
Daniel Sandler08d05e32012-08-08 16:39:54 -040045
Jorim Jaggibe565df2014-04-28 17:51:23 +020046public class NotificationPanelView extends PanelView implements
Jorim Jaggid7daab72014-05-06 22:22:20 +020047 ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
48 View.OnClickListener {
Chet Haase4d179dc2012-08-22 07:14:42 -070049
Daniel Sandler040c2e42012-10-17 00:56:33 -040050 PhoneStatusBar mStatusBar;
Jorim Jaggid7daab72014-05-06 22:22:20 +020051 private StatusBarHeaderView mHeader;
John Spurlockaf8d6c42014-05-07 17:49:08 -040052 private View mQsContainer;
John Spurlock73203eb2014-04-15 16:14:46 -040053 private View mKeyguardStatusView;
Jorim Jaggid7daab72014-05-06 22:22:20 +020054 private ObservableScrollView mScrollView;
55 private View mStackScrollerContainer;
John Spurlock73203eb2014-04-15 16:14:46 -040056
Selim Cinekb6d85eb2014-03-28 20:21:01 +010057 private NotificationStackScrollLayout mNotificationStackScroller;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +020058 private int mNotificationTopPadding;
Jorim Jaggi98fb09c2014-05-01 22:40:56 +020059 private boolean mAnimateNextTopPaddingChange;
Chet Haase4d179dc2012-08-22 07:14:42 -070060
Jorim Jaggid7daab72014-05-06 22:22:20 +020061 private int mTrackingPointer;
62 private VelocityTracker mVelocityTracker;
63 private boolean mTracking;
Jorim Jaggifc1a8242014-05-12 19:30:05 +020064
65 /**
66 * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't
67 * intercepted yet.
68 */
69 private boolean mIntercepting;
Jorim Jaggid7daab72014-05-06 22:22:20 +020070 private boolean mQsExpanded;
71 private float mInitialHeightOnTouch;
72 private float mInitialTouchX;
73 private float mInitialTouchY;
Jorim Jaggifc1a8242014-05-12 19:30:05 +020074 private float mLastTouchX;
75 private float mLastTouchY;
Jorim Jaggid7daab72014-05-06 22:22:20 +020076 private float mQsExpansionHeight;
77 private int mQsMinExpansionHeight;
78 private int mQsMaxExpansionHeight;
79 private int mMinStackHeight;
80 private float mNotificationTranslation;
81 private int mStackScrollerIntrinsicPadding;
82 private boolean mQsExpansionEnabled = true;
83 private ValueAnimator mQsExpansionAnimator;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020084 private FlingAnimationUtils mFlingAnimationUtils;
Selim Cinek9dbbca82014-05-15 15:23:37 +020085 private int mStatusBarMinHeight;
Jorim Jaggid7daab72014-05-06 22:22:20 +020086
Jorim Jaggi75c95042014-05-16 19:09:59 +020087 private Interpolator mFastOutSlowInInterpolator;
88 private ObjectAnimator mClockAnimator;
89 private int mClockAnimationTarget = -1;
Jorim Jaggi7d16bb12014-05-22 04:24:50 +020090 private int mTopPaddingAdjustment;
91 private KeyguardClockPositionAlgorithm mClockPositionAlgorithm =
92 new KeyguardClockPositionAlgorithm();
93 private KeyguardClockPositionAlgorithm.Result mClockPositionResult =
94 new KeyguardClockPositionAlgorithm.Result();
Jorim Jaggi069cd032014-05-15 03:09:01 +020095
Daniel Sandler08d05e32012-08-08 16:39:54 -040096 public NotificationPanelView(Context context, AttributeSet attrs) {
97 super(context, attrs);
Daniel Sandler13522a22012-09-27 14:46:58 -040098 }
Chet Haase4d179dc2012-08-22 07:14:42 -070099
Daniel Sandler040c2e42012-10-17 00:56:33 -0400100 public void setStatusBar(PhoneStatusBar bar) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100101 if (mStatusBar != null) {
102 mStatusBar.setOnFlipRunnable(null);
103 }
Daniel Sandler040c2e42012-10-17 00:56:33 -0400104 mStatusBar = bar;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100105 if (bar != null) {
106 mStatusBar.setOnFlipRunnable(new Runnable() {
107 @Override
108 public void run() {
109 requestPanelHeightUpdate();
110 }
111 });
112 }
Daniel Sandler040c2e42012-10-17 00:56:33 -0400113 }
114
Daniel Sandler13522a22012-09-27 14:46:58 -0400115 @Override
116 protected void onFinishInflate() {
117 super.onFinishInflate();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200118 mHeader = (StatusBarHeaderView) findViewById(R.id.header);
119 mHeader.getBackgroundView().setOnClickListener(this);
Jorim Jaggi3d878be2014-05-10 03:22:32 +0200120 mHeader.setOverlayParent(this);
John Spurlock73203eb2014-04-15 16:14:46 -0400121 mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200122 mStackScrollerContainer = findViewById(R.id.notification_container_parent);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400123 mQsContainer = findViewById(R.id.quick_settings_container);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200124 mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
125 mScrollView.setListener(this);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100126 mNotificationStackScroller = (NotificationStackScrollLayout)
127 findViewById(R.id.notification_stack_scroller);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200128 mNotificationStackScroller.setOnHeightChangedListener(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200129 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
130 android.R.interpolator.fast_out_slow_in);
Jorim Jaggi069cd032014-05-15 03:09:01 +0200131 }
132
133 @Override
134 protected void loadDimens() {
135 super.loadDimens();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200136 mNotificationTopPadding = getResources().getDimensionPixelSize(
137 R.dimen.notifications_top_padding);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200138 mMinStackHeight = getResources().getDimensionPixelSize(R.dimen.collapsed_stack_height);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200139 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f);
Selim Cinek9dbbca82014-05-15 15:23:37 +0200140 mStatusBarMinHeight = getResources().getDimensionPixelSize(
141 com.android.internal.R.dimen.status_bar_height);
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200142 mClockPositionAlgorithm.loadDimens(getResources());
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200143 }
144
145 @Override
146 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
147 super.onLayout(changed, left, top, right, bottom);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200148 if (!mQsExpanded) {
Jorim Jaggi069cd032014-05-15 03:09:01 +0200149 positionClockAndNotifications();
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200150 mNotificationStackScroller.setStackHeight(getExpandedHeight());
Jorim Jaggid7daab72014-05-06 22:22:20 +0200151 }
152
153 // Calculate quick setting heights.
154 mQsMinExpansionHeight = mHeader.getCollapsedHeight();
155 mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight();
156 if (mQsExpansionHeight == 0) {
157 mQsExpansionHeight = mQsMinExpansionHeight;
158 }
Jorim Jaggi98fb09c2014-05-01 22:40:56 +0200159 }
160
Jorim Jaggi069cd032014-05-15 03:09:01 +0200161 /**
162 * Positions the clock and notifications dynamically depending on how many notifications are
163 * showing.
164 */
165 private void positionClockAndNotifications() {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200166 boolean animateClock = mNotificationStackScroller.isAddOrRemoveAnimationPending();
Jorim Jaggi069cd032014-05-15 03:09:01 +0200167 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
168 mStackScrollerIntrinsicPadding = mHeader.getBottom() + mNotificationTopPadding;
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200169 mTopPaddingAdjustment = 0;
Jorim Jaggi069cd032014-05-15 03:09:01 +0200170 } else {
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200171 mClockPositionAlgorithm.setup(
172 mStatusBar.getMaxKeyguardNotifications(),
173 getMaxPanelHeight(),
174 getExpandedHeight(),
175 mNotificationStackScroller.getNotGoneChildCount(),
176 getHeight(),
177 mKeyguardStatusView.getHeight());
178 mClockPositionAlgorithm.run(mClockPositionResult);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200179 if (animateClock || mClockAnimator != null) {
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200180 startClockAnimation(mClockPositionResult.clockY);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200181 } else {
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200182 mKeyguardStatusView.setY(mClockPositionResult.clockY);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200183 }
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200184 applyClockAlpha(mClockPositionResult.clockAlpha);
185 mStackScrollerIntrinsicPadding = mClockPositionResult.stackScrollerPadding;
186 mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment;
Jorim Jaggi069cd032014-05-15 03:09:01 +0200187 }
188 mNotificationStackScroller.setTopPadding(mStackScrollerIntrinsicPadding,
Jorim Jaggi75c95042014-05-16 19:09:59 +0200189 mAnimateNextTopPaddingChange || animateClock);
Jorim Jaggi069cd032014-05-15 03:09:01 +0200190 mAnimateNextTopPaddingChange = false;
191 }
192
Jorim Jaggi75c95042014-05-16 19:09:59 +0200193 private void startClockAnimation(int y) {
194 if (mClockAnimationTarget == y) {
195 return;
196 }
197 mClockAnimationTarget = y;
198 getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
199 @Override
200 public boolean onPreDraw() {
201 getViewTreeObserver().removeOnPreDrawListener(this);
202 if (mClockAnimator != null) {
203 mClockAnimator.removeAllListeners();
204 mClockAnimator.cancel();
205 }
206 mClockAnimator =
207 ObjectAnimator.ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget);
208 mClockAnimator.setInterpolator(mFastOutSlowInInterpolator);
209 mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
210 mClockAnimator.addListener(new AnimatorListenerAdapter() {
211 @Override
212 public void onAnimationEnd(Animator animation) {
213 mClockAnimator = null;
214 mClockAnimationTarget = -1;
215 }
216 });
217 StackStateAnimator.startInstantly(mClockAnimator);
218 return true;
219 }
220 });
221 }
222
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200223 private void applyClockAlpha(float alpha) {
224 if (alpha != 1.0f) {
225 mKeyguardStatusView.setLayerType(LAYER_TYPE_HARDWARE, null);
226 } else {
227 mKeyguardStatusView.setLayerType(LAYER_TYPE_NONE, null);
228 }
229 mKeyguardStatusView.setAlpha(alpha);
Jorim Jaggi069cd032014-05-15 03:09:01 +0200230 }
231
Selim Cinekc27437b2014-05-14 10:23:33 +0200232 public void animateToFullShade() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +0200233 mAnimateNextTopPaddingChange = true;
Selim Cinekc27437b2014-05-14 10:23:33 +0200234 mNotificationStackScroller.goToFullShade();
Jorim Jaggi98fb09c2014-05-01 22:40:56 +0200235 requestLayout();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400236 }
237
Jorim Jaggid7daab72014-05-06 22:22:20 +0200238 /**
239 * @return Whether Quick Settings are currently expanded.
240 */
241 public boolean isQsExpanded() {
242 return mQsExpanded;
243 }
244
245 public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
246 mQsExpansionEnabled = qsExpansionEnabled;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200247 }
248
249 public void closeQs() {
250 cancelAnimation();
251 setQsExpansion(mQsMinExpansionHeight);
252 }
253
254 public void openQs() {
255 cancelAnimation();
256 if (mQsExpansionEnabled) {
257 setQsExpansion(mQsMaxExpansionHeight);
258 }
259 }
260
Daniel Sandler08d05e32012-08-08 16:39:54 -0400261 @Override
262 public void fling(float vel, boolean always) {
Daniel Sandler151f00d2012-10-02 22:33:08 -0400263 GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
264 if (gr != null) {
265 gr.tag(
266 "fling " + ((vel > 0) ? "open" : "closed"),
267 "notifications,v=" + vel);
268 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400269 super.fling(vel, always);
270 }
Chet Haase4d179dc2012-08-22 07:14:42 -0700271
Casey Burkhardt23b0a4e2013-04-29 12:18:32 -0700272 @Override
273 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
274 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
275 event.getText()
276 .add(getContext().getString(R.string.accessibility_desc_notification_shade));
277 return true;
278 }
279
280 return super.dispatchPopulateAccessibilityEvent(event);
281 }
282
Daniel Sandler040c2e42012-10-17 00:56:33 -0400283 @Override
John Spurlock73203eb2014-04-15 16:14:46 -0400284 public boolean onInterceptTouchEvent(MotionEvent event) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200285 int pointerIndex = event.findPointerIndex(mTrackingPointer);
286 if (pointerIndex < 0) {
287 pointerIndex = 0;
288 mTrackingPointer = event.getPointerId(pointerIndex);
John Spurlock73203eb2014-04-15 16:14:46 -0400289 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200290 final float x = event.getX(pointerIndex);
291 final float y = event.getY(pointerIndex);
292
293 switch (event.getActionMasked()) {
294 case MotionEvent.ACTION_DOWN:
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200295 mIntercepting = true;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200296 mInitialTouchY = y;
297 mInitialTouchX = x;
298 initVelocityTracker();
299 trackMovement(event);
300 if (shouldIntercept(mInitialTouchX, mInitialTouchY, 0)) {
301 getParent().requestDisallowInterceptTouchEvent(true);
302 }
303 break;
304 case MotionEvent.ACTION_POINTER_UP:
305 final int upPointer = event.getPointerId(event.getActionIndex());
306 if (mTrackingPointer == upPointer) {
307 // gesture is ongoing, find a new pointer to track
308 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
309 mTrackingPointer = event.getPointerId(newIndex);
310 mInitialTouchX = event.getX(newIndex);
311 mInitialTouchY = event.getY(newIndex);
312 }
313 break;
314
315 case MotionEvent.ACTION_MOVE:
316 final float h = y - mInitialTouchY;
317 trackMovement(event);
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200318 if (mTracking) {
319
320 // Already tracking because onOverscrolled was called. We need to update here
321 // so we don't stop for a frame until the next touch event gets handled in
322 // onTouchEvent.
323 setQsExpansion(h + mInitialHeightOnTouch);
324 trackMovement(event);
325 mIntercepting = false;
326 return true;
327 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200328 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
329 && shouldIntercept(mInitialTouchX, mInitialTouchY, h)) {
330 onQsExpansionStarted();
331 mInitialHeightOnTouch = mQsExpansionHeight;
332 mInitialTouchY = y;
333 mInitialTouchX = x;
334 mTracking = true;
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200335 mIntercepting = false;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200336 return true;
337 }
338 break;
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200339
340 case MotionEvent.ACTION_CANCEL:
341 case MotionEvent.ACTION_UP:
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200342 trackMovement(event);
343 if (mTracking) {
344 flingWithCurrentVelocity();
345 mTracking = false;
346 }
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200347 mIntercepting = false;
348 break;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200349 }
350 return !mQsExpanded && super.onInterceptTouchEvent(event);
John Spurlock73203eb2014-04-15 16:14:46 -0400351 }
352
353 @Override
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200354 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
355
Jorim Jaggice50b812014-05-24 16:29:57 +0200356 // Block request when interacting with the scroll view so we can still intercept the
357 // scrolling when QS is expanded.
358 if (mScrollView.isDispatchingTouchEvent()) {
359 return;
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200360 }
Jorim Jaggice50b812014-05-24 16:29:57 +0200361 super.requestDisallowInterceptTouchEvent(disallowIntercept);
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200362 }
363
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200364 private void flingWithCurrentVelocity() {
365 float vel = getCurrentVelocity();
366
367 // TODO: Better logic whether we should expand or not.
368 flingSettings(vel, vel > 0);
369 }
370
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200371 @Override
Daniel Sandler040c2e42012-10-17 00:56:33 -0400372 public boolean onTouchEvent(MotionEvent event) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100373 // TODO: Handle doublefinger swipe to notifications again. Look at history for a reference
374 // implementation.
Jorim Jaggid7daab72014-05-06 22:22:20 +0200375 if (mTracking) {
376 int pointerIndex = event.findPointerIndex(mTrackingPointer);
377 if (pointerIndex < 0) {
378 pointerIndex = 0;
379 mTrackingPointer = event.getPointerId(pointerIndex);
380 }
381 final float y = event.getY(pointerIndex);
382 final float x = event.getX(pointerIndex);
383
384 switch (event.getActionMasked()) {
385 case MotionEvent.ACTION_DOWN:
386 mTracking = true;
387 mInitialTouchY = y;
388 mInitialTouchX = x;
389 onQsExpansionStarted();
390 mInitialHeightOnTouch = mQsExpansionHeight;
391 initVelocityTracker();
392 trackMovement(event);
393 break;
394
395 case MotionEvent.ACTION_POINTER_UP:
396 final int upPointer = event.getPointerId(event.getActionIndex());
397 if (mTrackingPointer == upPointer) {
398 // gesture is ongoing, find a new pointer to track
399 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
400 final float newY = event.getY(newIndex);
401 final float newX = event.getX(newIndex);
402 mTrackingPointer = event.getPointerId(newIndex);
403 mInitialHeightOnTouch = mQsExpansionHeight;
404 mInitialTouchY = newY;
405 mInitialTouchX = newX;
406 }
407 break;
408
409 case MotionEvent.ACTION_MOVE:
410 final float h = y - mInitialTouchY;
411 setQsExpansion(h + mInitialHeightOnTouch);
412 trackMovement(event);
413 break;
414
415 case MotionEvent.ACTION_UP:
416 case MotionEvent.ACTION_CANCEL:
417 mTracking = false;
418 mTrackingPointer = -1;
419 trackMovement(event);
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200420 flingWithCurrentVelocity();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200421 if (mVelocityTracker != null) {
422 mVelocityTracker.recycle();
423 mVelocityTracker = null;
424 }
425 break;
John Spurlock73203eb2014-04-15 16:14:46 -0400426 }
427 return true;
428 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200429
430 // Consume touch events when QS are expanded.
431 return mQsExpanded || super.onTouchEvent(event);
432 }
433
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200434 @Override
435 public void onOverscrolled(int amount) {
436 if (mIntercepting) {
437 onQsExpansionStarted(amount);
438 mInitialHeightOnTouch = mQsExpansionHeight;
439 mInitialTouchY = mLastTouchY;
440 mInitialTouchX = mLastTouchX;
441 mTracking = true;
442 }
443 }
444
Jorim Jaggid7daab72014-05-06 22:22:20 +0200445 private void onQsExpansionStarted() {
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200446 onQsExpansionStarted(0);
447 }
448
449 private void onQsExpansionStarted(int overscrollAmount) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200450 cancelAnimation();
451
452 // Reset scroll position and apply that position to the expanded height.
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200453 float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200454 mScrollView.scrollTo(0, 0);
455 setQsExpansion(height);
456 }
457
458 private void expandQs() {
459 mHeader.setExpanded(true);
460 mNotificationStackScroller.setEnabled(false);
461 mScrollView.setVisibility(View.VISIBLE);
462 mQsExpanded = true;
463 }
464
465 private void collapseQs() {
466 mHeader.setExpanded(false);
467 mNotificationStackScroller.setEnabled(true);
468 mScrollView.setVisibility(View.INVISIBLE);
469 mQsExpanded = false;
470 }
471
472 private void setQsExpansion(float height) {
473 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
474 if (height > mQsMinExpansionHeight && !mQsExpanded) {
475 expandQs();
476 } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
477 collapseQs();
John Spurlock73203eb2014-04-15 16:14:46 -0400478 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200479 mQsExpansionHeight = height;
480 mHeader.setExpansion(height);
481 setQsTranslation(height);
482 setQsStackScrollerPadding(height);
483 }
484
485 private void setQsTranslation(float height) {
486 mQsContainer.setY(height - mQsContainer.getHeight());
487 }
488
489 private void setQsStackScrollerPadding(float height) {
490 float start = height - mScrollView.getScrollY() + mNotificationTopPadding;
491 float stackHeight = mNotificationStackScroller.getHeight() - start;
492 if (stackHeight <= mMinStackHeight) {
493 float overflow = mMinStackHeight - stackHeight;
494 stackHeight = mMinStackHeight;
495 start = mNotificationStackScroller.getHeight() - stackHeight;
496 mNotificationStackScroller.setTranslationY(overflow);
497 mNotificationTranslation = overflow + mScrollView.getScrollY();
498 } else {
499 mNotificationStackScroller.setTranslationY(0);
500 mNotificationTranslation = mScrollView.getScrollY();
501 }
502 mNotificationStackScroller.setTopPadding(clampQsStackScrollerPadding((int) start), false);
503 }
504
505 private int clampQsStackScrollerPadding(int desiredPadding) {
506 return Math.max(desiredPadding, mStackScrollerIntrinsicPadding);
507 }
508
509 private void trackMovement(MotionEvent event) {
510 if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200511 mLastTouchX = event.getX();
512 mLastTouchY = event.getY();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200513 }
514
515 private void initVelocityTracker() {
516 if (mVelocityTracker != null) {
517 mVelocityTracker.recycle();
518 }
519 mVelocityTracker = VelocityTracker.obtain();
520 }
521
522 private float getCurrentVelocity() {
523 if (mVelocityTracker == null) {
524 return 0;
525 }
526 mVelocityTracker.computeCurrentVelocity(1000);
527 return mVelocityTracker.getYVelocity();
528 }
529
530 private void cancelAnimation() {
531 if (mQsExpansionAnimator != null) {
532 mQsExpansionAnimator.cancel();
533 }
534 }
535 private void flingSettings(float vel, boolean expand) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200536 float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
Jorim Jaggi9cd731a2014-05-22 14:26:06 +0200537 if (target == mQsExpansionHeight) {
538 return;
539 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200540 ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200541 mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200542 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
543 @Override
544 public void onAnimationUpdate(ValueAnimator animation) {
545 setQsExpansion((Float) animation.getAnimatedValue());
546 }
547 });
548 animator.addListener(new AnimatorListenerAdapter() {
549 @Override
550 public void onAnimationEnd(Animator animation) {
551 mQsExpansionAnimator = null;
552 }
553 });
554 animator.start();
555 mQsExpansionAnimator = animator;
556 }
557
558 /**
559 * @return Whether we should intercept a gesture to open Quick Settings.
560 */
561 private boolean shouldIntercept(float x, float y, float yDiff) {
562 if (!mQsExpansionEnabled) {
563 return false;
564 }
Jorim Jaggibc5f4962014-05-12 00:00:52 +0200565 boolean onHeader = x >= mHeader.getLeft() && x <= mHeader.getRight()
566 && y >= mHeader.getTop() && y <= mHeader.getBottom();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200567 if (mQsExpanded) {
568 return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0);
569 } else {
570 return onHeader;
571 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100572 }
573
574 @Override
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +0200575 public void setVisibility(int visibility) {
576 int oldVisibility = getVisibility();
577 super.setVisibility(visibility);
578 if (visibility != oldVisibility) {
579 reparentStatusIcons(visibility == VISIBLE);
580 }
581 }
582
583 /**
584 * When the notification panel gets expanded, we need to move the status icons in the header
585 * card.
586 */
587 private void reparentStatusIcons(boolean toHeader) {
588 if (mStatusBar == null) {
589 return;
590 }
591 LinearLayout systemIcons = mStatusBar.getSystemIcons();
592 if (systemIcons.getParent() != null) {
593 ((ViewGroup) systemIcons.getParent()).removeView(systemIcons);
594 }
595 if (toHeader) {
596 mHeader.attachSystemIcons(systemIcons);
597 } else {
Jorim Jaggi13c1b1f2014-05-11 21:55:00 +0200598 mHeader.onSystemIconsDetached();
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +0200599 mStatusBar.reattachSystemIcons();
600 }
601 }
602
603 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100604 protected boolean isScrolledToBottom() {
605 if (!isInSettings()) {
606 return mNotificationStackScroller.isScrolledToBottom();
Chris Wren64161cc2012-12-17 16:49:30 -0500607 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100608 return super.isScrolledToBottom();
609 }
610
611 @Override
612 protected int getMaxPanelHeight() {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200613 // TODO: Figure out transition for collapsing when QS is open, adjust height here.
614 int maxPanelHeight = super.getMaxPanelHeight();
615 int emptyBottomMargin = mStackScrollerContainer.getHeight()
616 - mNotificationStackScroller.getHeight()
617 + mNotificationStackScroller.getEmptyBottomMargin();
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200618 int maxHeight = maxPanelHeight - emptyBottomMargin - mTopPaddingAdjustment;
Jorim Jaggi1d480692014-05-20 19:41:58 +0200619 maxHeight = Math.max(maxHeight, mStatusBarMinHeight);
620 return maxHeight;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100621 }
622
623 private boolean isInSettings() {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200624 return mQsExpanded;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100625 }
626
627 @Override
628 protected void onHeightUpdated(float expandedHeight) {
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200629 if (!mQsExpanded) {
630 positionClockAndNotifications();
631 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200632 mNotificationStackScroller.setStackHeight(expandedHeight);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100633 }
634
635 @Override
Selim Cinek1685e632014-04-08 02:27:49 +0200636 protected void onExpandingStarted() {
637 super.onExpandingStarted();
638 mNotificationStackScroller.onExpansionStarted();
639 }
640
641 @Override
642 protected void onExpandingFinished() {
643 super.onExpandingFinished();
644 mNotificationStackScroller.onExpansionStopped();
645 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200646
647 @Override
648 public void onHeightChanged(ExpandableView view) {
649 requestPanelHeightUpdate();
650 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200651
652 @Override
653 public void onScrollChanged() {
654 if (mQsExpanded) {
655 mNotificationStackScroller.setTranslationY(
656 mNotificationTranslation - mScrollView.getScrollY());
657 }
658 }
659
660 @Override
661 public void onClick(View v) {
662 if (v == mHeader.getBackgroundView()) {
663 onQsExpansionStarted();
664 if (mQsExpanded) {
665 flingSettings(0 /* vel */, false /* expand */);
666 } else if (mQsExpansionEnabled) {
667 flingSettings(0 /* vel */, true /* expand */);
668 }
669 }
670 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400671}