• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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