Loading packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +268 −22 Original line number Diff line number Diff line Loading @@ -16,11 +16,15 @@ package com.android.systemui.statusbar.car; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.car.drivingstate.CarDrivingStateEvent; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.Log; import android.view.GestureDetector; Loading Loading @@ -55,6 +59,7 @@ import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.car.CarQSFragment; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.car.hvac.HvacController; import com.android.systemui.statusbar.car.hvac.TemperatureView; Loading @@ -74,6 +79,15 @@ import java.util.Map; public class CarStatusBar extends StatusBar implements CarBatteryController.BatteryViewHandler { private static final String TAG = "CarStatusBar"; // used to calculate how fast to open or close the window private static final float DEFAULT_FLING_VELOCITY = 0; // max time a fling animation takes private static final float FLING_ANIMATION_MAX_TIME = 0.5f; // acceleration rate for the fling animation private static final float FLING_SPEED_UP_FACTOR = 0.6f; private float mOpeningVelocity = DEFAULT_FLING_VELOCITY; private float mClosingVelocity = DEFAULT_FLING_VELOCITY; private TaskStackListenerImpl mTaskStackListener; Loading @@ -100,6 +114,7 @@ public class CarStatusBar extends StatusBar implements private boolean mDeviceIsProvisioned = true; private HvacController mHvacController; private DrivingStateHelper mDrivingStateHelper; private static FlingAnimationUtils sFlingAnimationUtils; private SwitchToGuestTimer mSwitchToGuestTimer; // The container for the notifications. Loading @@ -113,6 +128,27 @@ public class CarStatusBar extends StatusBar implements // it's open. private View.OnTouchListener mNavBarNotificationTouchListener; // Percentage from top of the screen after which the notification shade will open. This value // will be used while opening the notification shade. private int mSettleOpenPercentage; // Percentage from top of the screen below which the notification shade will close. This // value will be used while closing the notification shade. private int mSettleClosePercentage; // Percentage of notification shade open from top of the screen. private int mPercentageFromBottom; // If notification shade is animation to close or to open. private boolean mIsNotificationAnimating; // Tracks when the notification shade is being scrolled. This refers to the glass pane being // scrolled not the recycler view. private boolean mIsTracking; private float mFirstTouchDownOnGlassPane; // If the notification card inside the recycler view is being swiped. private boolean mIsNotificationCardSwiping; // If notification shade is being swiped vertically to close. private boolean mIsSwipingVerticallyToClose; @Override public void start() { // get the provisioned state before calling the parent class since it's that flow that Loading @@ -125,6 +161,12 @@ public class CarStatusBar extends StatusBar implements mActivityManagerWrapper.registerTaskStackListener(mTaskStackListener); mNotificationPanel.setScrollingEnabled(true); mSettleOpenPercentage = mContext.getResources().getInteger( R.integer.notification_settle_open_percentage); mSettleClosePercentage = mContext.getResources().getInteger( R.integer.notification_settle_close_percentage); sFlingAnimationUtils = new FlingAnimationUtils(mContext, FLING_ANIMATION_MAX_TIME, FLING_SPEED_UP_FACTOR); createBatteryController(); mCarBatteryController.startListening(); Loading Loading @@ -313,14 +355,46 @@ public class CarStatusBar extends StatusBar implements } }); mNavBarNotificationTouchListener = (v, event) -> navBarCloseNotificationGestureDetector.onTouchEvent(event); (v, event) -> { boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event); if (consumed) { return true; } if (event.getActionMasked() == MotionEvent.ACTION_UP && mNotificationView.getVisibility() == View.VISIBLE) { if (mSettleClosePercentage < mPercentageFromBottom) { animateNotificationPanel( DEFAULT_FLING_VELOCITY, false); } else { animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); } } return true; }; // The following are the ui elements that the user would call the status bar. // This will set the status bar so it they can make call backs. CarNavigationBarView topBar = mStatusBarWindow.findViewById(R.id.car_top_bar); topBar.setStatusBar(this); topBar.setStatusBarWindowTouchListener((v1, event1) -> openGestureDetector.onTouchEvent(event1)); topBar.setStatusBarWindowTouchListener((v1, event1) -> { boolean consumed = openGestureDetector.onTouchEvent(event1); if (consumed) { return true; } if (event1.getActionMasked() == MotionEvent.ACTION_UP && mNotificationView.getVisibility() == View.VISIBLE) { if (mSettleOpenPercentage > mPercentageFromBottom) { animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); } else { animateNotificationPanel( DEFAULT_FLING_VELOCITY, false); } } return true; } ); NotificationClickHandlerFactory clickHandlerFactory = new NotificationClickHandlerFactory( mBarService, Loading @@ -347,7 +421,10 @@ public class CarStatusBar extends StatusBar implements mNotificationListAtBottomAtTimeOfTouch = false; } if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { mFirstTouchDownOnGlassPane = event.getRawX(); mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom; // Reset the tracker when there is a touch down on the glass pane. mIsTracking = false; // Pass the down event to gesture detector so that it knows where the touch event // started. closeGestureDetector.onTouchEvent(event); Loading @@ -364,23 +441,56 @@ public class CarStatusBar extends StatusBar implements return; } mNotificationListAtBottom = false; mIsSwipingVerticallyToClose = false; mNotificationListAtBottomAtTimeOfTouch = false; } }); mNotificationList.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX()) > SWIPE_MAX_OFF_PATH; if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) { // We need to save the state here as if notification card is swiping we will // change the mNotificationListAtBottomAtTimeOfTouch. This is to protect // closing the notification shade while the notification card is being swiped. mIsSwipingVerticallyToClose = true; } // If the card is swiping we should not allow the notification shade to close. // Hence setting mNotificationListAtBottomAtTimeOfTouch to false will stop that // for us. We are also checking for mIsTracking because while swiping the // notification shade to close if the user goes a bit horizontal while swiping // upwards then also this should close. if (mIsNotificationCardSwiping && !mIsTracking) { mNotificationListAtBottomAtTimeOfTouch = false; } boolean handled = false; if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) { handled = closeGestureDetector.onTouchEvent(event); } boolean isTracking = mIsTracking; Rect rect = mNotificationView.getClipBounds(); float clippedHeight = rect.bottom; if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP && mIsSwipingVerticallyToClose) { if (mSettleClosePercentage < mPercentageFromBottom && isTracking) { animateNotificationPanel(DEFAULT_FLING_VELOCITY, false); } else if (clippedHeight != mNotificationView.getHeight() && isTracking) { // this can be caused when user is at the end of the list and trying to // fling to top of the list by scrolling down. animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); } } // Updating the mNotificationListAtBottomAtTimeOfTouch state has to be done after // the event has been passed to the closeGestureDetector above, such that the // closeGestureDetector sees the up event before the state has changed. if (event.getActionMasked() == MotionEvent.ACTION_UP) { mNotificationListAtBottomAtTimeOfTouch = false; } return handled; return handled || isTracking; } }); Loading @@ -401,7 +511,9 @@ public class CarStatusBar extends StatusBar implements mNotificationList.scrollToPosition(0); mStatusBarWindowController.setPanelVisible(true); mNotificationView.setVisibility(View.VISIBLE); // let the status bar know that the panel is open animateNotificationPanel(mOpeningVelocity, false); setPanelExpanded(true); } Loading @@ -415,12 +527,66 @@ public class CarStatusBar extends StatusBar implements mStatusBarWindowController.setStatusBarFocusable(false); mStatusBarWindow.cancelExpandHelper(); mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor); animateNotificationPanel(mClosingVelocity, true); if (!mIsTracking) { mStatusBarWindowController.setPanelVisible(false); mNotificationView.setVisibility(View.INVISIBLE); // let the status bar know that the panel is cloased } setPanelExpanded(false); } /** * Animates the notification shade from one position to other. This is used to either open or * close the notification shade completely with a velocity. Id the animation is to close the * notification shade this method also makes the view invisible after animation ends. */ private void animateNotificationPanel(float velocity, boolean isClosing) { Rect rect = mNotificationView.getClipBounds(); if (rect == null) { return; } float from = rect.bottom; float to = 0; if (!isClosing) { to = mNotificationView.getHeight(); } if (mIsNotificationAnimating) { return; } mIsNotificationAnimating = true; mIsTracking = true; ValueAnimator animator = ValueAnimator.ofFloat(from, to); animator.addUpdateListener( animation -> { float animatedValue = (Float) animation.getAnimatedValue(); setNotificationViewClipBounds((int) animatedValue); }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mIsNotificationAnimating = false; mIsTracking = false; mOpeningVelocity = DEFAULT_FLING_VELOCITY; mClosingVelocity = DEFAULT_FLING_VELOCITY; if (isClosing) { mStatusBarWindowController.setPanelVisible(false); mNotificationView.setVisibility(View.INVISIBLE); mNotificationView.setClipBounds(null); // let the status bar know that the panel is closed setPanelExpanded(false); } else { // let the status bar know that the panel is open setPanelExpanded(true); } } }); sFlingAnimationUtils.apply(animator, from, to, Math.abs(velocity)); animator.start(); } @Override protected QS createDefaultQSFragment() { Loading Loading @@ -576,7 +742,7 @@ public class CarStatusBar extends StatusBar implements @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { //When executing dump() funciton simultaneously, we need to serialize them //When executing dump() function simultaneously, we need to serialize them //to get mStackScroller's position correctly. synchronized (mQueueLock) { pw.println(" mStackScroller: " + viewInfo(mStackScroller)); Loading Loading @@ -760,38 +926,69 @@ public class CarStatusBar extends StatusBar implements } /** Returns true if the current user makes it through the setup wizard, false otherwise. */ public boolean getIsUserSetup(){ private boolean getIsUserSetup() { return mUserSetup; } private void setNotificationViewClipBounds(int height) { Rect clipBounds = new Rect(); clipBounds.set(0, 0, mNotificationView.getWidth(), height); mNotificationView.setClipBounds(clipBounds); } private void calculatePercentageFromBottom(float height) { if (mNotificationView.getHeight() > 0) { mPercentageFromBottom = (int) Math.abs( height / mNotificationView.getHeight() * 100); } } // TODO: add settle down/up logic private static final int SWIPE_UP_MIN_DISTANCE = 75; private static final int SWIPE_DOWN_MIN_DISTANCE = 25; private static final int SWIPE_MAX_OFF_PATH = 75; private static final int SWIPE_THRESHOLD_VELOCITY = 200; // Only responsible for open hooks. Since once the panel opens it covers all elements // there is no need to merge with close. private abstract class OpenNotificationGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY) { if (mNotificationView.getVisibility() == View.INVISIBLE) { // when the on-scroll is called for the first time to open. mNotificationList.scrollToPosition(0); } mStatusBarWindowController.setPanelVisible(true); mNotificationView.setVisibility(View.VISIBLE); // clips the view for the notification shade when the user scrolls to open. setNotificationViewClipBounds((int) event2.getRawY()); // Initially the scroll starts with height being zero. This checks protects from divide // by zero error. calculatePercentageFromBottom(event2.getRawY()); mIsTracking = true; return true; } @Override public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) { if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) { // swipe was not vertical or was not fast enough return false; } boolean isDown = velocityY > 0; float distanceDelta = Math.abs(event1.getY() - event2.getY()); if (isDown && distanceDelta > SWIPE_DOWN_MIN_DISTANCE) { if (velocityY > SWIPE_THRESHOLD_VELOCITY) { mOpeningVelocity = velocityY; openNotification(); return true; } animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); return false; } protected abstract void openNotification(); } Loading @@ -799,36 +996,85 @@ public class CarStatusBar extends StatusBar implements private abstract class CloseNotificationGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY) { if (!mNotificationListAtBottomAtTimeOfTouch) { return false; } float actualNotificationHeight = mNotificationView.getHeight() - (event1.getRawY() - event2.getRawY()); if (actualNotificationHeight > mNotificationView.getHeight()) { actualNotificationHeight = mNotificationView.getHeight(); } if (mNotificationView.getHeight() > 0) { mPercentageFromBottom = (int) Math.abs( actualNotificationHeight / mNotificationView.getHeight() * 100); boolean isUp = distanceY > 0; // This check is to figure out if onScroll was called while swiping the card at // bottom of the list. At that time we should not allow notification shade to // close. We are also checking for the upwards swipe gesture here because it is // possible if a user is closing the notification shade and while swiping starts // to open again but does not fling. At that time we should allow the // notification shade to close fully or else it would stuck in between. if (Math.abs(mNotificationView.getHeight() - actualNotificationHeight) > SWIPE_DOWN_MIN_DISTANCE && isUp) { setNotificationViewClipBounds((int) actualNotificationHeight); mIsTracking = true; } else if (!isUp) { setNotificationViewClipBounds((int) actualNotificationHeight); } } // if we return true the the items in RV won't be scrollable. return false; } @Override public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) { if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) { // swipe was not vertical or was not fast enough return false; } boolean isUp = velocityY < 0; float distanceDelta = Math.abs(event1.getY() - event2.getY()); if (isUp && distanceDelta > SWIPE_UP_MIN_DISTANCE) { if (isUp) { close(); return true; } else { // we should close the shade animateNotificationPanel(velocityY, false); } return false; } protected abstract void close(); } // to be installed on the nav bars // To be installed on the nav bars. private abstract class NavBarCloseNotificationGestureListener extends CloseNotificationGestureListener { @Override public boolean onSingleTapUp(MotionEvent e) { mClosingVelocity = DEFAULT_FLING_VELOCITY; close(); return super.onSingleTapUp(e); } @Override public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY) { calculatePercentageFromBottom(event2.getRawY()); setNotificationViewClipBounds((int) event2.getRawY()); return true; } @Override public void onLongPress(MotionEvent e) { mClosingVelocity = DEFAULT_FLING_VELOCITY; close(); super.onLongPress(e); } Loading Loading
packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +268 −22 Original line number Diff line number Diff line Loading @@ -16,11 +16,15 @@ package com.android.systemui.statusbar.car; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.car.drivingstate.CarDrivingStateEvent; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.Log; import android.view.GestureDetector; Loading Loading @@ -55,6 +59,7 @@ import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.car.CarQSFragment; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.car.hvac.HvacController; import com.android.systemui.statusbar.car.hvac.TemperatureView; Loading @@ -74,6 +79,15 @@ import java.util.Map; public class CarStatusBar extends StatusBar implements CarBatteryController.BatteryViewHandler { private static final String TAG = "CarStatusBar"; // used to calculate how fast to open or close the window private static final float DEFAULT_FLING_VELOCITY = 0; // max time a fling animation takes private static final float FLING_ANIMATION_MAX_TIME = 0.5f; // acceleration rate for the fling animation private static final float FLING_SPEED_UP_FACTOR = 0.6f; private float mOpeningVelocity = DEFAULT_FLING_VELOCITY; private float mClosingVelocity = DEFAULT_FLING_VELOCITY; private TaskStackListenerImpl mTaskStackListener; Loading @@ -100,6 +114,7 @@ public class CarStatusBar extends StatusBar implements private boolean mDeviceIsProvisioned = true; private HvacController mHvacController; private DrivingStateHelper mDrivingStateHelper; private static FlingAnimationUtils sFlingAnimationUtils; private SwitchToGuestTimer mSwitchToGuestTimer; // The container for the notifications. Loading @@ -113,6 +128,27 @@ public class CarStatusBar extends StatusBar implements // it's open. private View.OnTouchListener mNavBarNotificationTouchListener; // Percentage from top of the screen after which the notification shade will open. This value // will be used while opening the notification shade. private int mSettleOpenPercentage; // Percentage from top of the screen below which the notification shade will close. This // value will be used while closing the notification shade. private int mSettleClosePercentage; // Percentage of notification shade open from top of the screen. private int mPercentageFromBottom; // If notification shade is animation to close or to open. private boolean mIsNotificationAnimating; // Tracks when the notification shade is being scrolled. This refers to the glass pane being // scrolled not the recycler view. private boolean mIsTracking; private float mFirstTouchDownOnGlassPane; // If the notification card inside the recycler view is being swiped. private boolean mIsNotificationCardSwiping; // If notification shade is being swiped vertically to close. private boolean mIsSwipingVerticallyToClose; @Override public void start() { // get the provisioned state before calling the parent class since it's that flow that Loading @@ -125,6 +161,12 @@ public class CarStatusBar extends StatusBar implements mActivityManagerWrapper.registerTaskStackListener(mTaskStackListener); mNotificationPanel.setScrollingEnabled(true); mSettleOpenPercentage = mContext.getResources().getInteger( R.integer.notification_settle_open_percentage); mSettleClosePercentage = mContext.getResources().getInteger( R.integer.notification_settle_close_percentage); sFlingAnimationUtils = new FlingAnimationUtils(mContext, FLING_ANIMATION_MAX_TIME, FLING_SPEED_UP_FACTOR); createBatteryController(); mCarBatteryController.startListening(); Loading Loading @@ -313,14 +355,46 @@ public class CarStatusBar extends StatusBar implements } }); mNavBarNotificationTouchListener = (v, event) -> navBarCloseNotificationGestureDetector.onTouchEvent(event); (v, event) -> { boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event); if (consumed) { return true; } if (event.getActionMasked() == MotionEvent.ACTION_UP && mNotificationView.getVisibility() == View.VISIBLE) { if (mSettleClosePercentage < mPercentageFromBottom) { animateNotificationPanel( DEFAULT_FLING_VELOCITY, false); } else { animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); } } return true; }; // The following are the ui elements that the user would call the status bar. // This will set the status bar so it they can make call backs. CarNavigationBarView topBar = mStatusBarWindow.findViewById(R.id.car_top_bar); topBar.setStatusBar(this); topBar.setStatusBarWindowTouchListener((v1, event1) -> openGestureDetector.onTouchEvent(event1)); topBar.setStatusBarWindowTouchListener((v1, event1) -> { boolean consumed = openGestureDetector.onTouchEvent(event1); if (consumed) { return true; } if (event1.getActionMasked() == MotionEvent.ACTION_UP && mNotificationView.getVisibility() == View.VISIBLE) { if (mSettleOpenPercentage > mPercentageFromBottom) { animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); } else { animateNotificationPanel( DEFAULT_FLING_VELOCITY, false); } } return true; } ); NotificationClickHandlerFactory clickHandlerFactory = new NotificationClickHandlerFactory( mBarService, Loading @@ -347,7 +421,10 @@ public class CarStatusBar extends StatusBar implements mNotificationListAtBottomAtTimeOfTouch = false; } if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { mFirstTouchDownOnGlassPane = event.getRawX(); mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom; // Reset the tracker when there is a touch down on the glass pane. mIsTracking = false; // Pass the down event to gesture detector so that it knows where the touch event // started. closeGestureDetector.onTouchEvent(event); Loading @@ -364,23 +441,56 @@ public class CarStatusBar extends StatusBar implements return; } mNotificationListAtBottom = false; mIsSwipingVerticallyToClose = false; mNotificationListAtBottomAtTimeOfTouch = false; } }); mNotificationList.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX()) > SWIPE_MAX_OFF_PATH; if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) { // We need to save the state here as if notification card is swiping we will // change the mNotificationListAtBottomAtTimeOfTouch. This is to protect // closing the notification shade while the notification card is being swiped. mIsSwipingVerticallyToClose = true; } // If the card is swiping we should not allow the notification shade to close. // Hence setting mNotificationListAtBottomAtTimeOfTouch to false will stop that // for us. We are also checking for mIsTracking because while swiping the // notification shade to close if the user goes a bit horizontal while swiping // upwards then also this should close. if (mIsNotificationCardSwiping && !mIsTracking) { mNotificationListAtBottomAtTimeOfTouch = false; } boolean handled = false; if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) { handled = closeGestureDetector.onTouchEvent(event); } boolean isTracking = mIsTracking; Rect rect = mNotificationView.getClipBounds(); float clippedHeight = rect.bottom; if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP && mIsSwipingVerticallyToClose) { if (mSettleClosePercentage < mPercentageFromBottom && isTracking) { animateNotificationPanel(DEFAULT_FLING_VELOCITY, false); } else if (clippedHeight != mNotificationView.getHeight() && isTracking) { // this can be caused when user is at the end of the list and trying to // fling to top of the list by scrolling down. animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); } } // Updating the mNotificationListAtBottomAtTimeOfTouch state has to be done after // the event has been passed to the closeGestureDetector above, such that the // closeGestureDetector sees the up event before the state has changed. if (event.getActionMasked() == MotionEvent.ACTION_UP) { mNotificationListAtBottomAtTimeOfTouch = false; } return handled; return handled || isTracking; } }); Loading @@ -401,7 +511,9 @@ public class CarStatusBar extends StatusBar implements mNotificationList.scrollToPosition(0); mStatusBarWindowController.setPanelVisible(true); mNotificationView.setVisibility(View.VISIBLE); // let the status bar know that the panel is open animateNotificationPanel(mOpeningVelocity, false); setPanelExpanded(true); } Loading @@ -415,12 +527,66 @@ public class CarStatusBar extends StatusBar implements mStatusBarWindowController.setStatusBarFocusable(false); mStatusBarWindow.cancelExpandHelper(); mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor); animateNotificationPanel(mClosingVelocity, true); if (!mIsTracking) { mStatusBarWindowController.setPanelVisible(false); mNotificationView.setVisibility(View.INVISIBLE); // let the status bar know that the panel is cloased } setPanelExpanded(false); } /** * Animates the notification shade from one position to other. This is used to either open or * close the notification shade completely with a velocity. Id the animation is to close the * notification shade this method also makes the view invisible after animation ends. */ private void animateNotificationPanel(float velocity, boolean isClosing) { Rect rect = mNotificationView.getClipBounds(); if (rect == null) { return; } float from = rect.bottom; float to = 0; if (!isClosing) { to = mNotificationView.getHeight(); } if (mIsNotificationAnimating) { return; } mIsNotificationAnimating = true; mIsTracking = true; ValueAnimator animator = ValueAnimator.ofFloat(from, to); animator.addUpdateListener( animation -> { float animatedValue = (Float) animation.getAnimatedValue(); setNotificationViewClipBounds((int) animatedValue); }); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mIsNotificationAnimating = false; mIsTracking = false; mOpeningVelocity = DEFAULT_FLING_VELOCITY; mClosingVelocity = DEFAULT_FLING_VELOCITY; if (isClosing) { mStatusBarWindowController.setPanelVisible(false); mNotificationView.setVisibility(View.INVISIBLE); mNotificationView.setClipBounds(null); // let the status bar know that the panel is closed setPanelExpanded(false); } else { // let the status bar know that the panel is open setPanelExpanded(true); } } }); sFlingAnimationUtils.apply(animator, from, to, Math.abs(velocity)); animator.start(); } @Override protected QS createDefaultQSFragment() { Loading Loading @@ -576,7 +742,7 @@ public class CarStatusBar extends StatusBar implements @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { //When executing dump() funciton simultaneously, we need to serialize them //When executing dump() function simultaneously, we need to serialize them //to get mStackScroller's position correctly. synchronized (mQueueLock) { pw.println(" mStackScroller: " + viewInfo(mStackScroller)); Loading Loading @@ -760,38 +926,69 @@ public class CarStatusBar extends StatusBar implements } /** Returns true if the current user makes it through the setup wizard, false otherwise. */ public boolean getIsUserSetup(){ private boolean getIsUserSetup() { return mUserSetup; } private void setNotificationViewClipBounds(int height) { Rect clipBounds = new Rect(); clipBounds.set(0, 0, mNotificationView.getWidth(), height); mNotificationView.setClipBounds(clipBounds); } private void calculatePercentageFromBottom(float height) { if (mNotificationView.getHeight() > 0) { mPercentageFromBottom = (int) Math.abs( height / mNotificationView.getHeight() * 100); } } // TODO: add settle down/up logic private static final int SWIPE_UP_MIN_DISTANCE = 75; private static final int SWIPE_DOWN_MIN_DISTANCE = 25; private static final int SWIPE_MAX_OFF_PATH = 75; private static final int SWIPE_THRESHOLD_VELOCITY = 200; // Only responsible for open hooks. Since once the panel opens it covers all elements // there is no need to merge with close. private abstract class OpenNotificationGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY) { if (mNotificationView.getVisibility() == View.INVISIBLE) { // when the on-scroll is called for the first time to open. mNotificationList.scrollToPosition(0); } mStatusBarWindowController.setPanelVisible(true); mNotificationView.setVisibility(View.VISIBLE); // clips the view for the notification shade when the user scrolls to open. setNotificationViewClipBounds((int) event2.getRawY()); // Initially the scroll starts with height being zero. This checks protects from divide // by zero error. calculatePercentageFromBottom(event2.getRawY()); mIsTracking = true; return true; } @Override public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) { if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) { // swipe was not vertical or was not fast enough return false; } boolean isDown = velocityY > 0; float distanceDelta = Math.abs(event1.getY() - event2.getY()); if (isDown && distanceDelta > SWIPE_DOWN_MIN_DISTANCE) { if (velocityY > SWIPE_THRESHOLD_VELOCITY) { mOpeningVelocity = velocityY; openNotification(); return true; } animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); return false; } protected abstract void openNotification(); } Loading @@ -799,36 +996,85 @@ public class CarStatusBar extends StatusBar implements private abstract class CloseNotificationGestureListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY) { if (!mNotificationListAtBottomAtTimeOfTouch) { return false; } float actualNotificationHeight = mNotificationView.getHeight() - (event1.getRawY() - event2.getRawY()); if (actualNotificationHeight > mNotificationView.getHeight()) { actualNotificationHeight = mNotificationView.getHeight(); } if (mNotificationView.getHeight() > 0) { mPercentageFromBottom = (int) Math.abs( actualNotificationHeight / mNotificationView.getHeight() * 100); boolean isUp = distanceY > 0; // This check is to figure out if onScroll was called while swiping the card at // bottom of the list. At that time we should not allow notification shade to // close. We are also checking for the upwards swipe gesture here because it is // possible if a user is closing the notification shade and while swiping starts // to open again but does not fling. At that time we should allow the // notification shade to close fully or else it would stuck in between. if (Math.abs(mNotificationView.getHeight() - actualNotificationHeight) > SWIPE_DOWN_MIN_DISTANCE && isUp) { setNotificationViewClipBounds((int) actualNotificationHeight); mIsTracking = true; } else if (!isUp) { setNotificationViewClipBounds((int) actualNotificationHeight); } } // if we return true the the items in RV won't be scrollable. return false; } @Override public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) { if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) { // swipe was not vertical or was not fast enough return false; } boolean isUp = velocityY < 0; float distanceDelta = Math.abs(event1.getY() - event2.getY()); if (isUp && distanceDelta > SWIPE_UP_MIN_DISTANCE) { if (isUp) { close(); return true; } else { // we should close the shade animateNotificationPanel(velocityY, false); } return false; } protected abstract void close(); } // to be installed on the nav bars // To be installed on the nav bars. private abstract class NavBarCloseNotificationGestureListener extends CloseNotificationGestureListener { @Override public boolean onSingleTapUp(MotionEvent e) { mClosingVelocity = DEFAULT_FLING_VELOCITY; close(); return super.onSingleTapUp(e); } @Override public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY) { calculatePercentageFromBottom(event2.getRawY()); setNotificationViewClipBounds((int) event2.getRawY()); return true; } @Override public void onLongPress(MotionEvent e) { mClosingVelocity = DEFAULT_FLING_VELOCITY; close(); super.onLongPress(e); } Loading