blob: f5252a32dbdef2f3b766e9bcecc7fbadc458a0a3 [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;
24import android.util.AttributeSet;
Daniel Sandler040c2e42012-10-17 00:56:33 -040025import android.view.MotionEvent;
Jorim Jaggid7daab72014-05-06 22:22:20 +020026import android.view.VelocityTracker;
Daniel Sandler13522a22012-09-27 14:46:58 -040027import android.view.View;
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +020028import android.view.ViewGroup;
Jorim Jaggi75c95042014-05-16 19:09:59 +020029import android.view.ViewTreeObserver;
Casey Burkhardt23b0a4e2013-04-29 12:18:32 -070030import android.view.accessibility.AccessibilityEvent;
Jorim Jaggi75c95042014-05-16 19:09:59 +020031import android.view.animation.AnimationUtils;
32import android.view.animation.Interpolator;
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +020033import android.widget.LinearLayout;
Daniel Sandler13522a22012-09-27 14:46:58 -040034
Chet Haase4d179dc2012-08-22 07:14:42 -070035import com.android.systemui.R;
Jorim Jaggibe565df2014-04-28 17:51:23 +020036import com.android.systemui.statusbar.ExpandableView;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020037import com.android.systemui.statusbar.FlingAnimationUtils;
Daniel Sandler151f00d2012-10-02 22:33:08 -040038import com.android.systemui.statusbar.GestureRecorder;
Jorim Jaggiecbab362014-04-23 16:13:15 +020039import com.android.systemui.statusbar.StatusBarState;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010040import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
Jorim Jaggi75c95042014-05-16 19:09:59 +020041import com.android.systemui.statusbar.stack.StackStateAnimator;
Daniel Sandler08d05e32012-08-08 16:39:54 -040042
Jorim Jaggibe565df2014-04-28 17:51:23 +020043public class NotificationPanelView extends PanelView implements
Jorim Jaggid7daab72014-05-06 22:22:20 +020044 ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
45 View.OnClickListener {
Chet Haase4d179dc2012-08-22 07:14:42 -070046
Daniel Sandler040c2e42012-10-17 00:56:33 -040047 PhoneStatusBar mStatusBar;
Jorim Jaggid7daab72014-05-06 22:22:20 +020048 private StatusBarHeaderView mHeader;
John Spurlockaf8d6c42014-05-07 17:49:08 -040049 private View mQsContainer;
Jorim Jaggif62f2bb2014-05-25 22:28:41 +020050 private View mQsPanel;
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;
Jorim Jaggif62f2bb2014-05-25 22:28:41 +020069 private boolean mKeyguardShowing;
Jorim Jaggid7daab72014-05-06 22:22:20 +020070 private float mInitialHeightOnTouch;
71 private float mInitialTouchX;
72 private float mInitialTouchY;
Jorim Jaggifc1a8242014-05-12 19:30:05 +020073 private float mLastTouchX;
74 private float mLastTouchY;
Jorim Jaggid7daab72014-05-06 22:22:20 +020075 private float mQsExpansionHeight;
76 private int mQsMinExpansionHeight;
77 private int mQsMaxExpansionHeight;
78 private int mMinStackHeight;
Jorim Jaggif62f2bb2014-05-25 22:28:41 +020079 private int mQsPeekHeight;
Jorim Jaggid7daab72014-05-06 22:22:20 +020080 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 Jaggif62f2bb2014-05-25 22:28:41 +0200124 mQsPanel = findViewById(R.id.quick_settings_panel);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200125 mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
126 mScrollView.setListener(this);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100127 mNotificationStackScroller = (NotificationStackScrollLayout)
128 findViewById(R.id.notification_stack_scroller);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200129 mNotificationStackScroller.setOnHeightChangedListener(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200130 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
131 android.R.interpolator.fast_out_slow_in);
Jorim Jaggi069cd032014-05-15 03:09:01 +0200132 }
133
134 @Override
135 protected void loadDimens() {
136 super.loadDimens();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200137 mNotificationTopPadding = getResources().getDimensionPixelSize(
138 R.dimen.notifications_top_padding);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200139 mMinStackHeight = getResources().getDimensionPixelSize(R.dimen.collapsed_stack_height);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200140 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f);
Selim Cinek9dbbca82014-05-15 15:23:37 +0200141 mStatusBarMinHeight = getResources().getDimensionPixelSize(
142 com.android.internal.R.dimen.status_bar_height);
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200143 mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height);
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200144 mClockPositionAlgorithm.loadDimens(getResources());
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
151 // Calculate quick setting heights.
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200152 mQsMinExpansionHeight = mHeader.getCollapsedHeight() + mQsPeekHeight;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200153 mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight();
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200154 if (!mQsExpanded) {
155 setQsExpansion(mQsMinExpansionHeight);
156 positionClockAndNotifications();
157 mNotificationStackScroller.setStackHeight(getExpandedHeight());
Jorim Jaggid7daab72014-05-06 22:22:20 +0200158 }
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) {
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200168 mStackScrollerIntrinsicPadding = mHeader.getBottom() + mQsPeekHeight
169 + mNotificationTopPadding;
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200170 mTopPaddingAdjustment = 0;
Jorim Jaggi069cd032014-05-15 03:09:01 +0200171 } else {
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200172 mClockPositionAlgorithm.setup(
173 mStatusBar.getMaxKeyguardNotifications(),
174 getMaxPanelHeight(),
175 getExpandedHeight(),
176 mNotificationStackScroller.getNotGoneChildCount(),
177 getHeight(),
178 mKeyguardStatusView.getHeight());
179 mClockPositionAlgorithm.run(mClockPositionResult);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200180 if (animateClock || mClockAnimator != null) {
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200181 startClockAnimation(mClockPositionResult.clockY);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200182 } else {
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200183 mKeyguardStatusView.setY(mClockPositionResult.clockY);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200184 }
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200185 applyClockAlpha(mClockPositionResult.clockAlpha);
186 mStackScrollerIntrinsicPadding = mClockPositionResult.stackScrollerPadding;
187 mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment;
Jorim Jaggi069cd032014-05-15 03:09:01 +0200188 }
189 mNotificationStackScroller.setTopPadding(mStackScrollerIntrinsicPadding,
Jorim Jaggi75c95042014-05-16 19:09:59 +0200190 mAnimateNextTopPaddingChange || animateClock);
Jorim Jaggi069cd032014-05-15 03:09:01 +0200191 mAnimateNextTopPaddingChange = false;
192 }
193
Jorim Jaggi75c95042014-05-16 19:09:59 +0200194 private void startClockAnimation(int y) {
195 if (mClockAnimationTarget == y) {
196 return;
197 }
198 mClockAnimationTarget = y;
199 getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
200 @Override
201 public boolean onPreDraw() {
202 getViewTreeObserver().removeOnPreDrawListener(this);
203 if (mClockAnimator != null) {
204 mClockAnimator.removeAllListeners();
205 mClockAnimator.cancel();
206 }
207 mClockAnimator =
208 ObjectAnimator.ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget);
209 mClockAnimator.setInterpolator(mFastOutSlowInInterpolator);
210 mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
211 mClockAnimator.addListener(new AnimatorListenerAdapter() {
212 @Override
213 public void onAnimationEnd(Animator animation) {
214 mClockAnimator = null;
215 mClockAnimationTarget = -1;
216 }
217 });
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200218 mClockAnimator.start();
Jorim Jaggi75c95042014-05-16 19:09:59 +0200219 return true;
220 }
221 });
222 }
223
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200224 private void applyClockAlpha(float alpha) {
225 if (alpha != 1.0f) {
226 mKeyguardStatusView.setLayerType(LAYER_TYPE_HARDWARE, null);
227 } else {
228 mKeyguardStatusView.setLayerType(LAYER_TYPE_NONE, null);
229 }
230 mKeyguardStatusView.setAlpha(alpha);
Jorim Jaggi069cd032014-05-15 03:09:01 +0200231 }
232
Selim Cinekc27437b2014-05-14 10:23:33 +0200233 public void animateToFullShade() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +0200234 mAnimateNextTopPaddingChange = true;
Selim Cinekc27437b2014-05-14 10:23:33 +0200235 mNotificationStackScroller.goToFullShade();
Jorim Jaggi98fb09c2014-05-01 22:40:56 +0200236 requestLayout();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400237 }
238
Jorim Jaggid7daab72014-05-06 22:22:20 +0200239 /**
240 * @return Whether Quick Settings are currently expanded.
241 */
242 public boolean isQsExpanded() {
243 return mQsExpanded;
244 }
245
246 public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
247 mQsExpansionEnabled = qsExpansionEnabled;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200248 }
249
250 public void closeQs() {
251 cancelAnimation();
252 setQsExpansion(mQsMinExpansionHeight);
253 }
254
255 public void openQs() {
256 cancelAnimation();
257 if (mQsExpansionEnabled) {
258 setQsExpansion(mQsMaxExpansionHeight);
259 }
260 }
261
Daniel Sandler08d05e32012-08-08 16:39:54 -0400262 @Override
263 public void fling(float vel, boolean always) {
Daniel Sandler151f00d2012-10-02 22:33:08 -0400264 GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
265 if (gr != null) {
266 gr.tag(
267 "fling " + ((vel > 0) ? "open" : "closed"),
268 "notifications,v=" + vel);
269 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400270 super.fling(vel, always);
271 }
Chet Haase4d179dc2012-08-22 07:14:42 -0700272
Casey Burkhardt23b0a4e2013-04-29 12:18:32 -0700273 @Override
274 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
275 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
276 event.getText()
277 .add(getContext().getString(R.string.accessibility_desc_notification_shade));
278 return true;
279 }
280
281 return super.dispatchPopulateAccessibilityEvent(event);
282 }
283
Daniel Sandler040c2e42012-10-17 00:56:33 -0400284 @Override
John Spurlock73203eb2014-04-15 16:14:46 -0400285 public boolean onInterceptTouchEvent(MotionEvent event) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200286 int pointerIndex = event.findPointerIndex(mTrackingPointer);
287 if (pointerIndex < 0) {
288 pointerIndex = 0;
289 mTrackingPointer = event.getPointerId(pointerIndex);
John Spurlock73203eb2014-04-15 16:14:46 -0400290 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200291 final float x = event.getX(pointerIndex);
292 final float y = event.getY(pointerIndex);
293
294 switch (event.getActionMasked()) {
295 case MotionEvent.ACTION_DOWN:
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200296 mIntercepting = true;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200297 mInitialTouchY = y;
298 mInitialTouchX = x;
299 initVelocityTracker();
300 trackMovement(event);
301 if (shouldIntercept(mInitialTouchX, mInitialTouchY, 0)) {
302 getParent().requestDisallowInterceptTouchEvent(true);
303 }
304 break;
305 case MotionEvent.ACTION_POINTER_UP:
306 final int upPointer = event.getPointerId(event.getActionIndex());
307 if (mTrackingPointer == upPointer) {
308 // gesture is ongoing, find a new pointer to track
309 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
310 mTrackingPointer = event.getPointerId(newIndex);
311 mInitialTouchX = event.getX(newIndex);
312 mInitialTouchY = event.getY(newIndex);
313 }
314 break;
315
316 case MotionEvent.ACTION_MOVE:
317 final float h = y - mInitialTouchY;
318 trackMovement(event);
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200319 if (mTracking) {
320
321 // Already tracking because onOverscrolled was called. We need to update here
322 // so we don't stop for a frame until the next touch event gets handled in
323 // onTouchEvent.
324 setQsExpansion(h + mInitialHeightOnTouch);
325 trackMovement(event);
326 mIntercepting = false;
327 return true;
328 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200329 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
330 && shouldIntercept(mInitialTouchX, mInitialTouchY, h)) {
331 onQsExpansionStarted();
332 mInitialHeightOnTouch = mQsExpansionHeight;
333 mInitialTouchY = y;
334 mInitialTouchX = x;
335 mTracking = true;
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200336 mIntercepting = false;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200337 return true;
338 }
339 break;
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200340
341 case MotionEvent.ACTION_CANCEL:
342 case MotionEvent.ACTION_UP:
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200343 trackMovement(event);
344 if (mTracking) {
345 flingWithCurrentVelocity();
346 mTracking = false;
347 }
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200348 mIntercepting = false;
349 break;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200350 }
351 return !mQsExpanded && super.onInterceptTouchEvent(event);
John Spurlock73203eb2014-04-15 16:14:46 -0400352 }
353
354 @Override
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200355 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
356
Jorim Jaggice50b812014-05-24 16:29:57 +0200357 // Block request when interacting with the scroll view so we can still intercept the
358 // scrolling when QS is expanded.
359 if (mScrollView.isDispatchingTouchEvent()) {
360 return;
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200361 }
Jorim Jaggice50b812014-05-24 16:29:57 +0200362 super.requestDisallowInterceptTouchEvent(disallowIntercept);
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200363 }
364
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200365 private void flingWithCurrentVelocity() {
366 float vel = getCurrentVelocity();
367
368 // TODO: Better logic whether we should expand or not.
369 flingSettings(vel, vel > 0);
370 }
371
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200372 @Override
Daniel Sandler040c2e42012-10-17 00:56:33 -0400373 public boolean onTouchEvent(MotionEvent event) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100374 // TODO: Handle doublefinger swipe to notifications again. Look at history for a reference
375 // implementation.
Jorim Jaggid7daab72014-05-06 22:22:20 +0200376 if (mTracking) {
377 int pointerIndex = event.findPointerIndex(mTrackingPointer);
378 if (pointerIndex < 0) {
379 pointerIndex = 0;
380 mTrackingPointer = event.getPointerId(pointerIndex);
381 }
382 final float y = event.getY(pointerIndex);
383 final float x = event.getX(pointerIndex);
384
385 switch (event.getActionMasked()) {
386 case MotionEvent.ACTION_DOWN:
387 mTracking = true;
388 mInitialTouchY = y;
389 mInitialTouchX = x;
390 onQsExpansionStarted();
391 mInitialHeightOnTouch = mQsExpansionHeight;
392 initVelocityTracker();
393 trackMovement(event);
394 break;
395
396 case MotionEvent.ACTION_POINTER_UP:
397 final int upPointer = event.getPointerId(event.getActionIndex());
398 if (mTrackingPointer == upPointer) {
399 // gesture is ongoing, find a new pointer to track
400 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
401 final float newY = event.getY(newIndex);
402 final float newX = event.getX(newIndex);
403 mTrackingPointer = event.getPointerId(newIndex);
404 mInitialHeightOnTouch = mQsExpansionHeight;
405 mInitialTouchY = newY;
406 mInitialTouchX = newX;
407 }
408 break;
409
410 case MotionEvent.ACTION_MOVE:
411 final float h = y - mInitialTouchY;
412 setQsExpansion(h + mInitialHeightOnTouch);
413 trackMovement(event);
414 break;
415
416 case MotionEvent.ACTION_UP:
417 case MotionEvent.ACTION_CANCEL:
418 mTracking = false;
419 mTrackingPointer = -1;
420 trackMovement(event);
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200421 flingWithCurrentVelocity();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200422 if (mVelocityTracker != null) {
423 mVelocityTracker.recycle();
424 mVelocityTracker = null;
425 }
426 break;
John Spurlock73203eb2014-04-15 16:14:46 -0400427 }
428 return true;
429 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200430
431 // Consume touch events when QS are expanded.
432 return mQsExpanded || super.onTouchEvent(event);
433 }
434
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200435 @Override
436 public void onOverscrolled(int amount) {
437 if (mIntercepting) {
438 onQsExpansionStarted(amount);
439 mInitialHeightOnTouch = mQsExpansionHeight;
440 mInitialTouchY = mLastTouchY;
441 mInitialTouchX = mLastTouchX;
442 mTracking = true;
443 }
444 }
445
Jorim Jaggid7daab72014-05-06 22:22:20 +0200446 private void onQsExpansionStarted() {
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200447 onQsExpansionStarted(0);
448 }
449
450 private void onQsExpansionStarted(int overscrollAmount) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200451 cancelAnimation();
452
453 // Reset scroll position and apply that position to the expanded height.
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200454 float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200455 mScrollView.scrollTo(0, 0);
456 setQsExpansion(height);
457 }
458
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200459 private void setQsExpanded(boolean expanded) {
460 boolean changed = mQsExpanded != expanded;
461 if (changed) {
462 mQsExpanded = expanded;
463 updateQsState();
464 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200465 }
466
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200467 public void setKeyguardShowing(boolean keyguardShowing) {
468 mKeyguardShowing = keyguardShowing;
469 updateQsState();
470 }
471
472 private void updateQsState() {
473 mHeader.setExpanded(mQsExpanded);
474 mNotificationStackScroller.setEnabled(!mQsExpanded);
475 mQsPanel.setVisibility(mQsExpanded ? View.VISIBLE : View.INVISIBLE);
476 mQsContainer.setVisibility(mKeyguardShowing && !mQsExpanded
477 ? View.INVISIBLE
478 : View.VISIBLE);
479 mScrollView.setTouchEnabled(mQsExpanded);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200480 }
481
482 private void setQsExpansion(float height) {
483 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
484 if (height > mQsMinExpansionHeight && !mQsExpanded) {
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200485 setQsExpanded(true);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200486 } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200487 setQsExpanded(false);
John Spurlock73203eb2014-04-15 16:14:46 -0400488 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200489 mQsExpansionHeight = height;
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200490 mHeader.setExpansion(height - mQsPeekHeight);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200491 setQsTranslation(height);
492 setQsStackScrollerPadding(height);
John Spurlockd08f91f2014-05-23 11:00:34 -0400493 mStatusBar.userActivity();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200494 }
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();
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200629 int maxHeight = maxPanelHeight - emptyBottomMargin - mTopPaddingAdjustment;
Jorim Jaggi1d480692014-05-20 19:41:58 +0200630 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 Jaggi7d16bb12014-05-22 04:24:50 +0200640 if (!mQsExpanded) {
641 positionClockAndNotifications();
642 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200643 mNotificationStackScroller.setStackHeight(expandedHeight);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100644 }
645
646 @Override
Selim Cinek1685e632014-04-08 02:27:49 +0200647 protected void onExpandingStarted() {
648 super.onExpandingStarted();
649 mNotificationStackScroller.onExpansionStarted();
650 }
651
652 @Override
653 protected void onExpandingFinished() {
654 super.onExpandingFinished();
655 mNotificationStackScroller.onExpansionStopped();
656 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200657
658 @Override
Selim Cinek24120a52014-05-26 10:05:42 +0200659 protected void onOverExpansionChanged(float overExpansion) {
660 float currentOverScroll = mNotificationStackScroller.getCurrentOverScrolledPixels(true);
661 mNotificationStackScroller.setOverScrolledPixels(currentOverScroll + overExpansion
662 - mOverExpansion, true /* onTop */, false /* animate */);
663 super.onOverExpansionChanged(overExpansion);
664 }
665
666 @Override
667 protected void onTrackingStopped() {
668 super.onTrackingStopped();
669 mOverExpansion = 0.0f;
670 mNotificationStackScroller.setOverScrolledPixels(0.0f, true /* onTop */,
671 true /* animate */);
672 }
673
674
675 @Override
Jorim Jaggibe565df2014-04-28 17:51:23 +0200676 public void onHeightChanged(ExpandableView view) {
677 requestPanelHeightUpdate();
678 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200679
680 @Override
681 public void onScrollChanged() {
682 if (mQsExpanded) {
683 mNotificationStackScroller.setTranslationY(
684 mNotificationTranslation - mScrollView.getScrollY());
685 }
686 }
687
688 @Override
689 public void onClick(View v) {
690 if (v == mHeader.getBackgroundView()) {
691 onQsExpansionStarted();
692 if (mQsExpanded) {
693 flingSettings(0 /* vel */, false /* expand */);
694 } else if (mQsExpansionEnabled) {
695 flingSettings(0 /* vel */, true /* expand */);
696 }
697 }
698 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400699}