1 /* 2 * Copyright (C) 2020 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.car.notification; 18 19 import android.app.ActivityManager; 20 import android.car.Car; 21 import android.car.drivingstate.CarUxRestrictionsManager; 22 import android.content.Context; 23 import android.content.res.Resources; 24 import android.graphics.Rect; 25 import android.graphics.drawable.Drawable; 26 import android.inputmethodservice.InputMethodService; 27 import android.os.RemoteException; 28 import android.util.Log; 29 import android.view.GestureDetector; 30 import android.view.KeyEvent; 31 import android.view.LayoutInflater; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.view.WindowInsets; 35 36 import androidx.annotation.NonNull; 37 import androidx.recyclerview.widget.RecyclerView; 38 39 import com.android.car.notification.CarNotificationListener; 40 import com.android.car.notification.CarNotificationView; 41 import com.android.car.notification.CarUxRestrictionManagerWrapper; 42 import com.android.car.notification.NotificationClickHandlerFactory; 43 import com.android.car.notification.NotificationClickHandlerFactory.OnNotificationClickListener; 44 import com.android.car.notification.NotificationDataManager; 45 import com.android.car.notification.NotificationViewController; 46 import com.android.car.notification.PreprocessingManager; 47 import com.android.internal.statusbar.IStatusBarService; 48 import com.android.systemui.R; 49 import com.android.systemui.car.CarDeviceProvisionedController; 50 import com.android.systemui.car.CarServiceProvider; 51 import com.android.systemui.car.CarServiceProvider.CarServiceOnConnectedListener; 52 import com.android.systemui.car.users.CarSystemUIUserUtil; 53 import com.android.systemui.car.window.OverlayPanelViewController; 54 import com.android.systemui.car.window.OverlayViewController; 55 import com.android.systemui.car.window.OverlayViewGlobalStateController; 56 import com.android.systemui.dagger.SysUISingleton; 57 import com.android.systemui.dagger.qualifiers.Main; 58 import com.android.systemui.dagger.qualifiers.UiBackground; 59 import com.android.systemui.plugins.statusbar.StatusBarStateController; 60 import com.android.systemui.statusbar.CommandQueue; 61 import com.android.systemui.statusbar.StatusBarState; 62 import com.android.wm.shell.animation.FlingAnimationUtils; 63 64 import java.util.concurrent.Executor; 65 66 import javax.inject.Inject; 67 68 /** View controller for the notification panel. */ 69 @SysUISingleton 70 public class NotificationPanelViewController extends OverlayPanelViewController 71 implements CommandQueue.Callbacks { 72 73 private static final boolean DEBUG = true; 74 private static final String TAG = "NotificationPanelViewController"; 75 76 private final Context mContext; 77 private final Resources mResources; 78 private final CarServiceProvider mCarServiceProvider; 79 private final IStatusBarService mBarService; 80 private final CommandQueue mCommandQueue; 81 private final Executor mUiBgExecutor; 82 private final NotificationDataManager mNotificationDataManager; 83 private final CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper; 84 private final CarNotificationListener mCarNotificationListener; 85 private final NotificationClickHandlerFactory mNotificationClickHandlerFactory; 86 private final StatusBarStateController mStatusBarStateController; 87 private final boolean mEnableHeadsUpNotificationWhenNotificationPanelOpen; 88 private final NotificationVisibilityLogger mNotificationVisibilityLogger; 89 90 private final boolean mFitTopSystemBarInset; 91 private final boolean mFitBottomSystemBarInset; 92 private final boolean mFitLeftSystemBarInset; 93 private final boolean mFitRightSystemBarInset; 94 95 private float mInitialBackgroundAlpha; 96 private float mBackgroundAlphaDiff; 97 98 private CarNotificationView mNotificationView; 99 private RecyclerView mNotificationList; 100 private NotificationViewController mNotificationViewController; 101 102 private boolean mNotificationListAtEnd; 103 private float mFirstTouchDownOnGlassPane; 104 private boolean mNotificationListAtEndAtTimeOfTouch; 105 private boolean mIsSwipingVerticallyToClose; 106 private boolean mIsNotificationCardSwiping; 107 private boolean mImeVisible = false; 108 private boolean mOnConnectListenerAdded; 109 110 private OnUnseenCountUpdateListener mUnseenCountUpdateListener; 111 private OnNotificationClickListener mOnNotificationClickListener = 112 (launchResult, alertEntry) -> { 113 if (launchResult == ActivityManager.START_TASK_TO_FRONT 114 || launchResult == ActivityManager.START_SUCCESS 115 || launchResult == ActivityManager.START_DELIVERED_TO_TOP) { 116 animateCollapsePanel(); 117 } 118 }; 119 120 private CarServiceOnConnectedListener mCarConnectedListener = 121 new CarServiceOnConnectedListener() { 122 @Override 123 public void onConnected(Car car) { 124 CarUxRestrictionsManager carUxRestrictionsManager = 125 (CarUxRestrictionsManager) 126 car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE); 127 mCarUxRestrictionManagerWrapper.setCarUxRestrictionsManager( 128 carUxRestrictionsManager); 129 130 PreprocessingManager preprocessingManager = 131 PreprocessingManager.getInstance(mContext); 132 preprocessingManager.setCarUxRestrictionManagerWrapper( 133 mCarUxRestrictionManagerWrapper); 134 135 mNotificationViewController.enable(); 136 } 137 }; 138 139 @Inject NotificationPanelViewController( Context context, @Main Resources resources, OverlayViewGlobalStateController overlayViewGlobalStateController, FlingAnimationUtils.Builder flingAnimationUtilsBuilder, @UiBackground Executor uiBgExecutor, CarServiceProvider carServiceProvider, CarDeviceProvisionedController carDeviceProvisionedController, IStatusBarService barService, CommandQueue commandQueue, NotificationDataManager notificationDataManager, CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper, CarNotificationListener carNotificationListener, NotificationClickHandlerFactory notificationClickHandlerFactory, NotificationVisibilityLogger notificationVisibilityLogger, StatusBarStateController statusBarStateController )140 public NotificationPanelViewController( 141 Context context, 142 @Main Resources resources, 143 OverlayViewGlobalStateController overlayViewGlobalStateController, 144 FlingAnimationUtils.Builder flingAnimationUtilsBuilder, 145 @UiBackground Executor uiBgExecutor, 146 147 /* Other things */ 148 CarServiceProvider carServiceProvider, 149 CarDeviceProvisionedController carDeviceProvisionedController, 150 151 /* Things needed for notifications */ 152 IStatusBarService barService, 153 CommandQueue commandQueue, 154 NotificationDataManager notificationDataManager, 155 CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper, 156 CarNotificationListener carNotificationListener, 157 NotificationClickHandlerFactory notificationClickHandlerFactory, 158 NotificationVisibilityLogger notificationVisibilityLogger, 159 160 /* Things that need to be replaced */ 161 StatusBarStateController statusBarStateController 162 ) { 163 super(context, resources, R.id.notification_panel_stub, overlayViewGlobalStateController, 164 flingAnimationUtilsBuilder, carDeviceProvisionedController); 165 mContext = context; 166 mResources = resources; 167 mCarServiceProvider = carServiceProvider; 168 mBarService = barService; 169 mCommandQueue = commandQueue; 170 mUiBgExecutor = uiBgExecutor; 171 mNotificationDataManager = notificationDataManager; 172 mCarUxRestrictionManagerWrapper = carUxRestrictionManagerWrapper; 173 mCarNotificationListener = carNotificationListener; 174 mNotificationClickHandlerFactory = notificationClickHandlerFactory; 175 mStatusBarStateController = statusBarStateController; 176 mNotificationVisibilityLogger = notificationVisibilityLogger; 177 178 mCommandQueue.addCallback(this); 179 180 // Notification background setup. 181 mInitialBackgroundAlpha = (float) mResources.getInteger( 182 R.integer.config_initialNotificationBackgroundAlpha) / 100; 183 if (mInitialBackgroundAlpha < 0 || mInitialBackgroundAlpha > 100) { 184 throw new RuntimeException( 185 "Unable to setup notification bar due to incorrect initial background alpha" 186 + " percentage"); 187 } 188 float finalBackgroundAlpha = Math.max( 189 mInitialBackgroundAlpha, 190 (float) mResources.getInteger( 191 R.integer.config_finalNotificationBackgroundAlpha) / 100); 192 if (finalBackgroundAlpha < 0 || finalBackgroundAlpha > 100) { 193 throw new RuntimeException( 194 "Unable to setup notification bar due to incorrect final background alpha" 195 + " percentage"); 196 } 197 mBackgroundAlphaDiff = finalBackgroundAlpha - mInitialBackgroundAlpha; 198 199 mEnableHeadsUpNotificationWhenNotificationPanelOpen = mResources.getBoolean( 200 com.android.car.notification.R.bool 201 .config_enableHeadsUpNotificationWhenNotificationPanelOpen); 202 203 mFitTopSystemBarInset = mResources.getBoolean( 204 R.bool.config_notif_panel_inset_by_top_systembar); 205 mFitBottomSystemBarInset = mResources.getBoolean( 206 R.bool.config_notif_panel_inset_by_bottom_systembar); 207 mFitLeftSystemBarInset = mResources.getBoolean( 208 R.bool.config_notif_panel_inset_by_left_systembar); 209 mFitRightSystemBarInset = mResources.getBoolean( 210 R.bool.config_notif_panel_inset_by_right_systembar); 211 212 // Inflate view on instantiation to properly initialize listeners even if panel has 213 // not been opened. 214 getOverlayViewGlobalStateController().inflateView(this); 215 } 216 217 // CommandQueue.Callbacks 218 219 @Override animateExpandNotificationsPanel()220 public void animateExpandNotificationsPanel() { 221 if (!isPanelExpanded()) { 222 toggle(); 223 } 224 } 225 226 @Override animateCollapsePanels(int flags, boolean force)227 public void animateCollapsePanels(int flags, boolean force) { 228 if (isPanelExpanded()) { 229 toggle(); 230 } 231 } 232 233 @Override setImeWindowStatus(int displayId, int vis, int backDisposition, boolean showImeSwitcher)234 public void setImeWindowStatus(int displayId, int vis, int backDisposition, 235 boolean showImeSwitcher) { 236 if (mContext.getDisplayId() != displayId) { 237 return; 238 } 239 mImeVisible = (vis & InputMethodService.IME_VISIBLE) != 0; 240 } 241 242 // OverlayViewController 243 244 @Override shouldPanelConsumeSystemBarTouch()245 public boolean shouldPanelConsumeSystemBarTouch() { 246 return true; 247 } 248 249 @Override onFinishInflate()250 protected void onFinishInflate() { 251 reinflate(); 252 } 253 254 @Override hideInternal()255 protected void hideInternal() { 256 super.hideInternal(); 257 mNotificationVisibilityLogger.stop(); 258 } 259 260 @Override getFocusAreaViewId()261 protected int getFocusAreaViewId() { 262 return R.id.notification_container; 263 } 264 265 @Override shouldShowNavigationBarInsets()266 protected boolean shouldShowNavigationBarInsets() { 267 return true; 268 } 269 270 @Override shouldShowStatusBarInsets()271 protected boolean shouldShowStatusBarInsets() { 272 return true; 273 } 274 275 @Override getInsetSidesToFit()276 protected int getInsetSidesToFit() { 277 int insetSidesToFit = OverlayViewController.NO_INSET_SIDE; 278 279 if (mFitTopSystemBarInset) { 280 insetSidesToFit = insetSidesToFit | WindowInsets.Side.TOP; 281 } 282 283 if (mFitBottomSystemBarInset) { 284 insetSidesToFit = insetSidesToFit | WindowInsets.Side.BOTTOM; 285 } 286 287 if (mFitLeftSystemBarInset) { 288 insetSidesToFit = insetSidesToFit | WindowInsets.Side.LEFT; 289 } 290 291 if (mFitRightSystemBarInset) { 292 insetSidesToFit = insetSidesToFit | WindowInsets.Side.RIGHT; 293 } 294 295 return insetSidesToFit; 296 } 297 298 @Override shouldShowHUN()299 protected boolean shouldShowHUN() { 300 return mEnableHeadsUpNotificationWhenNotificationPanelOpen; 301 } 302 303 @Override shouldUseStableInsets()304 protected boolean shouldUseStableInsets() { 305 // When IME is visible, then the inset from the nav bar should not be applied. 306 return !mImeVisible; 307 } 308 309 /** Reinflates the view. */ reinflate()310 public void reinflate() { 311 // Do not reinflate the view if it has not been inflated at all. 312 if (!isInflated()) return; 313 314 mNotificationClickHandlerFactory.unregisterClickListener(mOnNotificationClickListener); 315 316 if (mOnConnectListenerAdded) { 317 mCarServiceProvider.removeListener(mCarConnectedListener); 318 mOnConnectListenerAdded = false; 319 } 320 321 ViewGroup container = (ViewGroup) getLayout(); 322 container.removeView(mNotificationView); 323 324 mNotificationView = (CarNotificationView) LayoutInflater.from(mContext).inflate( 325 R.layout.notification_center_activity, container, 326 /* attachToRoot= */ false); 327 mNotificationView.setKeyEventHandler( 328 event -> { 329 if (event.getKeyCode() != KeyEvent.KEYCODE_BACK) { 330 return false; 331 } 332 333 if (event.getAction() == KeyEvent.ACTION_UP && isPanelExpanded()) { 334 toggle(); 335 } 336 return true; 337 }); 338 339 container.addView(mNotificationView); 340 onNotificationViewInflated(); 341 } 342 onNotificationViewInflated()343 private void onNotificationViewInflated() { 344 // Find views. 345 mNotificationView = getLayout().findViewById(R.id.notification_view); 346 setUpHandleBar(); 347 setupNotificationPanel(); 348 349 mNotificationClickHandlerFactory.registerClickListener(mOnNotificationClickListener); 350 351 mNotificationDataManager.setOnUnseenCountUpdateListener(() -> { 352 if (mUnseenCountUpdateListener != null) { 353 // Don't show unseen markers for <= LOW importance notifications to be consistent 354 // with how these notifications are handled on phones 355 int unseenCount = 356 mNotificationDataManager.getNonLowImportanceUnseenNotificationCount( 357 mCarNotificationListener.getCurrentRanking()); 358 mUnseenCountUpdateListener.onUnseenCountUpdate(unseenCount); 359 } 360 if (isPanelExpanded()) { 361 // only report the seen notifications when the panel is expanded 362 mCarNotificationListener.setNotificationsShown( 363 mNotificationDataManager.getSeenNotifications()); 364 } 365 // This logs both when the notification panel is expanded and when the notification 366 // panel is scrolled. 367 mNotificationVisibilityLogger.log(isPanelExpanded()); 368 }); 369 370 mNotificationView.setClickHandlerFactory(mNotificationClickHandlerFactory); 371 mNotificationViewController = new NotificationViewController( 372 mNotificationView, 373 PreprocessingManager.getInstance(mContext), 374 mCarNotificationListener, 375 mCarUxRestrictionManagerWrapper); 376 377 if (!mOnConnectListenerAdded) { 378 mCarServiceProvider.addListener(mCarConnectedListener); 379 mOnConnectListenerAdded = true; 380 } 381 } 382 setupNotificationPanel()383 private void setupNotificationPanel() { 384 View glassPane = mNotificationView.findViewById(R.id.glass_pane); 385 mNotificationList = mNotificationView.findViewById(R.id.notifications); 386 GestureDetector closeGestureDetector = new GestureDetector(mContext, 387 new CloseGestureListener() { 388 @Override 389 protected void close() { 390 if (isPanelExpanded()) { 391 animateCollapsePanel(); 392 } 393 } 394 }); 395 396 // The glass pane is used to view touch events before passed to the notification list. 397 // This allows us to initialize gesture listeners and detect when to close the notifications 398 glassPane.setOnTouchListener((v, event) -> { 399 if (isClosingAction(event)) { 400 mNotificationListAtEndAtTimeOfTouch = false; 401 } 402 if (isOpeningAction(event)) { 403 mFirstTouchDownOnGlassPane = event.getRawX(); 404 mNotificationListAtEndAtTimeOfTouch = mNotificationListAtEnd; 405 // Reset the tracker when there is a touch down on the glass pane. 406 setIsTracking(false); 407 // Pass the down event to gesture detector so that it knows where the touch event 408 // started. 409 closeGestureDetector.onTouchEvent(event); 410 } 411 return false; 412 }); 413 414 mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() { 415 @Override 416 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { 417 super.onScrolled(recyclerView, dx, dy); 418 // Check if we can scroll vertically in the animation direction. 419 if (!mNotificationList.canScrollVertically(mAnimateDirection)) { 420 mNotificationListAtEnd = true; 421 return; 422 } 423 mNotificationListAtEnd = false; 424 mIsSwipingVerticallyToClose = false; 425 mNotificationListAtEndAtTimeOfTouch = false; 426 } 427 }); 428 429 mNotificationList.setOnTouchListener((v, event) -> { 430 mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX()) 431 > SWIPE_MAX_OFF_PATH; 432 if (mNotificationListAtEndAtTimeOfTouch && mNotificationListAtEnd) { 433 // We need to save the state here as if notification card is swiping we will 434 // change the mNotificationListAtEndAtTimeOfTouch. This is to protect 435 // closing the notification shade while the notification card is being swiped. 436 mIsSwipingVerticallyToClose = true; 437 } 438 439 // If the card is swiping we should not allow the notification shade to close. 440 // Hence setting mNotificationListAtEndAtTimeOfTouch to false will stop that 441 // for us. We are also checking for isTracking() because while swiping the 442 // notification shade to close if the user goes a bit horizontal while swiping 443 // upwards then also this should close. 444 if (mIsNotificationCardSwiping && !isTracking()) { 445 mNotificationListAtEndAtTimeOfTouch = false; 446 } 447 448 boolean handled = closeGestureDetector.onTouchEvent(event); 449 boolean isTracking = isTracking(); 450 Rect rect = getLayout().getClipBounds(); 451 float clippedHeight = 0; 452 if (rect != null) { 453 clippedHeight = rect.bottom; 454 } 455 if (!handled && isClosingAction(event) && mIsSwipingVerticallyToClose) { 456 if (getSettleClosePercentage() < getPercentageFromEndingEdge() && isTracking) { 457 animatePanel(DEFAULT_FLING_VELOCITY, false); 458 } else if (clippedHeight != getLayout().getHeight() && isTracking) { 459 // this can be caused when user is at the end of the list and trying to 460 // fling to top of the list by scrolling down. 461 animatePanel(DEFAULT_FLING_VELOCITY, true); 462 } 463 } 464 465 // Updating the mNotificationListAtEndAtTimeOfTouch state has to be done after 466 // the event has been passed to the closeGestureDetector above, such that the 467 // closeGestureDetector sees the up event before the state has changed. 468 if (isClosingAction(event)) { 469 mNotificationListAtEndAtTimeOfTouch = false; 470 } 471 return handled || isTracking; 472 }); 473 } 474 475 /** Called when the car power state is changed to ON. */ onCarPowerStateOn()476 public void onCarPowerStateOn() { 477 if (mNotificationClickHandlerFactory != null) { 478 mNotificationClickHandlerFactory.clearAllNotifications(mContext); 479 } 480 mNotificationDataManager.clearAll(); 481 } 482 483 /** 484 * Forwards the call to clear all Notification cache. 485 * Note: This is a blocking call so should not execute any long-running or time-consuming tasks 486 * like storing cache. 487 */ clearCache()488 public void clearCache() { 489 mCarNotificationListener.clearCache(); 490 } 491 492 // OverlayPanelViewController 493 494 @Override shouldAnimateCollapsePanel()495 protected boolean shouldAnimateCollapsePanel() { 496 return true; 497 } 498 499 @Override onAnimateCollapsePanel()500 protected void onAnimateCollapsePanel() { 501 // no-op 502 } 503 504 @Override shouldAnimateExpandPanel()505 protected boolean shouldAnimateExpandPanel() { 506 return mCommandQueue.panelsEnabled(); 507 } 508 509 @Override onAnimateExpandPanel()510 protected void onAnimateExpandPanel() { 511 mNotificationList.scrollToPosition(0); 512 } 513 514 @Override getSettleClosePercentage()515 protected int getSettleClosePercentage() { 516 return mResources.getInteger(R.integer.notification_settle_close_percentage); 517 } 518 519 @Override onCollapseAnimationEnd()520 protected void onCollapseAnimationEnd() { 521 mNotificationViewController.onVisibilityChanged(false); 522 } 523 524 @Override onExpandAnimationEnd()525 protected void onExpandAnimationEnd() { 526 mNotificationView.setVisibleNotificationsAsSeen(); 527 mNotificationViewController.onVisibilityChanged(true); 528 } 529 530 @Override onPanelVisible(boolean visible)531 protected void onPanelVisible(boolean visible) { 532 super.onPanelVisible(visible); 533 if (CarSystemUIUserUtil.isSecondaryMUMDSystemUI()) { 534 // TODO: b/341604160 - Supports visible background users properly. 535 Log.d(TAG, "Status bar manager is disabled for visible background users"); 536 return; 537 } 538 539 mUiBgExecutor.execute(() -> { 540 try { 541 if (visible) { 542 // When notification panel is open even just a bit, we want to clear 543 // notification effects. 544 boolean clearNotificationEffects = 545 mStatusBarStateController.getState() != StatusBarState.KEYGUARD; 546 mBarService.onPanelRevealed(clearNotificationEffects, 547 mNotificationDataManager.getVisibleNotifications().size()); 548 } else { 549 mBarService.onPanelHidden(); 550 } 551 } catch (RemoteException ex) { 552 // Won't fail unless the world has ended. 553 Log.e(TAG, String.format( 554 "Unable to notify StatusBarService of panel visibility: %s", visible)); 555 } 556 }); 557 558 } 559 560 @Override onPanelExpanded(boolean expand)561 protected void onPanelExpanded(boolean expand) { 562 super.onPanelExpanded(expand); 563 564 if (expand && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) { 565 if (DEBUG) { 566 Log.v(TAG, "clearing notification effects from setExpandedHeight"); 567 } 568 clearNotificationEffects(); 569 } 570 if (!expand) { 571 mNotificationVisibilityLogger.log(isPanelExpanded()); 572 } 573 } 574 575 /** 576 * Clear Buzz/Beep/Blink. 577 */ clearNotificationEffects()578 private void clearNotificationEffects() { 579 if (CarSystemUIUserUtil.isSecondaryMUMDSystemUI()) { 580 // TODO: b/341604160 - Supports visible background users properly. 581 Log.d(TAG, "Status bar manager is disabled for visible background users"); 582 return; 583 } 584 585 try { 586 mBarService.clearNotificationEffects(); 587 } catch (RemoteException e) { 588 // Won't fail unless the world has ended. 589 } 590 } 591 592 @Override onOpenScrollStart()593 protected void onOpenScrollStart() { 594 mNotificationList.scrollToPosition(0); 595 } 596 597 @Override onScroll(int y)598 protected void onScroll(int y) { 599 super.onScroll(y); 600 601 if (mNotificationView.getHeight() > 0) { 602 Drawable background = mNotificationView.getBackground().mutate(); 603 background.setAlpha((int) (getBackgroundAlpha(y) * 255)); 604 mNotificationView.setBackground(background); 605 } 606 } 607 608 @Override shouldAllowClosingScroll()609 protected boolean shouldAllowClosingScroll() { 610 // Unless the notification list is at the end, the panel shouldn't be allowed to 611 // collapse on scroll. 612 return mNotificationListAtEndAtTimeOfTouch; 613 } 614 615 @Override getHandleBarViewId()616 protected Integer getHandleBarViewId() { 617 return R.id.handle_bar; 618 } 619 620 /** 621 * Calculates the alpha value for the background based on how much of the notification 622 * shade is visible to the user. When the notification shade is completely open then 623 * alpha value will be 1. 624 */ getBackgroundAlpha(int y)625 private float getBackgroundAlpha(int y) { 626 float fractionCovered = 627 ((float) (mAnimateDirection > 0 ? y : mNotificationView.getHeight() - y)) 628 / mNotificationView.getHeight(); 629 return mInitialBackgroundAlpha + fractionCovered * mBackgroundAlphaDiff; 630 } 631 632 /** Sets the unseen count listener. */ setOnUnseenCountUpdateListener(OnUnseenCountUpdateListener listener)633 public void setOnUnseenCountUpdateListener(OnUnseenCountUpdateListener listener) { 634 mUnseenCountUpdateListener = listener; 635 } 636 637 /** Listener that is updated when the number of unseen notifications changes. */ 638 public interface OnUnseenCountUpdateListener { 639 /** 640 * This method is automatically called whenever there is an update to the number of unseen 641 * notifications. This method can be extended by OEMs to customize the desired logic. 642 */ onUnseenCountUpdate(int unseenNotificationCount)643 void onUnseenCountUpdate(int unseenNotificationCount); 644 } 645 } 646