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