blob: 802e5e578add6104068535cafb7986171048a0ee [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;
Selim Cinek4c6969a2014-05-26 19:22:17 +020024import android.content.res.Configuration;
Daniel Sandler08d05e32012-08-08 16:39:54 -040025import android.util.AttributeSet;
Daniel Sandler040c2e42012-10-17 00:56:33 -040026import android.view.MotionEvent;
Jorim Jaggid7daab72014-05-06 22:22:20 +020027import android.view.VelocityTracker;
Daniel Sandler13522a22012-09-27 14:46:58 -040028import android.view.View;
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +020029import android.view.ViewGroup;
Jorim Jaggi75c95042014-05-16 19:09:59 +020030import android.view.ViewTreeObserver;
Casey Burkhardt23b0a4e2013-04-29 12:18:32 -070031import android.view.accessibility.AccessibilityEvent;
Jorim Jaggi75c95042014-05-16 19:09:59 +020032import android.view.animation.AnimationUtils;
33import android.view.animation.Interpolator;
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +020034import android.widget.LinearLayout;
Daniel Sandler13522a22012-09-27 14:46:58 -040035
Chet Haase4d179dc2012-08-22 07:14:42 -070036import com.android.systemui.R;
Jorim Jaggibe565df2014-04-28 17:51:23 +020037import com.android.systemui.statusbar.ExpandableView;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020038import com.android.systemui.statusbar.FlingAnimationUtils;
Daniel Sandler151f00d2012-10-02 22:33:08 -040039import com.android.systemui.statusbar.GestureRecorder;
Jorim Jaggiecbab362014-04-23 16:13:15 +020040import com.android.systemui.statusbar.StatusBarState;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010041import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
Jorim Jaggi75c95042014-05-16 19:09:59 +020042import com.android.systemui.statusbar.stack.StackStateAnimator;
Daniel Sandler08d05e32012-08-08 16:39:54 -040043
Selim Cinek4c6969a2014-05-26 19:22:17 +020044import java.util.ArrayList;
45
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,
Selim Cinek4c6969a2014-05-26 19:22:17 +020048 View.OnClickListener, KeyguardPageSwipeHelper.Callback {
Chet Haase4d179dc2012-08-22 07:14:42 -070049
Selim Cinek4c6969a2014-05-26 19:22:17 +020050 private KeyguardPageSwipeHelper mPageSwiper;
Daniel Sandler040c2e42012-10-17 00:56:33 -040051 PhoneStatusBar mStatusBar;
Jorim Jaggid7daab72014-05-06 22:22:20 +020052 private StatusBarHeaderView mHeader;
John Spurlockaf8d6c42014-05-07 17:49:08 -040053 private View mQsContainer;
John Spurlock3e04cc82014-05-30 12:34:03 -040054 private View mQsPanel;
John Spurlock73203eb2014-04-15 16:14:46 -040055 private View mKeyguardStatusView;
Jorim Jaggid7daab72014-05-06 22:22:20 +020056 private ObservableScrollView mScrollView;
57 private View mStackScrollerContainer;
John Spurlock73203eb2014-04-15 16:14:46 -040058
Selim Cinekb6d85eb2014-03-28 20:21:01 +010059 private NotificationStackScrollLayout mNotificationStackScroller;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +020060 private int mNotificationTopPadding;
Jorim Jaggi98fb09c2014-05-01 22:40:56 +020061 private boolean mAnimateNextTopPaddingChange;
Chet Haase4d179dc2012-08-22 07:14:42 -070062
Jorim Jaggid7daab72014-05-06 22:22:20 +020063 private int mTrackingPointer;
64 private VelocityTracker mVelocityTracker;
Selim Cinek4c6969a2014-05-26 19:22:17 +020065 private boolean mQsTracking;
Jorim Jaggifc1a8242014-05-12 19:30:05 +020066
67 /**
68 * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't
69 * intercepted yet.
70 */
71 private boolean mIntercepting;
Jorim Jaggid7daab72014-05-06 22:22:20 +020072 private boolean mQsExpanded;
Jorim Jaggif62f2bb2014-05-25 22:28:41 +020073 private boolean mKeyguardShowing;
Jorim Jaggid7daab72014-05-06 22:22:20 +020074 private float mInitialHeightOnTouch;
75 private float mInitialTouchX;
76 private float mInitialTouchY;
Jorim Jaggifc1a8242014-05-12 19:30:05 +020077 private float mLastTouchX;
78 private float mLastTouchY;
Jorim Jaggid7daab72014-05-06 22:22:20 +020079 private float mQsExpansionHeight;
80 private int mQsMinExpansionHeight;
81 private int mQsMaxExpansionHeight;
82 private int mMinStackHeight;
Jorim Jaggif62f2bb2014-05-25 22:28:41 +020083 private int mQsPeekHeight;
Jorim Jaggid7daab72014-05-06 22:22:20 +020084 private float mNotificationTranslation;
85 private int mStackScrollerIntrinsicPadding;
86 private boolean mQsExpansionEnabled = true;
87 private ValueAnimator mQsExpansionAnimator;
Jorim Jaggi87cd5e72014-05-12 23:29:10 +020088 private FlingAnimationUtils mFlingAnimationUtils;
Selim Cinek9dbbca82014-05-15 15:23:37 +020089 private int mStatusBarMinHeight;
John Spurlock3e04cc82014-05-30 12:34:03 -040090
Jorim Jaggi75c95042014-05-16 19:09:59 +020091 private Interpolator mFastOutSlowInInterpolator;
92 private ObjectAnimator mClockAnimator;
93 private int mClockAnimationTarget = -1;
Jorim Jaggi7d16bb12014-05-22 04:24:50 +020094 private int mTopPaddingAdjustment;
95 private KeyguardClockPositionAlgorithm mClockPositionAlgorithm =
96 new KeyguardClockPositionAlgorithm();
97 private KeyguardClockPositionAlgorithm.Result mClockPositionResult =
98 new KeyguardClockPositionAlgorithm.Result();
Selim Cinek4c6969a2014-05-26 19:22:17 +020099 private boolean mIsSwipedHorizontally;
100 private boolean mIsExpanding;
101 private KeyguardBottomAreaView mKeyguardBottomArea;
102 private boolean mBlockTouches;
103 private ArrayList<View> mSwipeTranslationViews = new ArrayList<>();
Jorim Jaggi069cd032014-05-15 03:09:01 +0200104
Daniel Sandler08d05e32012-08-08 16:39:54 -0400105 public NotificationPanelView(Context context, AttributeSet attrs) {
106 super(context, attrs);
Daniel Sandler13522a22012-09-27 14:46:58 -0400107 }
Chet Haase4d179dc2012-08-22 07:14:42 -0700108
Daniel Sandler040c2e42012-10-17 00:56:33 -0400109 public void setStatusBar(PhoneStatusBar bar) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100110 if (mStatusBar != null) {
111 mStatusBar.setOnFlipRunnable(null);
112 }
Daniel Sandler040c2e42012-10-17 00:56:33 -0400113 mStatusBar = bar;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100114 if (bar != null) {
115 mStatusBar.setOnFlipRunnable(new Runnable() {
116 @Override
117 public void run() {
118 requestPanelHeightUpdate();
119 }
120 });
121 }
Daniel Sandler040c2e42012-10-17 00:56:33 -0400122 }
123
Daniel Sandler13522a22012-09-27 14:46:58 -0400124 @Override
125 protected void onFinishInflate() {
126 super.onFinishInflate();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200127 mHeader = (StatusBarHeaderView) findViewById(R.id.header);
128 mHeader.getBackgroundView().setOnClickListener(this);
Jorim Jaggi3d878be2014-05-10 03:22:32 +0200129 mHeader.setOverlayParent(this);
John Spurlock73203eb2014-04-15 16:14:46 -0400130 mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200131 mStackScrollerContainer = findViewById(R.id.notification_container_parent);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400132 mQsContainer = findViewById(R.id.quick_settings_container);
John Spurlock3e04cc82014-05-30 12:34:03 -0400133 mQsPanel = findViewById(R.id.quick_settings_panel);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200134 mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
135 mScrollView.setListener(this);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100136 mNotificationStackScroller = (NotificationStackScrollLayout)
137 findViewById(R.id.notification_stack_scroller);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200138 mNotificationStackScroller.setOnHeightChangedListener(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200139 mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
140 android.R.interpolator.fast_out_slow_in);
Selim Cinek4c6969a2014-05-26 19:22:17 +0200141 mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
142 mSwipeTranslationViews.add(mNotificationStackScroller);
143 mSwipeTranslationViews.add(mKeyguardStatusView);
144 mPageSwiper = new KeyguardPageSwipeHelper(this, getContext());
Jorim Jaggi069cd032014-05-15 03:09:01 +0200145 }
146
147 @Override
148 protected void loadDimens() {
149 super.loadDimens();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200150 mNotificationTopPadding = getResources().getDimensionPixelSize(
151 R.dimen.notifications_top_padding);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200152 mMinStackHeight = getResources().getDimensionPixelSize(R.dimen.collapsed_stack_height);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200153 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f);
Selim Cinek9dbbca82014-05-15 15:23:37 +0200154 mStatusBarMinHeight = getResources().getDimensionPixelSize(
155 com.android.internal.R.dimen.status_bar_height);
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200156 mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height);
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200157 mClockPositionAlgorithm.loadDimens(getResources());
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200158 }
159
160 @Override
161 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
162 super.onLayout(changed, left, top, right, bottom);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200163
164 // Calculate quick setting heights.
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200165 mQsMinExpansionHeight = mHeader.getCollapsedHeight() + mQsPeekHeight;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200166 mQsMaxExpansionHeight = mHeader.getExpandedHeight() + mQsContainer.getHeight();
John Spurlock2684d5e2014-05-28 20:27:44 -0400167 if (mQsExpanded) {
John Spurlock3e04cc82014-05-30 12:34:03 -0400168 setQsStackScrollerPadding(mQsMaxExpansionHeight);
John Spurlock2684d5e2014-05-28 20:27:44 -0400169 } else {
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200170 setQsExpansion(mQsMinExpansionHeight);
171 positionClockAndNotifications();
172 mNotificationStackScroller.setStackHeight(getExpandedHeight());
Jorim Jaggid7daab72014-05-06 22:22:20 +0200173 }
Jorim Jaggi98fb09c2014-05-01 22:40:56 +0200174 }
175
Jorim Jaggi069cd032014-05-15 03:09:01 +0200176 /**
177 * Positions the clock and notifications dynamically depending on how many notifications are
178 * showing.
179 */
180 private void positionClockAndNotifications() {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200181 boolean animateClock = mNotificationStackScroller.isAddOrRemoveAnimationPending();
Jorim Jaggi069cd032014-05-15 03:09:01 +0200182 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200183 mStackScrollerIntrinsicPadding = mHeader.getBottom() + mQsPeekHeight
184 + mNotificationTopPadding;
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200185 mTopPaddingAdjustment = 0;
Jorim Jaggi069cd032014-05-15 03:09:01 +0200186 } else {
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200187 mClockPositionAlgorithm.setup(
188 mStatusBar.getMaxKeyguardNotifications(),
189 getMaxPanelHeight(),
190 getExpandedHeight(),
191 mNotificationStackScroller.getNotGoneChildCount(),
192 getHeight(),
193 mKeyguardStatusView.getHeight());
194 mClockPositionAlgorithm.run(mClockPositionResult);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200195 if (animateClock || mClockAnimator != null) {
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200196 startClockAnimation(mClockPositionResult.clockY);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200197 } else {
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200198 mKeyguardStatusView.setY(mClockPositionResult.clockY);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200199 }
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200200 applyClockAlpha(mClockPositionResult.clockAlpha);
201 mStackScrollerIntrinsicPadding = mClockPositionResult.stackScrollerPadding;
202 mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment;
Jorim Jaggi069cd032014-05-15 03:09:01 +0200203 }
204 mNotificationStackScroller.setTopPadding(mStackScrollerIntrinsicPadding,
Jorim Jaggi75c95042014-05-16 19:09:59 +0200205 mAnimateNextTopPaddingChange || animateClock);
Jorim Jaggi069cd032014-05-15 03:09:01 +0200206 mAnimateNextTopPaddingChange = false;
207 }
208
Jorim Jaggi75c95042014-05-16 19:09:59 +0200209 private void startClockAnimation(int y) {
210 if (mClockAnimationTarget == y) {
211 return;
212 }
213 mClockAnimationTarget = y;
214 getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
215 @Override
216 public boolean onPreDraw() {
217 getViewTreeObserver().removeOnPreDrawListener(this);
218 if (mClockAnimator != null) {
219 mClockAnimator.removeAllListeners();
220 mClockAnimator.cancel();
221 }
222 mClockAnimator =
223 ObjectAnimator.ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget);
224 mClockAnimator.setInterpolator(mFastOutSlowInInterpolator);
225 mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
226 mClockAnimator.addListener(new AnimatorListenerAdapter() {
227 @Override
228 public void onAnimationEnd(Animator animation) {
229 mClockAnimator = null;
230 mClockAnimationTarget = -1;
231 }
232 });
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200233 mClockAnimator.start();
Jorim Jaggi75c95042014-05-16 19:09:59 +0200234 return true;
235 }
236 });
237 }
238
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200239 private void applyClockAlpha(float alpha) {
240 if (alpha != 1.0f) {
241 mKeyguardStatusView.setLayerType(LAYER_TYPE_HARDWARE, null);
242 } else {
243 mKeyguardStatusView.setLayerType(LAYER_TYPE_NONE, null);
244 }
245 mKeyguardStatusView.setAlpha(alpha);
Jorim Jaggi069cd032014-05-15 03:09:01 +0200246 }
247
Selim Cinekc27437b2014-05-14 10:23:33 +0200248 public void animateToFullShade() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +0200249 mAnimateNextTopPaddingChange = true;
Selim Cinekc27437b2014-05-14 10:23:33 +0200250 mNotificationStackScroller.goToFullShade();
Jorim Jaggi98fb09c2014-05-01 22:40:56 +0200251 requestLayout();
Daniel Sandler08d05e32012-08-08 16:39:54 -0400252 }
253
Jorim Jaggid7daab72014-05-06 22:22:20 +0200254 /**
255 * @return Whether Quick Settings are currently expanded.
256 */
257 public boolean isQsExpanded() {
258 return mQsExpanded;
259 }
260
261 public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
262 mQsExpansionEnabled = qsExpansionEnabled;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200263 }
264
Selim Cinek3c4635c2014-05-29 02:12:47 +0200265 @Override
Selim Cinek4c6969a2014-05-26 19:22:17 +0200266 public void resetViews() {
267 mBlockTouches = false;
268 mPageSwiper.reset();
269 closeQs();
270 }
271
Jorim Jaggid7daab72014-05-06 22:22:20 +0200272 public void closeQs() {
273 cancelAnimation();
274 setQsExpansion(mQsMinExpansionHeight);
275 }
276
277 public void openQs() {
278 cancelAnimation();
279 if (mQsExpansionEnabled) {
280 setQsExpansion(mQsMaxExpansionHeight);
281 }
282 }
283
Daniel Sandler08d05e32012-08-08 16:39:54 -0400284 @Override
285 public void fling(float vel, boolean always) {
Daniel Sandler151f00d2012-10-02 22:33:08 -0400286 GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
287 if (gr != null) {
Selim Cinek4c6969a2014-05-26 19:22:17 +0200288 gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
Daniel Sandler151f00d2012-10-02 22:33:08 -0400289 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400290 super.fling(vel, always);
291 }
Chet Haase4d179dc2012-08-22 07:14:42 -0700292
Casey Burkhardt23b0a4e2013-04-29 12:18:32 -0700293 @Override
294 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
295 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
296 event.getText()
297 .add(getContext().getString(R.string.accessibility_desc_notification_shade));
298 return true;
299 }
300
301 return super.dispatchPopulateAccessibilityEvent(event);
302 }
303
Daniel Sandler040c2e42012-10-17 00:56:33 -0400304 @Override
John Spurlock73203eb2014-04-15 16:14:46 -0400305 public boolean onInterceptTouchEvent(MotionEvent event) {
Selim Cinek4c6969a2014-05-26 19:22:17 +0200306 if (mBlockTouches) {
307 return false;
308 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200309 int pointerIndex = event.findPointerIndex(mTrackingPointer);
310 if (pointerIndex < 0) {
311 pointerIndex = 0;
312 mTrackingPointer = event.getPointerId(pointerIndex);
John Spurlock73203eb2014-04-15 16:14:46 -0400313 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200314 final float x = event.getX(pointerIndex);
315 final float y = event.getY(pointerIndex);
316
317 switch (event.getActionMasked()) {
318 case MotionEvent.ACTION_DOWN:
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200319 mIntercepting = true;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200320 mInitialTouchY = y;
321 mInitialTouchX = x;
322 initVelocityTracker();
323 trackMovement(event);
Selim Cinek4c6969a2014-05-26 19:22:17 +0200324 if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200325 getParent().requestDisallowInterceptTouchEvent(true);
326 }
327 break;
328 case MotionEvent.ACTION_POINTER_UP:
329 final int upPointer = event.getPointerId(event.getActionIndex());
330 if (mTrackingPointer == upPointer) {
331 // gesture is ongoing, find a new pointer to track
332 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
333 mTrackingPointer = event.getPointerId(newIndex);
334 mInitialTouchX = event.getX(newIndex);
335 mInitialTouchY = event.getY(newIndex);
336 }
337 break;
338
339 case MotionEvent.ACTION_MOVE:
340 final float h = y - mInitialTouchY;
341 trackMovement(event);
Selim Cinek4c6969a2014-05-26 19:22:17 +0200342 if (mQsTracking) {
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200343
344 // Already tracking because onOverscrolled was called. We need to update here
345 // so we don't stop for a frame until the next touch event gets handled in
346 // onTouchEvent.
347 setQsExpansion(h + mInitialHeightOnTouch);
348 trackMovement(event);
349 mIntercepting = false;
350 return true;
351 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200352 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
Selim Cinek4c6969a2014-05-26 19:22:17 +0200353 && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200354 onQsExpansionStarted();
355 mInitialHeightOnTouch = mQsExpansionHeight;
356 mInitialTouchY = y;
357 mInitialTouchX = x;
Selim Cinek4c6969a2014-05-26 19:22:17 +0200358 mQsTracking = true;
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200359 mIntercepting = false;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200360 return true;
361 }
362 break;
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200363
364 case MotionEvent.ACTION_CANCEL:
365 case MotionEvent.ACTION_UP:
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200366 trackMovement(event);
Selim Cinek4c6969a2014-05-26 19:22:17 +0200367 if (mQsTracking) {
368 flingQsWithCurrentVelocity();
369 mQsTracking = false;
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200370 }
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200371 mIntercepting = false;
372 break;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200373 }
374 return !mQsExpanded && super.onInterceptTouchEvent(event);
John Spurlock73203eb2014-04-15 16:14:46 -0400375 }
376
377 @Override
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200378 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
379
Jorim Jaggice50b812014-05-24 16:29:57 +0200380 // Block request when interacting with the scroll view so we can still intercept the
381 // scrolling when QS is expanded.
382 if (mScrollView.isDispatchingTouchEvent()) {
383 return;
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200384 }
Jorim Jaggice50b812014-05-24 16:29:57 +0200385 super.requestDisallowInterceptTouchEvent(disallowIntercept);
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200386 }
387
Selim Cinek4c6969a2014-05-26 19:22:17 +0200388 private void flingQsWithCurrentVelocity() {
Jorim Jaggic9a52ab2014-05-12 23:46:19 +0200389 float vel = getCurrentVelocity();
390
391 // TODO: Better logic whether we should expand or not.
392 flingSettings(vel, vel > 0);
393 }
394
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200395 @Override
Daniel Sandler040c2e42012-10-17 00:56:33 -0400396 public boolean onTouchEvent(MotionEvent event) {
Selim Cinek4c6969a2014-05-26 19:22:17 +0200397 if (mBlockTouches) {
398 return false;
399 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100400 // TODO: Handle doublefinger swipe to notifications again. Look at history for a reference
401 // implementation.
Selim Cinek4c6969a2014-05-26 19:22:17 +0200402 if (!mIsExpanding && !mQsExpanded && mStatusBar.getBarState() != StatusBarState.SHADE) {
403 mPageSwiper.onTouchEvent(event);
404 if (mPageSwiper.isSwipingInProgress()) {
405 return true;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200406 }
Selim Cinek4c6969a2014-05-26 19:22:17 +0200407 }
408 if (mQsTracking || mQsExpanded) {
409 return onQsTouch(event);
John Spurlock73203eb2014-04-15 16:14:46 -0400410 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200411
Selim Cinek4c6969a2014-05-26 19:22:17 +0200412 super.onTouchEvent(event);
413 return true;
414 }
415
416 @Override
417 protected boolean hasConflictingGestures() {
418 return mStatusBar.getBarState() != StatusBarState.SHADE;
419 }
420
421 private boolean onQsTouch(MotionEvent event) {
422 int pointerIndex = event.findPointerIndex(mTrackingPointer);
423 if (pointerIndex < 0) {
424 pointerIndex = 0;
425 mTrackingPointer = event.getPointerId(pointerIndex);
426 }
427 final float y = event.getY(pointerIndex);
428 final float x = event.getX(pointerIndex);
429
430 switch (event.getActionMasked()) {
431 case MotionEvent.ACTION_DOWN:
432 mQsTracking = true;
433 mInitialTouchY = y;
434 mInitialTouchX = x;
435 onQsExpansionStarted();
436 mInitialHeightOnTouch = mQsExpansionHeight;
437 initVelocityTracker();
438 trackMovement(event);
439 break;
440
441 case MotionEvent.ACTION_POINTER_UP:
442 final int upPointer = event.getPointerId(event.getActionIndex());
443 if (mTrackingPointer == upPointer) {
444 // gesture is ongoing, find a new pointer to track
445 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
446 final float newY = event.getY(newIndex);
447 final float newX = event.getX(newIndex);
448 mTrackingPointer = event.getPointerId(newIndex);
449 mInitialHeightOnTouch = mQsExpansionHeight;
450 mInitialTouchY = newY;
451 mInitialTouchX = newX;
452 }
453 break;
454
455 case MotionEvent.ACTION_MOVE:
456 final float h = y - mInitialTouchY;
457 setQsExpansion(h + mInitialHeightOnTouch);
458 trackMovement(event);
459 break;
460
461 case MotionEvent.ACTION_UP:
462 case MotionEvent.ACTION_CANCEL:
463 mQsTracking = false;
464 mTrackingPointer = -1;
465 trackMovement(event);
466 flingQsWithCurrentVelocity();
467 if (mVelocityTracker != null) {
468 mVelocityTracker.recycle();
469 mVelocityTracker = null;
470 }
471 break;
472 }
473 return true;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200474 }
475
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200476 @Override
477 public void onOverscrolled(int amount) {
478 if (mIntercepting) {
479 onQsExpansionStarted(amount);
480 mInitialHeightOnTouch = mQsExpansionHeight;
481 mInitialTouchY = mLastTouchY;
482 mInitialTouchX = mLastTouchX;
Selim Cinek4c6969a2014-05-26 19:22:17 +0200483 mQsTracking = true;
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200484 }
485 }
486
Jorim Jaggid7daab72014-05-06 22:22:20 +0200487 private void onQsExpansionStarted() {
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200488 onQsExpansionStarted(0);
489 }
490
491 private void onQsExpansionStarted(int overscrollAmount) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200492 cancelAnimation();
493
494 // Reset scroll position and apply that position to the expanded height.
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200495 float height = mQsExpansionHeight - mScrollView.getScrollY() - overscrollAmount;
Jorim Jaggid7daab72014-05-06 22:22:20 +0200496 mScrollView.scrollTo(0, 0);
497 setQsExpansion(height);
498 }
499
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200500 private void setQsExpanded(boolean expanded) {
501 boolean changed = mQsExpanded != expanded;
502 if (changed) {
503 mQsExpanded = expanded;
504 updateQsState();
505 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200506 }
507
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200508 public void setKeyguardShowing(boolean keyguardShowing) {
509 mKeyguardShowing = keyguardShowing;
510 updateQsState();
511 }
512
513 private void updateQsState() {
514 mHeader.setExpanded(mQsExpanded);
515 mNotificationStackScroller.setEnabled(!mQsExpanded);
516 mQsPanel.setVisibility(mQsExpanded ? View.VISIBLE : View.INVISIBLE);
517 mQsContainer.setVisibility(mKeyguardShowing && !mQsExpanded
518 ? View.INVISIBLE
519 : View.VISIBLE);
520 mScrollView.setTouchEnabled(mQsExpanded);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200521 }
522
523 private void setQsExpansion(float height) {
524 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
525 if (height > mQsMinExpansionHeight && !mQsExpanded) {
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200526 setQsExpanded(true);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200527 } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200528 setQsExpanded(false);
John Spurlock73203eb2014-04-15 16:14:46 -0400529 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200530 mQsExpansionHeight = height;
Jorim Jaggif62f2bb2014-05-25 22:28:41 +0200531 mHeader.setExpansion(height - mQsPeekHeight);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200532 setQsTranslation(height);
533 setQsStackScrollerPadding(height);
John Spurlockd08f91f2014-05-23 11:00:34 -0400534 mStatusBar.userActivity();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200535 }
536
537 private void setQsTranslation(float height) {
538 mQsContainer.setY(height - mQsContainer.getHeight());
539 }
540
541 private void setQsStackScrollerPadding(float height) {
542 float start = height - mScrollView.getScrollY() + mNotificationTopPadding;
543 float stackHeight = mNotificationStackScroller.getHeight() - start;
544 if (stackHeight <= mMinStackHeight) {
545 float overflow = mMinStackHeight - stackHeight;
546 stackHeight = mMinStackHeight;
547 start = mNotificationStackScroller.getHeight() - stackHeight;
548 mNotificationStackScroller.setTranslationY(overflow);
549 mNotificationTranslation = overflow + mScrollView.getScrollY();
550 } else {
551 mNotificationStackScroller.setTranslationY(0);
552 mNotificationTranslation = mScrollView.getScrollY();
553 }
554 mNotificationStackScroller.setTopPadding(clampQsStackScrollerPadding((int) start), false);
555 }
556
557 private int clampQsStackScrollerPadding(int desiredPadding) {
558 return Math.max(desiredPadding, mStackScrollerIntrinsicPadding);
559 }
560
561 private void trackMovement(MotionEvent event) {
562 if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
Jorim Jaggifc1a8242014-05-12 19:30:05 +0200563 mLastTouchX = event.getX();
564 mLastTouchY = event.getY();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200565 }
566
567 private void initVelocityTracker() {
568 if (mVelocityTracker != null) {
569 mVelocityTracker.recycle();
570 }
571 mVelocityTracker = VelocityTracker.obtain();
572 }
573
574 private float getCurrentVelocity() {
575 if (mVelocityTracker == null) {
576 return 0;
577 }
578 mVelocityTracker.computeCurrentVelocity(1000);
579 return mVelocityTracker.getYVelocity();
580 }
581
582 private void cancelAnimation() {
583 if (mQsExpansionAnimator != null) {
584 mQsExpansionAnimator.cancel();
585 }
586 }
587 private void flingSettings(float vel, boolean expand) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200588 float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight;
Jorim Jaggi9cd731a2014-05-22 14:26:06 +0200589 if (target == mQsExpansionHeight) {
590 return;
591 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200592 ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
Jorim Jaggi87cd5e72014-05-12 23:29:10 +0200593 mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200594 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
595 @Override
596 public void onAnimationUpdate(ValueAnimator animation) {
597 setQsExpansion((Float) animation.getAnimatedValue());
598 }
599 });
600 animator.addListener(new AnimatorListenerAdapter() {
601 @Override
602 public void onAnimationEnd(Animator animation) {
603 mQsExpansionAnimator = null;
604 }
605 });
606 animator.start();
607 mQsExpansionAnimator = animator;
608 }
609
610 /**
611 * @return Whether we should intercept a gesture to open Quick Settings.
612 */
Selim Cinek4c6969a2014-05-26 19:22:17 +0200613 private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200614 if (!mQsExpansionEnabled) {
615 return false;
616 }
Jorim Jaggibc5f4962014-05-12 00:00:52 +0200617 boolean onHeader = x >= mHeader.getLeft() && x <= mHeader.getRight()
John Spurlock3e04cc82014-05-30 12:34:03 -0400618 && y >= mHeader.getTop() && y <= mHeader.getBottom();
Jorim Jaggid7daab72014-05-06 22:22:20 +0200619 if (mQsExpanded) {
John Spurlock3e04cc82014-05-30 12:34:03 -0400620 return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0);
Jorim Jaggid7daab72014-05-06 22:22:20 +0200621 } else {
622 return onHeader;
623 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100624 }
625
626 @Override
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +0200627 public void setVisibility(int visibility) {
628 int oldVisibility = getVisibility();
629 super.setVisibility(visibility);
630 if (visibility != oldVisibility) {
631 reparentStatusIcons(visibility == VISIBLE);
632 }
633 }
634
635 /**
636 * When the notification panel gets expanded, we need to move the status icons in the header
637 * card.
638 */
639 private void reparentStatusIcons(boolean toHeader) {
640 if (mStatusBar == null) {
641 return;
642 }
643 LinearLayout systemIcons = mStatusBar.getSystemIcons();
644 if (systemIcons.getParent() != null) {
645 ((ViewGroup) systemIcons.getParent()).removeView(systemIcons);
646 }
647 if (toHeader) {
648 mHeader.attachSystemIcons(systemIcons);
649 } else {
Jorim Jaggi13c1b1f2014-05-11 21:55:00 +0200650 mHeader.onSystemIconsDetached();
Jorim Jaggi0d74eeb2014-05-09 22:05:24 +0200651 mStatusBar.reattachSystemIcons();
652 }
653 }
654
655 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100656 protected boolean isScrolledToBottom() {
657 if (!isInSettings()) {
658 return mNotificationStackScroller.isScrolledToBottom();
Chris Wren64161cc2012-12-17 16:49:30 -0500659 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100660 return super.isScrolledToBottom();
661 }
662
663 @Override
664 protected int getMaxPanelHeight() {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200665 // TODO: Figure out transition for collapsing when QS is open, adjust height here.
666 int maxPanelHeight = super.getMaxPanelHeight();
667 int emptyBottomMargin = mStackScrollerContainer.getHeight()
668 - mNotificationStackScroller.getHeight()
669 + mNotificationStackScroller.getEmptyBottomMargin();
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200670 int maxHeight = maxPanelHeight - emptyBottomMargin - mTopPaddingAdjustment;
Jorim Jaggi1d480692014-05-20 19:41:58 +0200671 maxHeight = Math.max(maxHeight, mStatusBarMinHeight);
672 return maxHeight;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100673 }
674
675 private boolean isInSettings() {
Jorim Jaggid7daab72014-05-06 22:22:20 +0200676 return mQsExpanded;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100677 }
678
679 @Override
680 protected void onHeightUpdated(float expandedHeight) {
Jorim Jaggi7d16bb12014-05-22 04:24:50 +0200681 if (!mQsExpanded) {
682 positionClockAndNotifications();
683 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200684 mNotificationStackScroller.setStackHeight(expandedHeight);
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100685 }
686
687 @Override
Selim Cinek1685e632014-04-08 02:27:49 +0200688 protected void onExpandingStarted() {
689 super.onExpandingStarted();
690 mNotificationStackScroller.onExpansionStarted();
Selim Cinek4c6969a2014-05-26 19:22:17 +0200691 mIsExpanding = true;
Selim Cinek1685e632014-04-08 02:27:49 +0200692 }
693
694 @Override
695 protected void onExpandingFinished() {
696 super.onExpandingFinished();
697 mNotificationStackScroller.onExpansionStopped();
Selim Cinek4c6969a2014-05-26 19:22:17 +0200698 mIsExpanding = false;
Selim Cinek1685e632014-04-08 02:27:49 +0200699 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200700
701 @Override
Selim Cinek24120a52014-05-26 10:05:42 +0200702 protected void onOverExpansionChanged(float overExpansion) {
703 float currentOverScroll = mNotificationStackScroller.getCurrentOverScrolledPixels(true);
704 mNotificationStackScroller.setOverScrolledPixels(currentOverScroll + overExpansion
705 - mOverExpansion, true /* onTop */, false /* animate */);
706 super.onOverExpansionChanged(overExpansion);
707 }
708
709 @Override
Jorim Jaggi2fbad7b2014-05-26 22:38:00 +0200710 protected void onTrackingStopped(boolean expand) {
711 super.onTrackingStopped(expand);
Selim Cinek24120a52014-05-26 10:05:42 +0200712 mOverExpansion = 0.0f;
713 mNotificationStackScroller.setOverScrolledPixels(0.0f, true /* onTop */,
714 true /* animate */);
715 }
716
717
718 @Override
Jorim Jaggibe565df2014-04-28 17:51:23 +0200719 public void onHeightChanged(ExpandableView view) {
720 requestPanelHeightUpdate();
721 }
Jorim Jaggid7daab72014-05-06 22:22:20 +0200722
723 @Override
724 public void onScrollChanged() {
725 if (mQsExpanded) {
726 mNotificationStackScroller.setTranslationY(
727 mNotificationTranslation - mScrollView.getScrollY());
728 }
729 }
730
731 @Override
Selim Cinek4c6969a2014-05-26 19:22:17 +0200732 protected void onConfigurationChanged(Configuration newConfig) {
733 super.onConfigurationChanged(newConfig);
734 mPageSwiper.onConfigurationChanged();
735 }
736
737 @Override
Jorim Jaggid7daab72014-05-06 22:22:20 +0200738 public void onClick(View v) {
739 if (v == mHeader.getBackgroundView()) {
740 onQsExpansionStarted();
741 if (mQsExpanded) {
742 flingSettings(0 /* vel */, false /* expand */);
743 } else if (mQsExpansionEnabled) {
744 flingSettings(0 /* vel */, true /* expand */);
745 }
746 }
747 }
Selim Cinek4c6969a2014-05-26 19:22:17 +0200748
749 @Override
750 public void onAnimationToSideStarted(boolean rightPage) {
751 if (rightPage) {
752 mKeyguardBottomArea.launchCamera();
753 } else {
754 mKeyguardBottomArea.launchPhone();
755 }
756 mBlockTouches = true;
757 }
758
759
760 @Override
761 public float getPageWidth() {
762 return getWidth();
763 }
764
765 @Override
766 public ArrayList<View> getTranslationViews() {
767 return mSwipeTranslationViews;
768 }
769
770 @Override
771 public View getLeftIcon() {
772 return mKeyguardBottomArea.getPhoneImageView();
773 }
774
775 @Override
776 public View getCenterIcon() {
777 return mKeyguardBottomArea.getLockIcon();
778 }
779
780 @Override
781 public View getRightIcon() {
782 return mKeyguardBottomArea.getCameraImageView();
783 }
Daniel Sandler08d05e32012-08-08 16:39:54 -0400784}