blob: 142241c83f4817d1534c347cfcb45cca40bfd4e6 [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 });
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200217 mClockAnimator.start();
Jorim Jaggi75c95042014-05-16 19:09:59 +0200218 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
356 // Block request so we can still intercept the scrolling when QS is expanded.
357 if (!mQsExpanded) {
358 super.requestDisallowInterceptTouchEvent(disallowIntercept);
359 }
360 }
361
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200362 private void flingWithCurrentVelocity() {
363 float vel = getCurrentVelocity();
364
365 // TODO: Better logic whether we should expand or not.
366 flingSettings(vel, vel > 0);
367 }
368
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200369 @Override
Daniel Sandler040c2e42012-10-17 00:56:33 -0400370 public boolean onTouchEvent(MotionEvent event) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100371 // TODO: Handle doublefinger swipe to notifications again. Look at history for a reference
372 // implementation.
Jorim Jaggid7daab72014-05-06 22:22:20 +0200373 if (mTracking) {
374 int pointerIndex = event.findPointerIndex(mTrackingPointer);
375 if (pointerIndex < 0) {
376 pointerIndex = 0;
377 mTrackingPointer = event.getPointerId(pointerIndex);
378 }
379 final float y = event.getY(pointerIndex);
380 final float x = event.getX(pointerIndex);
381
382 switch (event.getActionMasked()) {
383 case MotionEvent.ACTION_DOWN:
384 mTracking = true;
385 mInitialTouchY = y;
386 mInitialTouchX = x;
387 onQsExpansionStarted();
388 mInitialHeightOnTouch = mQsExpansionHeight;
389 initVelocityTracker();
390 trackMovement(event);
391 break;
392
393 case MotionEvent.ACTION_POINTER_UP:
394 final int upPointer = event.getPointerId(event.getActionIndex());
395 if (mTrackingPointer == upPointer) {
396 // gesture is ongoing, find a new pointer to track
397 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
398 final float newY = event.getY(newIndex);
399 final float newX = event.getX(newIndex);
400 mTrackingPointer = event.getPointerId(newIndex);
401 mInitialHeightOnTouch = mQsExpansionHeight;
402 mInitialTouchY = newY;
403 mInitialTouchX = newX;
404 }
405 break;
406
407 case MotionEvent.ACTION_MOVE:
408 final float h = y - mInitialTouchY;
409 setQsExpansion(h + mInitialHeightOnTouch);
410 trackMovement(event);
411 break;
412
413 case MotionEvent.ACTION_UP:
414 case MotionEvent.ACTION_CANCEL:
415 mTracking = false;
416 mTrackingPointer = -1;
417 trackMovement(event);
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200418 flingWithCurrentVelocity();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200419 if (mVelocityTracker != null) {
420 mVelocityTracker.recycle();
421 mVelocityTracker = null;
422 }
423 break;
John Spurlock73203eb2014-04-15 16:14:46 -0400424 }
425 return true;
426 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200427
428 // Consume touch events when QS are expanded.
429 return mQsExpanded || super.onTouchEvent(event);
430 }
431
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200432 @Override
433 public void onOverscrolled(int amount) {
434 if (mIntercepting) {
435 onQsExpansionStarted(amount);
436 mInitialHeightOnTouch = mQsExpansionHeight;
437 mInitialTouchY = mLastTouchY;
438 mInitialTouchX = mLastTouchX;
439 mTracking = true;
440 }
441 }
442
Jorim Jaggid7daab72014-05-06 22:22:20 +0200443 private void onQsExpansionStarted() {
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200444 onQsExpansionStarted(0);
445 }
446
447 private void onQsExpansionStarted(int overscrollAmount) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200448 cancelAnimation();
449
450 // Reset scroll position and apply that position to the expanded height.
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200451 float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200452 mScrollView.scrollTo(0, 0);
453 setQsExpansion(height);
454 }
455
456 private void expandQs() {
457 mHeader.setExpanded(true);
458 mNotificationStackScroller.setEnabled(false);
459 mScrollView.setVisibility(View.VISIBLE);
460 mQsExpanded = true;
461 }
462
463 private void collapseQs() {
464 mHeader.setExpanded(false);
465 mNotificationStackScroller.setEnabled(true);
466 mScrollView.setVisibility(View.INVISIBLE);
467 mQsExpanded = false;
468 }
469
470 private void setQsExpansion(float height) {
471 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
472 if (height > mQsMinExpansionHeight && !mQsExpanded) {
473 expandQs();
474 } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
475 collapseQs();
John Spurlock73203eb2014-04-15 16:14:46 -0400476 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200477 mQsExpansionHeight = height;
478 mHeader.setExpansion(height);
479 setQsTranslation(height);
480 setQsStackScrollerPadding(height);
481 }
482
483 private void setQsTranslation(float height) {
484 mQsContainer.setY(height - mQsContainer.getHeight());
485 }
486
487 private void setQsStackScrollerPadding(float height) {
488 float start = height - mScrollView.getScrollY() + mNotificationTopPadding;
489 float stackHeight = mNotificationStackScroller.getHeight() - start;
490 if (stackHeight <= mMinStackHeight) {
491 float overflow = mMinStackHeight - stackHeight;
492 stackHeight = mMinStackHeight;
493 start = mNotificationStackScroller.getHeight() - stackHeight;
494 mNotificationStackScroller.setTranslationY(overflow);
495 mNotificationTranslation = overflow + mScrollView.getScrollY();
496 } else {
497 mNotificationStackScroller.setTranslationY(0);
498 mNotificationTranslation = mScrollView.getScrollY();
499 }
500 mNotificationStackScroller.setTopPadding(clampQsStackScrollerPadding((int) start), false);
501 }
502
503 private int clampQsStackScrollerPadding(int desiredPadding) {
504 return Math.max(desiredPadding, mStackScrollerIntrinsicPadding);
505 }
506
507 private void trackMovement(MotionEvent event) {
508 if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200509 mLastTouchX = event.getX();
510 mLastTouchY = event.getY();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200511 }
512
513 private void initVelocityTracker() {
514 if (mVelocityTracker != null) {
515 mVelocityTracker.recycle();
516 }
517 mVelocityTracker = VelocityTracker.obtain();
518 }
519
520 private float getCurrentVelocity() {
521 if (mVelocityTracker == null) {
522 return 0;
523 }
524 mVelocityTracker.computeCurrentVelocity(1000);
525 return mVelocityTracker.getYVelocity();
526 }
527
528 private void cancelAnimation() {
529 if (mQsExpansionAnimator != null) {
530 mQsExpansionAnimator.cancel();
531 }
532 }
533 private void flingSettings(float vel, boolean expand) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200534 float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
Jorim Jaggi9cd731a2014-05-22 14:26:06 +0200535 if (target == mQsExpansionHeight) {
536 return;
537 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200538 ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200539 mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200540 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
541 @Override
542 public void onAnimationUpdate(ValueAnimator animation) {
543 setQsExpansion((Float) animation.getAnimatedValue());
544 }
545 });
546 animator.addListener(new AnimatorListenerAdapter() {
547 @Override
548 public void onAnimationEnd(Animator animation) {
549 mQsExpansionAnimator = null;
550 }
551 });
552 animator.start();
553 mQsExpansionAnimator = animator;
554 }
555
556 /**
557 * @return Whether we should intercept a gesture to open Quick Settings.
558 */
559 private boolean shouldIntercept(float x, float y, float yDiff) {
560 if (!mQsExpansionEnabled) {
561 return false;
562 }
Jorim Jaggibc5f4962014-05-12 00:00:52 +0200563 boolean onHeader = x >= mHeader.getLeft() && x <= mHeader.getRight()
564 && y >= mHeader.getTop() && y <= mHeader.getBottom();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200565 if (mQsExpanded) {
566 return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0);
567 } else {
568 return onHeader;
569 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100570 }
571
572 @Override
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +0200573 public void setVisibility(int visibility) {
574 int oldVisibility = getVisibility();
575 super.setVisibility(visibility);
576 if (visibility != oldVisibility) {
577 reparentStatusIcons(visibility == VISIBLE);
578 }
579 }
580
581 /**
582 * When the notification panel gets expanded, we need to move the status icons in the header
583 * card.
584 */
585 private void reparentStatusIcons(boolean toHeader) {
586 if (mStatusBar == null) {
587 return;
588 }
589 LinearLayout systemIcons = mStatusBar.getSystemIcons();
590 if (systemIcons.getParent() != null) {
591 ((ViewGroup) systemIcons.getParent()).removeView(systemIcons);
592 }
593 if (toHeader) {
594 mHeader.attachSystemIcons(systemIcons);
595 } else {
Jorim Jaggi13c1b1f2014-05-11 21:55:00 +0200596 mHeader.onSystemIconsDetached();
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +0200597 mStatusBar.reattachSystemIcons();
598 }
599 }
600
601 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100602 protected boolean isScrolledToBottom() {
603 if (!isInSettings()) {
604 return mNotificationStackScroller.isScrolledToBottom();
Chris Wren64161cc2012-12-17 16:49:30 -0500605 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100606 return super.isScrolledToBottom();
607 }
608
609 @Override
610 protected int getMaxPanelHeight() {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200611 // TODO: Figure out transition for collapsing when QS is open, adjust height here.
612 int maxPanelHeight = super.getMaxPanelHeight();
613 int emptyBottomMargin = mStackScrollerContainer.getHeight()
614 - mNotificationStackScroller.getHeight()
615 + mNotificationStackScroller.getEmptyBottomMargin();
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200616 int maxHeight = maxPanelHeight - emptyBottomMargin - mTopPaddingAdjustment;
Jorim Jaggi1d480692014-05-20 19:41:58 +0200617 maxHeight = Math.max(maxHeight, mStatusBarMinHeight);
618 return maxHeight;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100619 }
620
621 private boolean isInSettings() {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200622 return mQsExpanded;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100623 }
624
625 @Override
626 protected void onHeightUpdated(float expandedHeight) {
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200627 if (!mQsExpanded) {
628 positionClockAndNotifications();
629 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200630 mNotificationStackScroller.setStackHeight(expandedHeight);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100631 }
632
633 @Override
Selim Cinek1685e632014-04-08 02:27:49 +0200634 protected void onExpandingStarted() {
635 super.onExpandingStarted();
636 mNotificationStackScroller.onExpansionStarted();
637 }
638
639 @Override
640 protected void onExpandingFinished() {
641 super.onExpandingFinished();
642 mNotificationStackScroller.onExpansionStopped();
643 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200644
645 @Override
646 public void onHeightChanged(ExpandableView view) {
647 requestPanelHeightUpdate();
648 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200649
650 @Override
651 public void onScrollChanged() {
652 if (mQsExpanded) {
653 mNotificationStackScroller.setTranslationY(
654 mNotificationTranslation - mScrollView.getScrollY());
655 }
656 }
657
658 @Override
659 public void onClick(View v) {
660 if (v == mHeader.getBackgroundView()) {
661 onQsExpansionStarted();
662 if (mQsExpanded) {
663 flingSettings(0 /* vel */, false /* expand */);
664 } else if (mQsExpansionEnabled) {
665 flingSettings(0 /* vel */, true /* expand */);
666 }
667 }
668 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400669}