1 /* 2 * Copyright (C) 2018 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 17 package com.android.systemui.statusbar.car; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ValueAnimator; 22 import android.annotation.Nullable; 23 import android.app.ActivityManager; 24 import android.app.ActivityTaskManager; 25 import android.car.Car; 26 import android.car.drivingstate.CarDrivingStateEvent; 27 import android.car.drivingstate.CarUxRestrictionsManager; 28 import android.car.hardware.power.CarPowerManager.CarPowerStateListener; 29 import android.content.Context; 30 import android.graphics.PixelFormat; 31 import android.graphics.Rect; 32 import android.graphics.drawable.Drawable; 33 import android.util.Log; 34 import android.view.GestureDetector; 35 import android.view.Gravity; 36 import android.view.MotionEvent; 37 import android.view.View; 38 import android.view.ViewGroup; 39 import android.view.ViewGroup.LayoutParams; 40 import android.view.ViewTreeObserver; 41 import android.view.WindowManager; 42 43 import androidx.annotation.NonNull; 44 import androidx.recyclerview.widget.RecyclerView; 45 46 import com.android.car.notification.CarHeadsUpNotificationManager; 47 import com.android.car.notification.CarNotificationListener; 48 import com.android.car.notification.CarNotificationView; 49 import com.android.car.notification.CarUxRestrictionManagerWrapper; 50 import com.android.car.notification.HeadsUpEntry; 51 import com.android.car.notification.NotificationClickHandlerFactory; 52 import com.android.car.notification.NotificationDataManager; 53 import com.android.car.notification.NotificationViewController; 54 import com.android.car.notification.PreprocessingManager; 55 import com.android.internal.statusbar.RegisterStatusBarResult; 56 import com.android.keyguard.KeyguardUpdateMonitor; 57 import com.android.systemui.BatteryMeterView; 58 import com.android.systemui.CarSystemUIFactory; 59 import com.android.systemui.Dependency; 60 import com.android.systemui.Prefs; 61 import com.android.systemui.R; 62 import com.android.systemui.SystemUIFactory; 63 import com.android.systemui.classifier.FalsingLog; 64 import com.android.systemui.classifier.FalsingManagerFactory; 65 import com.android.systemui.fragments.FragmentHostManager; 66 import com.android.systemui.keyguard.ScreenLifecycle; 67 import com.android.systemui.plugins.qs.QS; 68 import com.android.systemui.qs.car.CarQSFragment; 69 import com.android.systemui.shared.system.ActivityManagerWrapper; 70 import com.android.systemui.shared.system.TaskStackChangeListener; 71 import com.android.systemui.statusbar.FlingAnimationUtils; 72 import com.android.systemui.statusbar.StatusBarState; 73 import com.android.systemui.statusbar.car.hvac.HvacController; 74 import com.android.systemui.statusbar.car.hvac.TemperatureView; 75 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; 76 import com.android.systemui.statusbar.phone.StatusBar; 77 import com.android.systemui.statusbar.policy.BatteryController; 78 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 79 import com.android.systemui.statusbar.policy.UserSwitcherController; 80 81 import java.io.FileDescriptor; 82 import java.io.PrintWriter; 83 import java.util.Map; 84 85 /** 86 * A status bar (and navigation bar) tailored for the automotive use case. 87 */ 88 public class CarStatusBar extends StatusBar implements 89 CarBatteryController.BatteryViewHandler { 90 private static final String TAG = "CarStatusBar"; 91 // used to calculate how fast to open or close the window 92 private static final float DEFAULT_FLING_VELOCITY = 0; 93 // max time a fling animation takes 94 private static final float FLING_ANIMATION_MAX_TIME = 0.5f; 95 // acceleration rate for the fling animation 96 private static final float FLING_SPEED_UP_FACTOR = 0.6f; 97 98 private float mOpeningVelocity = DEFAULT_FLING_VELOCITY; 99 private float mClosingVelocity = DEFAULT_FLING_VELOCITY; 100 101 private TaskStackListenerImpl mTaskStackListener; 102 103 private FullscreenUserSwitcher mFullscreenUserSwitcher; 104 105 private CarBatteryController mCarBatteryController; 106 private BatteryMeterView mBatteryMeterView; 107 private Drawable mNotificationPanelBackground; 108 109 private ViewGroup mNavigationBarWindow; 110 private ViewGroup mLeftNavigationBarWindow; 111 private ViewGroup mRightNavigationBarWindow; 112 private CarNavigationBarView mNavigationBarView; 113 private CarNavigationBarView mLeftNavigationBarView; 114 private CarNavigationBarView mRightNavigationBarView; 115 116 private final Object mQueueLock = new Object(); 117 private boolean mShowLeft; 118 private boolean mShowRight; 119 private boolean mShowBottom; 120 private CarFacetButtonController mCarFacetButtonController; 121 private ActivityManagerWrapper mActivityManagerWrapper; 122 private DeviceProvisionedController mDeviceProvisionedController; 123 private boolean mDeviceIsProvisioned = true; 124 private HvacController mHvacController; 125 private DrivingStateHelper mDrivingStateHelper; 126 private PowerManagerHelper mPowerManagerHelper; 127 private FlingAnimationUtils mFlingAnimationUtils; 128 private SwitchToGuestTimer mSwitchToGuestTimer; 129 private NotificationDataManager mNotificationDataManager; 130 private NotificationClickHandlerFactory mNotificationClickHandlerFactory; 131 private ScreenLifecycle mScreenLifecycle; 132 133 // The container for the notifications. 134 private CarNotificationView mNotificationView; 135 private RecyclerView mNotificationList; 136 // The handler bar view at the bottom of notification shade. 137 private View mHandleBar; 138 // The controller for the notification view. 139 private NotificationViewController mNotificationViewController; 140 // The state of if the notification list is currently showing the bottom. 141 private boolean mNotificationListAtBottom; 142 // Was the notification list at the bottom when the user first touched the screen 143 private boolean mNotificationListAtBottomAtTimeOfTouch; 144 // To be attached to the navigation bars such that they can close the notification panel if 145 // it's open. 146 private View.OnTouchListener mNavBarNotificationTouchListener; 147 148 // Percentage from top of the screen after which the notification shade will open. This value 149 // will be used while opening the notification shade. 150 private int mSettleOpenPercentage; 151 // Percentage from top of the screen below which the notification shade will close. This 152 // value will be used while closing the notification shade. 153 private int mSettleClosePercentage; 154 // Percentage of notification shade open from top of the screen. 155 private int mPercentageFromBottom; 156 // If notification shade is animation to close or to open. 157 private boolean mIsNotificationAnimating; 158 159 // Tracks when the notification shade is being scrolled. This refers to the glass pane being 160 // scrolled not the recycler view. 161 private boolean mIsTracking; 162 private float mFirstTouchDownOnGlassPane; 163 164 // If the notification card inside the recycler view is being swiped. 165 private boolean mIsNotificationCardSwiping; 166 // If notification shade is being swiped vertically to close. 167 private boolean mIsSwipingVerticallyToClose; 168 // Whether heads-up notifications should be shown when shade is open. 169 private boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen; 170 171 private final CarPowerStateListener mCarPowerStateListener = 172 (int state) -> { 173 // When the car powers on, clear all notifications and mute/unread states. 174 Log.d(TAG, "New car power state: " + state); 175 if (state == CarPowerStateListener.ON) { 176 if (mNotificationClickHandlerFactory != null) { 177 mNotificationClickHandlerFactory.clearAllNotifications(); 178 } 179 if (mNotificationDataManager != null) { 180 mNotificationDataManager.clearAll(); 181 } 182 } 183 }; 184 185 @Override start()186 public void start() { 187 // get the provisioned state before calling the parent class since it's that flow that 188 // builds the nav bar 189 mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); 190 mDeviceIsProvisioned = mDeviceProvisionedController.isDeviceProvisioned(); 191 super.start(); 192 mTaskStackListener = new TaskStackListenerImpl(); 193 mActivityManagerWrapper = ActivityManagerWrapper.getInstance(); 194 mActivityManagerWrapper.registerTaskStackListener(mTaskStackListener); 195 196 mNotificationPanel.setScrollingEnabled(true); 197 mSettleOpenPercentage = mContext.getResources().getInteger( 198 R.integer.notification_settle_open_percentage); 199 mSettleClosePercentage = mContext.getResources().getInteger( 200 R.integer.notification_settle_close_percentage); 201 mFlingAnimationUtils = new FlingAnimationUtils(mContext, 202 FLING_ANIMATION_MAX_TIME, FLING_SPEED_UP_FACTOR); 203 204 createBatteryController(); 205 mCarBatteryController.startListening(); 206 207 mHvacController.connectToCarService(); 208 209 CarSystemUIFactory factory = SystemUIFactory.getInstance(); 210 if (!mDeviceIsProvisioned) { 211 mDeviceProvisionedController.addCallback( 212 new DeviceProvisionedController.DeviceProvisionedListener() { 213 @Override 214 public void onDeviceProvisionedChanged() { 215 mHandler.post(() -> { 216 // on initial boot we are getting a call even though the value 217 // is the same so we are confirming the reset is needed 218 boolean deviceProvisioned = 219 mDeviceProvisionedController.isDeviceProvisioned(); 220 if (mDeviceIsProvisioned != deviceProvisioned) { 221 mDeviceIsProvisioned = deviceProvisioned; 222 restartNavBars(); 223 } 224 }); 225 } 226 }); 227 } 228 229 // Register a listener for driving state changes. 230 mDrivingStateHelper = new DrivingStateHelper(mContext, this::onDrivingStateChanged); 231 mDrivingStateHelper.connectToCarService(); 232 233 mPowerManagerHelper = new PowerManagerHelper(mContext, mCarPowerStateListener); 234 mPowerManagerHelper.connectToCarService(); 235 236 mSwitchToGuestTimer = new SwitchToGuestTimer(mContext); 237 238 mScreenLifecycle = Dependency.get(ScreenLifecycle.class); 239 mScreenLifecycle.addObserver(mScreenObserver); 240 } 241 242 /** 243 * Remove all content from navbars and rebuild them. Used to allow for different nav bars 244 * before and after the device is provisioned. . Also for change of density and font size. 245 */ restartNavBars()246 private void restartNavBars() { 247 // remove and reattach all hvac components such that we don't keep a reference to unused 248 // ui elements 249 mHvacController.removeAllComponents(); 250 addTemperatureViewToController(mStatusBarWindow); 251 mCarFacetButtonController.removeAll(); 252 if (mNavigationBarWindow != null) { 253 mNavigationBarWindow.removeAllViews(); 254 mNavigationBarView = null; 255 } 256 257 if (mLeftNavigationBarWindow != null) { 258 mLeftNavigationBarWindow.removeAllViews(); 259 mLeftNavigationBarView = null; 260 } 261 262 if (mRightNavigationBarWindow != null) { 263 mRightNavigationBarWindow.removeAllViews(); 264 mRightNavigationBarView = null; 265 } 266 267 buildNavBarContent(); 268 // If the UI was rebuilt (day/night change) while the keyguard was up we need to 269 // correctly respect that state. 270 if (mIsKeyguard) { 271 updateNavBarForKeyguardContent(); 272 } 273 // CarFacetButtonController was reset therefore we need to re-add the status bar elements 274 // to the controller. 275 mCarFacetButtonController.addAllFacetButtons(mStatusBarWindow); 276 } 277 addTemperatureViewToController(View v)278 private void addTemperatureViewToController(View v) { 279 if (v instanceof TemperatureView) { 280 mHvacController.addHvacTextView((TemperatureView) v); 281 } else if (v instanceof ViewGroup) { 282 ViewGroup viewGroup = (ViewGroup) v; 283 for (int i = 0; i < viewGroup.getChildCount(); i++) { 284 addTemperatureViewToController(viewGroup.getChildAt(i)); 285 } 286 } 287 } 288 289 /** 290 * Allows for showing or hiding just the navigation bars. This is indented to be used when 291 * the full screen user selector is shown. 292 */ setNavBarVisibility(@iew.Visibility int visibility)293 void setNavBarVisibility(@View.Visibility int visibility) { 294 if (mNavigationBarWindow != null) { 295 mNavigationBarWindow.setVisibility(visibility); 296 } 297 if (mLeftNavigationBarWindow != null) { 298 mLeftNavigationBarWindow.setVisibility(visibility); 299 } 300 if (mRightNavigationBarWindow != null) { 301 mRightNavigationBarWindow.setVisibility(visibility); 302 } 303 } 304 305 306 @Override hideKeyguard()307 public boolean hideKeyguard() { 308 boolean result = super.hideKeyguard(); 309 if (mNavigationBarView != null) { 310 mNavigationBarView.hideKeyguardButtons(); 311 } 312 if (mLeftNavigationBarView != null) { 313 mLeftNavigationBarView.hideKeyguardButtons(); 314 } 315 if (mRightNavigationBarView != null) { 316 mRightNavigationBarView.hideKeyguardButtons(); 317 } 318 return result; 319 } 320 321 @Override showKeyguard()322 public void showKeyguard() { 323 super.showKeyguard(); 324 updateNavBarForKeyguardContent(); 325 } 326 327 /** 328 * Switch to the keyguard applicable content contained in the nav bars 329 */ updateNavBarForKeyguardContent()330 private void updateNavBarForKeyguardContent() { 331 if (mNavigationBarView != null) { 332 mNavigationBarView.showKeyguardButtons(); 333 } 334 if (mLeftNavigationBarView != null) { 335 mLeftNavigationBarView.showKeyguardButtons(); 336 } 337 if (mRightNavigationBarView != null) { 338 mRightNavigationBarView.showKeyguardButtons(); 339 } 340 } 341 342 @Override makeStatusBarView(@ullable RegisterStatusBarResult result)343 protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) { 344 super.makeStatusBarView(result); 345 mHvacController = new HvacController(mContext); 346 347 CarSystemUIFactory factory = SystemUIFactory.getInstance(); 348 mCarFacetButtonController = factory.getCarDependencyComponent() 349 .getCarFacetButtonController(); 350 mNotificationPanelBackground = getDefaultWallpaper(); 351 mScrimController.setScrimBehindDrawable(mNotificationPanelBackground); 352 353 FragmentHostManager manager = FragmentHostManager.get(mStatusBarWindow); 354 manager.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> { 355 mBatteryMeterView = fragment.getView().findViewById(R.id.battery); 356 357 // By default, the BatteryMeterView should not be visible. It will be toggled 358 // when a device has connected by bluetooth. 359 mBatteryMeterView.setVisibility(View.GONE); 360 }); 361 362 connectNotificationsUI(); 363 } 364 365 /** 366 * Attach the notification listeners and controllers to the UI as well as build all the 367 * touch listeners needed for opening and closing the notification panel 368 */ connectNotificationsUI()369 private void connectNotificationsUI() { 370 // Attached to the status bar to detect pull down of the notification shade. 371 GestureDetector openGestureDetector = new GestureDetector(mContext, 372 new OpenNotificationGestureListener() { 373 @Override 374 protected void openNotification() { 375 animateExpandNotificationsPanel(); 376 } 377 }); 378 // Attached to the notification ui to detect close request of the notification shade. 379 GestureDetector closeGestureDetector = new GestureDetector(mContext, 380 new CloseNotificationGestureListener() { 381 @Override 382 protected void close() { 383 if (mPanelExpanded) { 384 animateCollapsePanels(); 385 } 386 } 387 }); 388 // Attached to the NavBars to close the notification shade 389 GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(mContext, 390 new NavBarCloseNotificationGestureListener() { 391 @Override 392 protected void close() { 393 if (mPanelExpanded) { 394 animateCollapsePanels(); 395 } 396 } 397 }); 398 399 // Attached to the Handle bar to close the notification shade 400 GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext, 401 new HandleBarCloseNotificationGestureListener()); 402 403 mNavBarNotificationTouchListener = 404 (v, event) -> { 405 boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event); 406 if (consumed) { 407 return true; 408 } 409 maybeCompleteAnimation(event); 410 return true; 411 }; 412 413 // The following are the ui elements that the user would call the status bar. 414 // This will set the status bar so it they can make call backs. 415 CarNavigationBarView topBar = mStatusBarWindow.findViewById(R.id.car_top_bar); 416 topBar.setStatusBar(this); 417 topBar.setStatusBarWindowTouchListener((v1, event1) -> { 418 419 boolean consumed = openGestureDetector.onTouchEvent(event1); 420 if (consumed) { 421 return true; 422 } 423 maybeCompleteAnimation(event1); 424 return true; 425 } 426 ); 427 428 mNotificationClickHandlerFactory = new NotificationClickHandlerFactory( 429 mBarService, 430 launchResult -> { 431 if (launchResult == ActivityManager.START_TASK_TO_FRONT 432 || launchResult == ActivityManager.START_SUCCESS) { 433 animateCollapsePanels(); 434 } 435 }); 436 Car car = Car.createCar(mContext); 437 CarUxRestrictionsManager carUxRestrictionsManager = (CarUxRestrictionsManager) 438 car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE); 439 CarNotificationListener carNotificationListener = new CarNotificationListener(); 440 CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper = 441 new CarUxRestrictionManagerWrapper(); 442 carUxRestrictionManagerWrapper.setCarUxRestrictionsManager(carUxRestrictionsManager); 443 444 mNotificationDataManager = new NotificationDataManager(); 445 mNotificationDataManager.setOnUnseenCountUpdateListener( 446 () -> { 447 if (mNavigationBarView != null && mNotificationDataManager != null) { 448 Boolean hasUnseen = 449 mNotificationDataManager.getUnseenNotificationCount() > 0; 450 if (mNavigationBarView != null) { 451 mNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen); 452 } 453 454 if (mLeftNavigationBarView != null) { 455 mLeftNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen); 456 } 457 458 if (mRightNavigationBarView != null) { 459 mRightNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen); 460 } 461 } 462 }); 463 464 mEnableHeadsUpNotificationWhenNotificationShadeOpen = mContext.getResources().getBoolean( 465 R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen); 466 CarHeadsUpNotificationManager carHeadsUpNotificationManager = 467 new CarSystemUIHeadsUpNotificationManager(mContext, 468 mNotificationClickHandlerFactory, mNotificationDataManager); 469 mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager); 470 471 carNotificationListener.registerAsSystemService(mContext, carUxRestrictionManagerWrapper, 472 carHeadsUpNotificationManager, mNotificationDataManager); 473 474 mNotificationView = mStatusBarWindow.findViewById(R.id.notification_view); 475 View glassPane = mStatusBarWindow.findViewById(R.id.glass_pane); 476 mHandleBar = mStatusBarWindow.findViewById(R.id.handle_bar); 477 mNotificationView.setClickHandlerFactory(mNotificationClickHandlerFactory); 478 mNotificationView.setNotificationDataManager(mNotificationDataManager); 479 480 // The glass pane is used to view touch events before passed to the notification list. 481 // This allows us to initialize gesture listeners and detect when to close the notifications 482 glassPane.setOnTouchListener((v, event) -> { 483 if (event.getActionMasked() == MotionEvent.ACTION_UP) { 484 mNotificationListAtBottomAtTimeOfTouch = false; 485 } 486 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 487 mFirstTouchDownOnGlassPane = event.getRawX(); 488 mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom; 489 // Reset the tracker when there is a touch down on the glass pane. 490 mIsTracking = false; 491 // Pass the down event to gesture detector so that it knows where the touch event 492 // started. 493 closeGestureDetector.onTouchEvent(event); 494 } 495 return false; 496 }); 497 498 mHandleBar.setOnTouchListener((v, event) -> { 499 handleBarCloseNotificationGestureDetector.onTouchEvent(event); 500 maybeCompleteAnimation(event); 501 return true; 502 }); 503 504 mNotificationList = mNotificationView.findViewById(R.id.notifications); 505 mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() { 506 @Override 507 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 508 super.onScrolled(recyclerView, dx, dy); 509 if (!mNotificationList.canScrollVertically(1)) { 510 mNotificationListAtBottom = true; 511 return; 512 } 513 mNotificationListAtBottom = false; 514 mIsSwipingVerticallyToClose = false; 515 mNotificationListAtBottomAtTimeOfTouch = false; 516 } 517 }); 518 mNotificationList.setOnTouchListener(new View.OnTouchListener() { 519 @Override 520 public boolean onTouch(View v, MotionEvent event) { 521 mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX()) 522 > SWIPE_MAX_OFF_PATH; 523 if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) { 524 // We need to save the state here as if notification card is swiping we will 525 // change the mNotificationListAtBottomAtTimeOfTouch. This is to protect 526 // closing the notification shade while the notification card is being swiped. 527 mIsSwipingVerticallyToClose = true; 528 } 529 530 // If the card is swiping we should not allow the notification shade to close. 531 // Hence setting mNotificationListAtBottomAtTimeOfTouch to false will stop that 532 // for us. We are also checking for mIsTracking because while swiping the 533 // notification shade to close if the user goes a bit horizontal while swiping 534 // upwards then also this should close. 535 if (mIsNotificationCardSwiping && !mIsTracking) { 536 mNotificationListAtBottomAtTimeOfTouch = false; 537 } 538 539 boolean handled = closeGestureDetector.onTouchEvent(event); 540 boolean isTracking = mIsTracking; 541 Rect rect = mNotificationView.getClipBounds(); 542 float clippedHeight = 0; 543 if (rect != null) { 544 clippedHeight = rect.bottom; 545 } 546 if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP 547 && mIsSwipingVerticallyToClose) { 548 if (mSettleClosePercentage < mPercentageFromBottom && isTracking) { 549 animateNotificationPanel(DEFAULT_FLING_VELOCITY, false); 550 } else if (clippedHeight != mNotificationView.getHeight() && isTracking) { 551 // this can be caused when user is at the end of the list and trying to 552 // fling to top of the list by scrolling down. 553 animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); 554 } 555 } 556 557 // Updating the mNotificationListAtBottomAtTimeOfTouch state has to be done after 558 // the event has been passed to the closeGestureDetector above, such that the 559 // closeGestureDetector sees the up event before the state has changed. 560 if (event.getActionMasked() == MotionEvent.ACTION_UP) { 561 mNotificationListAtBottomAtTimeOfTouch = false; 562 } 563 return handled || isTracking; 564 } 565 }); 566 567 mNotificationViewController = new NotificationViewController( 568 mNotificationView, 569 PreprocessingManager.getInstance(mContext), 570 carNotificationListener, 571 carUxRestrictionManagerWrapper, 572 mNotificationDataManager); 573 mNotificationViewController.enable(); 574 } 575 576 /** 577 * @return true if the notification panel is currently visible 578 */ isNotificationPanelOpen()579 boolean isNotificationPanelOpen() { 580 return mPanelExpanded; 581 } 582 583 @Override animateExpandNotificationsPanel()584 public void animateExpandNotificationsPanel() { 585 if (!mCommandQueue.panelsEnabled() || !mUserSetup) { 586 return; 587 } 588 // scroll to top 589 mNotificationList.scrollToPosition(0); 590 mStatusBarWindowController.setPanelVisible(true); 591 mNotificationView.setVisibility(View.VISIBLE); 592 animateNotificationPanel(mOpeningVelocity, false); 593 594 setPanelExpanded(true); 595 } 596 597 @Override animateCollapsePanels(int flags, boolean force, boolean delayed, float speedUpFactor)598 public void animateCollapsePanels(int flags, boolean force, boolean delayed, 599 float speedUpFactor) { 600 super.animateCollapsePanels(flags, force, delayed, speedUpFactor); 601 if (!mPanelExpanded || mNotificationView.getVisibility() == View.INVISIBLE) { 602 return; 603 } 604 mStatusBarWindowController.setStatusBarFocusable(false); 605 mStatusBarWindow.cancelExpandHelper(); 606 mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor); 607 608 animateNotificationPanel(mClosingVelocity, true); 609 610 if (!mIsTracking) { 611 mStatusBarWindowController.setPanelVisible(false); 612 mNotificationView.setVisibility(View.INVISIBLE); 613 } 614 615 setPanelExpanded(false); 616 } 617 maybeCompleteAnimation(MotionEvent event)618 private void maybeCompleteAnimation(MotionEvent event) { 619 if (event.getActionMasked() == MotionEvent.ACTION_UP 620 && mNotificationView.getVisibility() == View.VISIBLE) { 621 if (mSettleClosePercentage < mPercentageFromBottom) { 622 animateNotificationPanel(DEFAULT_FLING_VELOCITY, false); 623 } else { 624 animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); 625 } 626 } 627 } 628 629 /** 630 * Animates the notification shade from one position to other. This is used to either open or 631 * close the notification shade completely with a velocity. If the animation is to close the 632 * notification shade this method also makes the view invisible after animation ends. 633 */ animateNotificationPanel(float velocity, boolean isClosing)634 private void animateNotificationPanel(float velocity, boolean isClosing) { 635 float to = 0; 636 if (!isClosing) { 637 to = mNotificationView.getHeight(); 638 } 639 640 Rect rect = mNotificationView.getClipBounds(); 641 if (rect != null) { 642 float from = rect.bottom; 643 animate(from, to, velocity, isClosing); 644 return; 645 } 646 647 // We will only be here if the shade is being opened programmatically. 648 ViewTreeObserver notificationTreeObserver = mNotificationView.getViewTreeObserver(); 649 notificationTreeObserver.addOnGlobalLayoutListener( 650 new ViewTreeObserver.OnGlobalLayoutListener() { 651 @Override 652 public void onGlobalLayout() { 653 ViewTreeObserver obs = mNotificationView.getViewTreeObserver(); 654 obs.removeOnGlobalLayoutListener(this); 655 float to = mNotificationView.getHeight(); 656 animate(/* from= */ 0, to, velocity, isClosing); 657 } 658 }); 659 } 660 animate(float from, float to, float velocity, boolean isClosing)661 private void animate(float from, float to, float velocity, boolean isClosing) { 662 if (mIsNotificationAnimating) { 663 return; 664 } 665 mIsNotificationAnimating = true; 666 mIsTracking = true; 667 ValueAnimator animator = ValueAnimator.ofFloat(from, to); 668 animator.addUpdateListener( 669 animation -> { 670 float animatedValue = (Float) animation.getAnimatedValue(); 671 setNotificationViewClipBounds((int) animatedValue); 672 }); 673 animator.addListener(new AnimatorListenerAdapter() { 674 @Override 675 public void onAnimationEnd(Animator animation) { 676 super.onAnimationEnd(animation); 677 mIsNotificationAnimating = false; 678 mIsTracking = false; 679 mOpeningVelocity = DEFAULT_FLING_VELOCITY; 680 mClosingVelocity = DEFAULT_FLING_VELOCITY; 681 if (isClosing) { 682 mStatusBarWindowController.setPanelVisible(false); 683 mNotificationView.setVisibility(View.INVISIBLE); 684 mNotificationView.setClipBounds(null); 685 mNotificationViewController.setIsInForeground(false); 686 // let the status bar know that the panel is closed 687 setPanelExpanded(false); 688 } else { 689 mNotificationViewController.setIsInForeground(true); 690 // let the status bar know that the panel is open 691 mNotificationView.setVisibleNotificationsAsSeen(); 692 setPanelExpanded(true); 693 } 694 } 695 }); 696 mFlingAnimationUtils.apply(animator, from, to, Math.abs(velocity)); 697 animator.start(); 698 } 699 700 @Override createDefaultQSFragment()701 protected QS createDefaultQSFragment() { 702 return new CarQSFragment(); 703 } 704 createBatteryController()705 private BatteryController createBatteryController() { 706 mCarBatteryController = new CarBatteryController(mContext); 707 mCarBatteryController.addBatteryViewHandler(this); 708 return mCarBatteryController; 709 } 710 711 @Override createNavigationBar(@ullable RegisterStatusBarResult result)712 protected void createNavigationBar(@Nullable RegisterStatusBarResult result) { 713 mShowBottom = mContext.getResources().getBoolean(R.bool.config_enableBottomNavigationBar); 714 mShowLeft = mContext.getResources().getBoolean(R.bool.config_enableLeftNavigationBar); 715 mShowRight = mContext.getResources().getBoolean(R.bool.config_enableRightNavigationBar); 716 717 buildNavBarWindows(); 718 buildNavBarContent(); 719 attachNavBarWindows(); 720 721 // There has been a car customized nav bar on the default display, so just create nav bars 722 // on external displays. 723 mNavigationBarController.createNavigationBars(false /* includeDefaultDisplay */, result); 724 } 725 buildNavBarContent()726 private void buildNavBarContent() { 727 if (mShowBottom) { 728 buildBottomBar((mDeviceIsProvisioned) ? R.layout.car_navigation_bar : 729 R.layout.car_navigation_bar_unprovisioned); 730 } 731 732 if (mShowLeft) { 733 buildLeft((mDeviceIsProvisioned) ? R.layout.car_left_navigation_bar : 734 R.layout.car_left_navigation_bar_unprovisioned); 735 } 736 737 if (mShowRight) { 738 buildRight((mDeviceIsProvisioned) ? R.layout.car_right_navigation_bar : 739 R.layout.car_right_navigation_bar_unprovisioned); 740 } 741 } 742 buildNavBarWindows()743 private void buildNavBarWindows() { 744 if (mShowBottom) { 745 mNavigationBarWindow = (ViewGroup) View.inflate(mContext, 746 R.layout.navigation_bar_window, null); 747 } 748 if (mShowLeft) { 749 mLeftNavigationBarWindow = (ViewGroup) View.inflate(mContext, 750 R.layout.navigation_bar_window, null); 751 } 752 if (mShowRight) { 753 mRightNavigationBarWindow = (ViewGroup) View.inflate(mContext, 754 R.layout.navigation_bar_window, null); 755 } 756 757 } 758 attachNavBarWindows()759 private void attachNavBarWindows() { 760 761 if (mShowBottom) { 762 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 763 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 764 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, 765 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 766 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 767 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 768 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 769 PixelFormat.TRANSLUCENT); 770 lp.setTitle("CarNavigationBar"); 771 lp.windowAnimations = 0; 772 mWindowManager.addView(mNavigationBarWindow, lp); 773 } 774 775 if (mShowLeft) { 776 int width = mContext.getResources().getDimensionPixelSize( 777 R.dimen.car_left_navigation_bar_width); 778 WindowManager.LayoutParams leftlp = new WindowManager.LayoutParams( 779 width, LayoutParams.MATCH_PARENT, 780 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, 781 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 782 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 783 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 784 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 785 PixelFormat.TRANSLUCENT); 786 leftlp.setTitle("LeftCarNavigationBar"); 787 leftlp.windowAnimations = 0; 788 leftlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; 789 leftlp.gravity = Gravity.LEFT; 790 mWindowManager.addView(mLeftNavigationBarWindow, leftlp); 791 } 792 if (mShowRight) { 793 int width = mContext.getResources().getDimensionPixelSize( 794 R.dimen.car_right_navigation_bar_width); 795 WindowManager.LayoutParams rightlp = new WindowManager.LayoutParams( 796 width, LayoutParams.MATCH_PARENT, 797 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, 798 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 799 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 800 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 801 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 802 PixelFormat.TRANSLUCENT); 803 rightlp.setTitle("RightCarNavigationBar"); 804 rightlp.windowAnimations = 0; 805 rightlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; 806 rightlp.gravity = Gravity.RIGHT; 807 mWindowManager.addView(mRightNavigationBarWindow, rightlp); 808 } 809 810 } 811 buildBottomBar(int layout)812 private void buildBottomBar(int layout) { 813 // SystemUI requires that the navigation bar view have a parent. Since the regular 814 // StatusBar inflates navigation_bar_window as this parent view, use the same view for the 815 // CarNavigationBarView. 816 View.inflate(mContext, layout, mNavigationBarWindow); 817 mNavigationBarView = (CarNavigationBarView) mNavigationBarWindow.getChildAt(0); 818 if (mNavigationBarView == null) { 819 Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar"); 820 throw new RuntimeException("Unable to build botom nav bar due to missing layout"); 821 } 822 mNavigationBarView.setStatusBar(this); 823 addTemperatureViewToController(mNavigationBarView); 824 mNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener); 825 } 826 buildLeft(int layout)827 private void buildLeft(int layout) { 828 View.inflate(mContext, layout, mLeftNavigationBarWindow); 829 mLeftNavigationBarView = (CarNavigationBarView) mLeftNavigationBarWindow.getChildAt(0); 830 if (mLeftNavigationBarView == null) { 831 Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar"); 832 throw new RuntimeException("Unable to build left nav bar due to missing layout"); 833 } 834 mLeftNavigationBarView.setStatusBar(this); 835 addTemperatureViewToController(mLeftNavigationBarView); 836 mLeftNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener); 837 } 838 839 buildRight(int layout)840 private void buildRight(int layout) { 841 View.inflate(mContext, layout, mRightNavigationBarWindow); 842 mRightNavigationBarView = (CarNavigationBarView) mRightNavigationBarWindow.getChildAt(0); 843 if (mRightNavigationBarView == null) { 844 Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar"); 845 throw new RuntimeException("Unable to build right nav bar due to missing layout"); 846 } 847 mRightNavigationBarView.setStatusBar(this); 848 addTemperatureViewToController(mRightNavigationBarView); 849 mRightNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener); 850 } 851 852 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)853 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 854 //When executing dump() function simultaneously, we need to serialize them 855 //to get mStackScroller's position correctly. 856 synchronized (mQueueLock) { 857 pw.println(" mStackScroller: " + viewInfo(mStackScroller)); 858 pw.println(" mStackScroller: " + viewInfo(mStackScroller) 859 + " scroll " + mStackScroller.getScrollX() 860 + "," + mStackScroller.getScrollY()); 861 } 862 863 pw.print(" mTaskStackListener="); 864 pw.println(mTaskStackListener); 865 pw.print(" mCarFacetButtonController="); 866 pw.println(mCarFacetButtonController); 867 pw.print(" mFullscreenUserSwitcher="); 868 pw.println(mFullscreenUserSwitcher); 869 pw.print(" mCarBatteryController="); 870 pw.println(mCarBatteryController); 871 pw.print(" mBatteryMeterView="); 872 pw.println(mBatteryMeterView); 873 pw.print(" mNavigationBarView="); 874 pw.println(mNavigationBarView); 875 876 if (KeyguardUpdateMonitor.getInstance(mContext) != null) { 877 KeyguardUpdateMonitor.getInstance(mContext).dump(fd, pw, args); 878 } 879 880 FalsingManagerFactory.getInstance(mContext).dump(pw); 881 FalsingLog.dump(pw); 882 883 pw.println("SharedPreferences:"); 884 for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) { 885 pw.print(" "); 886 pw.print(entry.getKey()); 887 pw.print("="); 888 pw.println(entry.getValue()); 889 } 890 } 891 892 @Override showBatteryView()893 public void showBatteryView() { 894 if (Log.isLoggable(TAG, Log.DEBUG)) { 895 Log.d(TAG, "showBatteryView(). mBatteryMeterView: " + mBatteryMeterView); 896 } 897 898 if (mBatteryMeterView != null) { 899 mBatteryMeterView.setVisibility(View.VISIBLE); 900 } 901 } 902 903 @Override hideBatteryView()904 public void hideBatteryView() { 905 if (Log.isLoggable(TAG, Log.DEBUG)) { 906 Log.d(TAG, "hideBatteryView(). mBatteryMeterView: " + mBatteryMeterView); 907 } 908 909 if (mBatteryMeterView != null) { 910 mBatteryMeterView.setVisibility(View.GONE); 911 } 912 } 913 914 /** 915 * An implementation of TaskStackChangeListener, that listens for changes in the system 916 * task stack and notifies the navigation bar. 917 */ 918 private class TaskStackListenerImpl extends TaskStackChangeListener { 919 @Override onTaskStackChanged()920 public void onTaskStackChanged() { 921 try { 922 mCarFacetButtonController.taskChanged( 923 ActivityTaskManager.getService().getAllStackInfos()); 924 } catch (Exception e) { 925 Log.e(TAG, "Getting StackInfo from activity manager failed", e); 926 } 927 } 928 929 @Override onTaskDisplayChanged(int taskId, int newDisplayId)930 public void onTaskDisplayChanged(int taskId, int newDisplayId) { 931 try { 932 mCarFacetButtonController.taskChanged( 933 ActivityTaskManager.getService().getAllStackInfos()); 934 } catch (Exception e) { 935 Log.e(TAG, "Getting StackInfo from activity manager failed", e); 936 } 937 } 938 } 939 onDrivingStateChanged(CarDrivingStateEvent notUsed)940 private void onDrivingStateChanged(CarDrivingStateEvent notUsed) { 941 // Check if we need to start the timer every time driving state changes. 942 startSwitchToGuestTimerIfDrivingOnKeyguard(); 943 } 944 startSwitchToGuestTimerIfDrivingOnKeyguard()945 private void startSwitchToGuestTimerIfDrivingOnKeyguard() { 946 if (mDrivingStateHelper.isCurrentlyDriving() && mState != StatusBarState.SHADE) { 947 // We're driving while keyguard is up. 948 mSwitchToGuestTimer.start(); 949 } else { 950 mSwitchToGuestTimer.cancel(); 951 } 952 } 953 954 @Override createUserSwitcher()955 protected void createUserSwitcher() { 956 UserSwitcherController userSwitcherController = 957 Dependency.get(UserSwitcherController.class); 958 if (userSwitcherController.useFullscreenUserSwitcher()) { 959 mFullscreenUserSwitcher = new FullscreenUserSwitcher(this, 960 mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub), mContext); 961 } else { 962 super.createUserSwitcher(); 963 } 964 } 965 966 @Override setLockscreenUser(int newUserId)967 public void setLockscreenUser(int newUserId) { 968 super.setLockscreenUser(newUserId); 969 // Try to dismiss the keyguard after every user switch. 970 dismissKeyguardWhenUserSwitcherNotDisplayed(); 971 } 972 973 @Override onStateChanged(int newState)974 public void onStateChanged(int newState) { 975 super.onStateChanged(newState); 976 977 startSwitchToGuestTimerIfDrivingOnKeyguard(); 978 979 if (newState != StatusBarState.FULLSCREEN_USER_SWITCHER) { 980 hideUserSwitcher(); 981 } else { 982 dismissKeyguardWhenUserSwitcherNotDisplayed(); 983 } 984 } 985 986 /** Makes the full screen user switcher visible, if applicable. */ showUserSwitcher()987 public void showUserSwitcher() { 988 if (mFullscreenUserSwitcher != null && mState == StatusBarState.FULLSCREEN_USER_SWITCHER) { 989 mFullscreenUserSwitcher.show(); // Makes the switcher visible. 990 } 991 } 992 hideUserSwitcher()993 private void hideUserSwitcher() { 994 if (mFullscreenUserSwitcher != null) { 995 mFullscreenUserSwitcher.hide(); 996 } 997 } 998 999 final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { 1000 @Override 1001 public void onScreenTurnedOn() { 1002 dismissKeyguardWhenUserSwitcherNotDisplayed(); 1003 } 1004 }; 1005 1006 // We automatically dismiss keyguard unless user switcher is being shown on the keyguard. dismissKeyguardWhenUserSwitcherNotDisplayed()1007 private void dismissKeyguardWhenUserSwitcherNotDisplayed() { 1008 if (mFullscreenUserSwitcher == null) { 1009 return; // Not using the full screen user switcher. 1010 } 1011 1012 if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER 1013 && !mFullscreenUserSwitcher.isVisible()) { 1014 // Current execution path continues to set state after this, thus we deffer the 1015 // dismissal to the next execution cycle. 1016 postDismissKeyguard(); // Dismiss the keyguard if switcher is not visible. 1017 } 1018 } 1019 postDismissKeyguard()1020 public void postDismissKeyguard() { 1021 mHandler.post(this::dismissKeyguard); 1022 } 1023 1024 /** 1025 * Dismisses the keyguard and shows bouncer if authentication is necessary. 1026 */ dismissKeyguard()1027 public void dismissKeyguard() { 1028 // Don't dismiss keyguard when the screen is off. 1029 if (mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_OFF) { 1030 return; 1031 } 1032 executeRunnableDismissingKeyguard(null/* runnable */, null /* cancelAction */, 1033 true /* dismissShade */, true /* afterKeyguardGone */, true /* deferred */); 1034 } 1035 1036 /** 1037 * Ensures that relevant child views are appropriately recreated when the device's density 1038 * changes. 1039 */ 1040 @Override onDensityOrFontScaleChanged()1041 public void onDensityOrFontScaleChanged() { 1042 super.onDensityOrFontScaleChanged(); 1043 restartNavBars(); 1044 // Need to update the background on density changed in case the change was due to night 1045 // mode. 1046 mNotificationPanelBackground = getDefaultWallpaper(); 1047 mScrimController.setScrimBehindDrawable(mNotificationPanelBackground); 1048 } 1049 1050 /** 1051 * Returns the {@link Drawable} that represents the wallpaper that the user has currently set. 1052 */ getDefaultWallpaper()1053 private Drawable getDefaultWallpaper() { 1054 return mContext.getDrawable(com.android.internal.R.drawable.default_wallpaper); 1055 } 1056 setNotificationViewClipBounds(int height)1057 private void setNotificationViewClipBounds(int height) { 1058 if (height > mNotificationView.getHeight()) { 1059 height = mNotificationView.getHeight(); 1060 } 1061 Rect clipBounds = new Rect(); 1062 clipBounds.set(0, 0, mNotificationView.getWidth(), height); 1063 // Sets the clip region on the notification list view. 1064 mNotificationView.setClipBounds(clipBounds); 1065 if (mHandleBar != null) { 1066 ViewGroup.MarginLayoutParams lp = 1067 (ViewGroup.MarginLayoutParams) mHandleBar.getLayoutParams(); 1068 mHandleBar.setTranslationY(height - mHandleBar.getHeight() - lp.bottomMargin); 1069 } 1070 if (mNotificationView.getHeight() > 0) { 1071 // Calculates the alpha value for the background based on how much of the notification 1072 // shade is visible to the user. When the notification shade is completely open then 1073 // alpha value will be 1. 1074 float alpha = (float) height / mNotificationView.getHeight(); 1075 Drawable background = mNotificationView.getBackground(); 1076 1077 background.setAlpha((int) (alpha * 255)); 1078 } 1079 } 1080 calculatePercentageFromBottom(float height)1081 private void calculatePercentageFromBottom(float height) { 1082 if (mNotificationView.getHeight() > 0) { 1083 mPercentageFromBottom = (int) Math.abs( 1084 height / mNotificationView.getHeight() * 100); 1085 } 1086 } 1087 1088 private static final int SWIPE_DOWN_MIN_DISTANCE = 25; 1089 private static final int SWIPE_MAX_OFF_PATH = 75; 1090 private static final int SWIPE_THRESHOLD_VELOCITY = 200; 1091 1092 /** 1093 * Only responsible for open hooks. Since once the panel opens it covers all elements 1094 * there is no need to merge with close. 1095 */ 1096 private abstract class OpenNotificationGestureListener extends 1097 GestureDetector.SimpleOnGestureListener { 1098 1099 @Override onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY)1100 public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, 1101 float distanceY) { 1102 1103 if (mNotificationView.getVisibility() == View.INVISIBLE) { 1104 // when the on-scroll is called for the first time to open. 1105 mNotificationList.scrollToPosition(0); 1106 } 1107 mStatusBarWindowController.setPanelVisible(true); 1108 mNotificationView.setVisibility(View.VISIBLE); 1109 1110 // clips the view for the notification shade when the user scrolls to open. 1111 setNotificationViewClipBounds((int) event2.getRawY()); 1112 1113 // Initially the scroll starts with height being zero. This checks protects from divide 1114 // by zero error. 1115 calculatePercentageFromBottom(event2.getRawY()); 1116 1117 mIsTracking = true; 1118 return true; 1119 } 1120 1121 1122 @Override onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY)1123 public boolean onFling(MotionEvent event1, MotionEvent event2, 1124 float velocityX, float velocityY) { 1125 if (velocityY > SWIPE_THRESHOLD_VELOCITY) { 1126 mOpeningVelocity = velocityY; 1127 openNotification(); 1128 return true; 1129 } 1130 animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); 1131 1132 return false; 1133 } 1134 openNotification()1135 protected abstract void openNotification(); 1136 } 1137 1138 /** 1139 * To be installed on the open panel notification panel 1140 */ 1141 private abstract class CloseNotificationGestureListener extends 1142 GestureDetector.SimpleOnGestureListener { 1143 1144 @Override onSingleTapUp(MotionEvent motionEvent)1145 public boolean onSingleTapUp(MotionEvent motionEvent) { 1146 if (mPanelExpanded) { 1147 animateNotificationPanel(DEFAULT_FLING_VELOCITY, true); 1148 } 1149 return true; 1150 } 1151 1152 @Override onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY)1153 public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, 1154 float distanceY) { 1155 // should not clip while scroll to the bottom of the list. 1156 if (!mNotificationListAtBottomAtTimeOfTouch) { 1157 return false; 1158 } 1159 float actualNotificationHeight = 1160 mNotificationView.getHeight() - (event1.getRawY() - event2.getRawY()); 1161 if (actualNotificationHeight > mNotificationView.getHeight()) { 1162 actualNotificationHeight = mNotificationView.getHeight(); 1163 } 1164 if (mNotificationView.getHeight() > 0) { 1165 mPercentageFromBottom = (int) Math.abs( 1166 actualNotificationHeight / mNotificationView.getHeight() * 100); 1167 boolean isUp = distanceY > 0; 1168 1169 // This check is to figure out if onScroll was called while swiping the card at 1170 // bottom of the list. At that time we should not allow notification shade to 1171 // close. We are also checking for the upwards swipe gesture here because it is 1172 // possible if a user is closing the notification shade and while swiping starts 1173 // to open again but does not fling. At that time we should allow the 1174 // notification shade to close fully or else it would stuck in between. 1175 if (Math.abs(mNotificationView.getHeight() - actualNotificationHeight) 1176 > SWIPE_DOWN_MIN_DISTANCE && isUp) { 1177 setNotificationViewClipBounds((int) actualNotificationHeight); 1178 mIsTracking = true; 1179 } else if (!isUp) { 1180 setNotificationViewClipBounds((int) actualNotificationHeight); 1181 } 1182 } 1183 // if we return true the the items in RV won't be scrollable. 1184 return false; 1185 } 1186 1187 1188 @Override onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY)1189 public boolean onFling(MotionEvent event1, MotionEvent event2, 1190 float velocityX, float velocityY) { 1191 // should not fling if the touch does not start when view is at the bottom of the list. 1192 if (!mNotificationListAtBottomAtTimeOfTouch) { 1193 return false; 1194 } 1195 if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH 1196 || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) { 1197 // swipe was not vertical or was not fast enough 1198 return false; 1199 } 1200 boolean isUp = velocityY < 0; 1201 if (isUp) { 1202 close(); 1203 return true; 1204 } else { 1205 // we should close the shade 1206 animateNotificationPanel(velocityY, false); 1207 } 1208 return false; 1209 } 1210 1211 protected abstract void close(); 1212 } 1213 1214 /** 1215 * To be installed on the nav bars. 1216 */ 1217 private abstract class NavBarCloseNotificationGestureListener extends 1218 CloseNotificationGestureListener { 1219 @Override 1220 public boolean onSingleTapUp(MotionEvent e) { 1221 mClosingVelocity = DEFAULT_FLING_VELOCITY; 1222 if (mPanelExpanded) { 1223 close(); 1224 } 1225 return super.onSingleTapUp(e); 1226 } 1227 1228 @Override 1229 public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, 1230 float distanceY) { 1231 calculatePercentageFromBottom(event2.getRawY()); 1232 setNotificationViewClipBounds((int) event2.getRawY()); 1233 return true; 1234 } 1235 1236 @Override 1237 public void onLongPress(MotionEvent e) { 1238 mClosingVelocity = DEFAULT_FLING_VELOCITY; 1239 close(); 1240 super.onLongPress(e); 1241 } 1242 } 1243 1244 /** 1245 * To be installed on the handle bar. 1246 */ 1247 private class HandleBarCloseNotificationGestureListener extends 1248 GestureDetector.SimpleOnGestureListener { 1249 1250 @Override 1251 public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, 1252 float distanceY) { 1253 calculatePercentageFromBottom(event2.getRawY()); 1254 // To prevent the jump in the clip bounds while closing the notification shade using 1255 // the handle bar we should calculate the height using the diff of event1 and event2. 1256 // This will help the notification shade to clip smoothly as the event2 value changes 1257 // as event1 value will be fixed. 1258 int clipHeight = 1259 mNotificationView.getHeight() - (int) (event1.getRawY() - event2.getRawY()); 1260 setNotificationViewClipBounds(clipHeight); 1261 return true; 1262 } 1263 } 1264 1265 /** 1266 * SystemUi version of the notification manager that overrides methods such that the 1267 * notifications end up in the status bar layouts instead of a standalone window. 1268 */ 1269 private class CarSystemUIHeadsUpNotificationManager extends CarHeadsUpNotificationManager { 1270 1271 CarSystemUIHeadsUpNotificationManager(Context context, 1272 NotificationClickHandlerFactory clickHandlerFactory, 1273 NotificationDataManager notificationDataManager) { 1274 super(context, clickHandlerFactory, notificationDataManager); 1275 } 1276 1277 @Override 1278 protected View createHeadsUpPanel() { 1279 // In SystemUi the view is already in the window so just return a reference. 1280 return mStatusBarWindow.findViewById(R.id.notification_headsup); 1281 } 1282 1283 @Override 1284 protected void addHeadsUpPanelToDisplay() { 1285 // Set the panel initial state to invisible 1286 mHeadsUpPanel.setVisibility(View.INVISIBLE); 1287 } 1288 1289 @Override 1290 protected void setInternalInsetsInfo(ViewTreeObserver.InternalInsetsInfo info, 1291 HeadsUpEntry currentNotification, boolean panelExpanded) { 1292 super.setInternalInsetsInfo(info, currentNotification, mPanelExpanded); 1293 } 1294 1295 @Override 1296 protected void setHeadsUpVisible() { 1297 // if the Notifications panel is showing don't show the Heads up 1298 if (!mEnableHeadsUpNotificationWhenNotificationShadeOpen && mPanelExpanded) { 1299 return; 1300 } 1301 1302 super.setHeadsUpVisible(); 1303 if (mHeadsUpPanel.getVisibility() == View.VISIBLE) { 1304 mStatusBarWindowController.setHeadsUpShowing(true); 1305 mStatusBarWindowController.setForceStatusBarVisible(true); 1306 } 1307 } 1308 1309 @Override 1310 protected void removeNotificationFromPanel(HeadsUpEntry currentHeadsUpNotification) { 1311 super.removeNotificationFromPanel(currentHeadsUpNotification); 1312 // If the panel ended up empty and hidden we can remove it from SystemUi 1313 if (mHeadsUpPanel.getVisibility() != View.VISIBLE) { 1314 mStatusBarWindowController.setHeadsUpShowing(false); 1315 mStatusBarWindowController.setForceStatusBarVisible(false); 1316 } 1317 } 1318 } 1319 } 1320