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 static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
20 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
21 
22 import android.animation.Animator;
23 import android.animation.AnimatorListenerAdapter;
24 import android.animation.ValueAnimator;
25 import android.annotation.Nullable;
26 import android.app.ActivityManager;
27 import android.app.ActivityTaskManager;
28 import android.car.Car;
29 import android.car.CarNotConnectedException;
30 import android.car.drivingstate.CarDrivingStateEvent;
31 import android.car.drivingstate.CarUxRestrictionsManager;
32 import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
33 import android.car.media.CarAudioManager;
34 import android.content.BroadcastReceiver;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.IntentFilter;
38 import android.graphics.PixelFormat;
39 import android.graphics.Rect;
40 import android.graphics.drawable.Drawable;
41 import android.inputmethodservice.InputMethodService;
42 import android.media.AudioManager;
43 import android.os.Build;
44 import android.os.Handler;
45 import android.os.IBinder;
46 import android.os.Looper;
47 import android.util.Log;
48 import android.view.Display;
49 import android.view.GestureDetector;
50 import android.view.Gravity;
51 import android.view.MotionEvent;
52 import android.view.View;
53 import android.view.ViewGroup;
54 import android.view.ViewGroup.LayoutParams;
55 import android.view.ViewTreeObserver;
56 import android.view.WindowManager;
57 
58 import androidx.annotation.NonNull;
59 import androidx.recyclerview.widget.RecyclerView;
60 
61 import com.android.car.notification.CarHeadsUpNotificationManager;
62 import com.android.car.notification.CarNotificationListener;
63 import com.android.car.notification.CarNotificationView;
64 import com.android.car.notification.CarUxRestrictionManagerWrapper;
65 import com.android.car.notification.HeadsUpEntry;
66 import com.android.car.notification.NotificationClickHandlerFactory;
67 import com.android.car.notification.NotificationDataManager;
68 import com.android.car.notification.NotificationViewController;
69 import com.android.car.notification.PreprocessingManager;
70 import com.android.internal.statusbar.RegisterStatusBarResult;
71 import com.android.keyguard.KeyguardUpdateMonitor;
72 import com.android.systemui.BatteryMeterView;
73 import com.android.systemui.CarSystemUIFactory;
74 import com.android.systemui.Dependency;
75 import com.android.systemui.ForegroundServiceController;
76 import com.android.systemui.Prefs;
77 import com.android.systemui.R;
78 import com.android.systemui.SystemUIFactory;
79 import com.android.systemui.car.SUWProgressController;
80 import com.android.systemui.classifier.FalsingLog;
81 import com.android.systemui.colorextraction.SysuiColorExtractor;
82 import com.android.systemui.fragments.FragmentHostManager;
83 import com.android.systemui.keyguard.ScreenLifecycle;
84 import com.android.systemui.keyguard.WakefulnessLifecycle;
85 import com.android.systemui.plugins.FalsingManager;
86 import com.android.systemui.plugins.qs.QS;
87 import com.android.systemui.qs.car.CarQSFragment;
88 import com.android.systemui.shared.system.ActivityManagerWrapper;
89 import com.android.systemui.shared.system.TaskStackChangeListener;
90 import com.android.systemui.statusbar.FlingAnimationUtils;
91 import com.android.systemui.statusbar.NavigationBarController;
92 import com.android.systemui.statusbar.NotificationListener;
93 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
94 import com.android.systemui.statusbar.NotificationMediaManager;
95 import com.android.systemui.statusbar.NotificationRemoteInputManager;
96 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
97 import com.android.systemui.statusbar.StatusBarState;
98 import com.android.systemui.statusbar.car.hvac.HvacController;
99 import com.android.systemui.statusbar.car.hvac.TemperatureView;
100 import com.android.systemui.statusbar.notification.NotificationEntryManager;
101 import com.android.systemui.statusbar.notification.VisualStabilityManager;
102 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
103 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
104 import com.android.systemui.statusbar.phone.AutoHideElement;
105 import com.android.systemui.statusbar.phone.BarTransitions;
106 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
107 import com.android.systemui.statusbar.phone.LightBarController;
108 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
109 import com.android.systemui.statusbar.phone.NotificationGroupManager;
110 import com.android.systemui.statusbar.phone.StatusBar;
111 import com.android.systemui.statusbar.phone.StatusBarIconController;
112 import com.android.systemui.statusbar.policy.BatteryController;
113 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
114 import com.android.systemui.statusbar.policy.KeyguardMonitor;
115 import com.android.systemui.statusbar.policy.NetworkController;
116 import com.android.systemui.statusbar.policy.UserSwitcherController;
117 import com.android.systemui.statusbar.policy.ZenModeController;
118 import com.android.systemui.volume.VolumeUI;
119 
120 import java.io.FileDescriptor;
121 import java.io.PrintWriter;
122 import java.util.Map;
123 
124 /**
125  * A status bar (and navigation bar) tailored for the automotive use case.
126  */
127 public class CarStatusBar extends StatusBar implements CarBatteryController.BatteryViewHandler {
128     private static final String TAG = "CarStatusBar";
129     private static final int MODE_INVALID = -1;
130     // used to calculate how fast to open or close the window
131     private static final float DEFAULT_FLING_VELOCITY = 0;
132     // max time a fling animation takes
133     private static final float FLING_ANIMATION_MAX_TIME = 0.5f;
134     // acceleration rate for the fling animation
135     private static final float FLING_SPEED_UP_FACTOR = 0.6f;
136 
137     private float mOpeningVelocity = DEFAULT_FLING_VELOCITY;
138     private float mClosingVelocity = DEFAULT_FLING_VELOCITY;
139 
140     private float mBackgroundAlphaDiff;
141     private float mInitialBackgroundAlpha;
142 
143     private TaskStackListenerImpl mTaskStackListener;
144 
145     private FullscreenUserSwitcher mFullscreenUserSwitcher;
146 
147     private CarBatteryController mCarBatteryController;
148     private BatteryMeterView mBatteryMeterView;
149     private Drawable mNotificationPanelBackground;
150 
151     private ViewGroup mTopNavigationBarContainer;
152     private ViewGroup mNavigationBarWindow;
153     private ViewGroup mLeftNavigationBarWindow;
154     private ViewGroup mRightNavigationBarWindow;
155     private CarNavigationBarView mTopNavigationBarView;
156     private CarNavigationBarView mNavigationBarView;
157     private CarNavigationBarView mLeftNavigationBarView;
158     private CarNavigationBarView mRightNavigationBarView;
159 
160     private final Object mQueueLock = new Object();
161     private boolean mShowLeft;
162     private boolean mShowRight;
163     private boolean mShowBottom;
164     private CarFacetButtonController mCarFacetButtonController;
165     private ActivityManagerWrapper mActivityManagerWrapper;
166     private DeviceProvisionedController mDeviceProvisionedController;
167     private SUWProgressController mSUWProgressController;
168     private boolean mDeviceIsSetUpForUser = true;
169     private boolean mIsUserSetupInProgress = false;
170     private HvacController mHvacController;
171     private DrivingStateHelper mDrivingStateHelper;
172     private PowerManagerHelper mPowerManagerHelper;
173     private FlingAnimationUtils mFlingAnimationUtils;
174     private SwitchToGuestTimer mSwitchToGuestTimer;
175     private NotificationDataManager mNotificationDataManager;
176     private CarNotificationListener mCarNotificationListener;
177     private NotificationClickHandlerFactory mNotificationClickHandlerFactory;
178     private ScreenLifecycle mScreenLifecycle;
179     private CarAudioManager mCarAudioManager;
180     private VolumeUI mVolumeUI;
181 
182     // The container for the notifications.
183     private CarNotificationView mNotificationView;
184     private RecyclerView mNotificationList;
185     // The handler bar view at the bottom of notification shade.
186     private View mHandleBar;
187     // The controller for the notification view.
188     private NotificationViewController mNotificationViewController;
189     // The state of if the notification list is currently showing the bottom.
190     private boolean mNotificationListAtBottom;
191     // Was the notification list at the bottom when the user first touched the screen
192     private boolean mNotificationListAtBottomAtTimeOfTouch;
193     // To be attached to the top navigation bar (i.e. status bar) to pull down the notification
194     // panel.
195     private View.OnTouchListener mTopNavBarNotificationTouchListener;
196     // To be attached to the navigation bars such that they can close the notification panel if
197     // it's open.
198     private View.OnTouchListener mNavBarNotificationTouchListener;
199 
200     // Percentage from top of the screen after which the notification shade will open. This value
201     // will be used while opening the notification shade.
202     private int mSettleOpenPercentage;
203     // Percentage from top of the screen below which the notification shade will close. This
204     // value will be used while closing the notification shade.
205     private int mSettleClosePercentage;
206     // Percentage of notification shade open from top of the screen.
207     private int mPercentageFromBottom;
208     // If notification shade is animation to close or to open.
209     private boolean mIsNotificationAnimating;
210 
211     // Tracks when the notification shade is being scrolled. This refers to the glass pane being
212     // scrolled not the recycler view.
213     private boolean mIsTracking;
214     private float mFirstTouchDownOnGlassPane;
215 
216     // If the notification card inside the recycler view is being swiped.
217     private boolean mIsNotificationCardSwiping;
218     // If notification shade is being swiped vertically to close.
219     private boolean mIsSwipingVerticallyToClose;
220     // Whether heads-up notifications should be shown when shade is open.
221     private boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen;
222     // If the nav bar should be hidden when the soft keyboard is visible.
223     private boolean mHideNavBarForKeyboard;
224     private boolean mBottomNavBarVisible;
225 
226     private CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper;
227 
228     private final CarPowerStateListener mCarPowerStateListener =
229             (int state) -> {
230                 // When the car powers on, clear all notifications and mute/unread states.
231                 Log.d(TAG, "New car power state: " + state);
232                 if (state == CarPowerStateListener.ON) {
233                     if (mNotificationClickHandlerFactory != null) {
234                         mNotificationClickHandlerFactory.clearAllNotifications();
235                     }
236                     if (mNotificationDataManager != null) {
237                         mNotificationDataManager.clearAll();
238                     }
239                 }
240             };
241 
242     private final CarAudioManager.CarVolumeCallback mVolumeChangeCallback =
243             new CarAudioManager.CarVolumeCallback() {
244                 @Override
245                 public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
246                     if (mVolumeUI == null && (flags & AudioManager.FLAG_SHOW_UI) != 0) {
247                         new Handler(Looper.getMainLooper()).post(() -> {
248                             // Initialize Volume UI
249                             mVolumeUI = new VolumeUI();
250                             mVolumeUI.mComponents = mComponents;
251                             mVolumeUI.mContext = mContext;
252                             mVolumeUI.start();
253                         });
254                         mCarAudioManager.unregisterCarVolumeCallback(mVolumeChangeCallback);
255                     }
256                 }
257 
258                 @Override
259                 public void onMasterMuteChanged(int zoneId, int flags) {
260                     // ignored
261                 }
262             };
263 
264     private boolean mBootCompleted = false;
265     private final BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
266         @Override
267         public void onReceive(Context context, Intent intent) {
268             mBootCompleted = true;
269             String action = intent.getAction();
270             if (action != null && action.equals(Intent.ACTION_BOOT_COMPLETED)) {
271                 initHvac();
272             }
273         }
274     };
275 
276     private int mNavigationBarMode;
277     private BarTransitions mNavBarTransitions;
278 
279     @Override
start()280     public void start() {
281         // Non blocking call to connect to car service. Call this early so that we'll be connected
282         // asap.
283         ((CarSystemUIFactory) SystemUIFactory.getInstance()).getCarServiceProvider(mContext);
284 
285         // Defer some actions for CarStatusBar initialization until after boot complete event
286         registerBootCompletedReceiver();
287 
288         // get the provisioned state before calling the parent class since it's that flow that
289         // builds the nav bar
290         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
291         mSUWProgressController = new SUWProgressController(mContext);
292         mDeviceIsSetUpForUser = mDeviceProvisionedController.isCurrentUserSetup();
293         mIsUserSetupInProgress = mSUWProgressController.isCurrentUserSetupInProgress();
294 
295         // Keyboard related setup, before nav bars are created.
296         mHideNavBarForKeyboard = mContext.getResources().getBoolean(
297                 com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard);
298         mBottomNavBarVisible = false;
299 
300         // Need to initialize screen lifecycle before calling super.start - before switcher is
301         // created.
302         mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
303         mScreenLifecycle.addObserver(mScreenObserver);
304 
305         // Need to initialize HVAC controller before calling super.start - before system bars are
306         // created.
307         mHvacController = new HvacController(mContext);
308 
309       	// Notification bar related setup.
310         mInitialBackgroundAlpha = (float) mContext.getResources().getInteger(
311             R.integer.config_initialNotificationBackgroundAlpha) / 100;
312         if (mInitialBackgroundAlpha < 0 || mInitialBackgroundAlpha > 100) {
313             throw new RuntimeException(
314               "Unable to setup notification bar due to incorrect initial background alpha"
315                       + " percentage");
316         }
317         float finalBackgroundAlpha = Math.max(
318             mInitialBackgroundAlpha,
319             (float) mContext.getResources().getInteger(
320                 R.integer.config_finalNotificationBackgroundAlpha) / 100);
321         if (finalBackgroundAlpha < 0 || finalBackgroundAlpha > 100) {
322             throw new RuntimeException(
323               "Unable to setup notification bar due to incorrect final background alpha"
324                       + " percentage");
325         }
326         mBackgroundAlphaDiff = finalBackgroundAlpha - mInitialBackgroundAlpha;
327 
328         super.start();
329         mTaskStackListener = new TaskStackListenerImpl();
330         mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
331         mActivityManagerWrapper.registerTaskStackListener(mTaskStackListener);
332 
333         mNotificationPanel.setScrollingEnabled(true);
334         mSettleOpenPercentage = mContext.getResources().getInteger(
335                 R.integer.notification_settle_open_percentage);
336         mSettleClosePercentage = mContext.getResources().getInteger(
337                 R.integer.notification_settle_close_percentage);
338         mFlingAnimationUtils = new FlingAnimationUtils(mContext,
339                 FLING_ANIMATION_MAX_TIME, FLING_SPEED_UP_FACTOR);
340 
341         createBatteryController();
342         mCarBatteryController.startListening();
343 
344         mHvacController.connectToCarService();
345 
346         mSUWProgressController.addCallback(
347                 new SUWProgressController.SUWProgressListener() {
348                     @Override
349                     public void onUserSetupInProgressChanged() {
350                         mHandler.post(() -> restartNavBarsIfNecessary());
351                     }
352                 });
353         mDeviceProvisionedController.addCallback(
354                 new DeviceProvisionedController.DeviceProvisionedListener() {
355                     @Override
356                     public void onUserSetupChanged() {
357                         mHandler.post(() -> restartNavBarsIfNecessary());
358                     }
359 
360                     @Override
361                     public void onUserSwitched() {
362                         mSUWProgressController.onUserSwitched();
363                         mHandler.post(() -> restartNavBarsIfNecessary());
364                     }
365                 });
366 
367         // Used by onDrivingStateChanged and it can be called inside
368         // DrivingStateHelper.connectToCarService()
369         mSwitchToGuestTimer = new SwitchToGuestTimer(mContext);
370 
371         // Register a listener for driving state changes.
372         mDrivingStateHelper = new DrivingStateHelper(mContext, this::onDrivingStateChanged);
373         mDrivingStateHelper.connectToCarService();
374 
375         mPowerManagerHelper = new PowerManagerHelper(mContext, mCarPowerStateListener);
376         mPowerManagerHelper.connectToCarService();
377 
378         ((CarSystemUIFactory) SystemUIFactory.getInstance()).getCarServiceProvider(mContext)
379                 .addListener((car, ready) -> {
380                     if (!ready || mCarAudioManager != null) {
381                         return;
382                     }
383                     try {
384                         mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE);
385                         Log.d(TAG, "Registering mVolumeChangeCallback.");
386                         // This volume call back is never unregistered because CarStatusBar is
387                         // never destroyed.
388                         mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback);
389                     } catch (CarNotConnectedException e) {
390                         Log.wtf(TAG, " mVolumeChangeCallback failed to connect to car ", e);
391                     }
392                 });
393 
394         mAutoHideController.setNavigationBar(new AutoHideElement() {
395             @Override
396             public void synchronizeState() {
397                 checkNavBarModes();
398             }
399 
400             @Override
401             public boolean isSemiTransparent() {
402                 return mNavigationBarMode == MODE_SEMI_TRANSPARENT;
403             }
404         });
405     }
406 
407     @Override
getDependencies()408     protected void getDependencies() {
409         // Keyguard
410         mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
411         mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
412         mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
413 
414         // Policy
415         mZenController = Dependency.get(ZenModeController.class);
416         if (Build.IS_USERDEBUG) {
417             mNetworkController = Dependency.get(NetworkController.class);
418         }
419 
420         // Icon
421         mIconController = Dependency.get(StatusBarIconController.class);
422         mLightBarController = Dependency.get(LightBarController.class);
423 
424         // Notifications
425         mEntryManager = Dependency.get(NotificationEntryManager.class);
426         mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
427         mGroupAlertTransferHelper = Dependency.get(NotificationGroupAlertTransferHelper.class);
428         mGroupManager = Dependency.get(NotificationGroupManager.class);
429         mGutsManager = Dependency.get(NotificationGutsManager.class);
430         mLockscreenUserManager = Dependency.get(NotificationLockscreenUserManager.class);
431         mMediaManager = Dependency.get(NotificationMediaManager.class);
432         mNotificationListener = Dependency.get(NotificationListener.class);
433         mNotificationLogger = Dependency.get(NotificationLogger.class);
434         mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
435         mViewHierarchyManager = Dependency.get(NotificationViewHierarchyManager.class);
436         mVisualStabilityManager = Dependency.get(VisualStabilityManager.class);
437 
438         // Others
439         mColorExtractor = Dependency.get(SysuiColorExtractor.class);
440         mNavigationBarController = Dependency.get(NavigationBarController.class);
441         mUserSwitcherController = Dependency.get(UserSwitcherController.class);
442     }
443 
444     @Override
setSystemUiVisibility(int displayId, int vis, int fullscreenStackVis, int dockedStackVis, int mask, Rect fullscreenStackBounds, Rect dockedStackBounds, boolean navbarColorManagedByIme)445     public void setSystemUiVisibility(int displayId, int vis, int fullscreenStackVis,
446             int dockedStackVis, int mask, Rect fullscreenStackBounds, Rect dockedStackBounds,
447             boolean navbarColorManagedByIme) {
448         // Ensure we store the systemUiVisibility flags before the super call overwrites it.
449         int oldVal = getSystemUiVisibility();
450 
451         super.setSystemUiVisibility(displayId, vis, fullscreenStackVis, dockedStackVis, mask,
452                 fullscreenStackBounds, dockedStackBounds, navbarColorManagedByIme);
453 
454         if (displayId != getDisplayId()) {
455             return;
456         }
457 
458         int newVal = (oldVal & ~mask) | (vis & mask);
459 
460         // update navigation bar mode
461         int nbMode = mNavigationBarWindow == null ? MODE_INVALID : computeNavBarMode(oldVal,
462                 newVal);
463         boolean nbModeChanged = nbMode != MODE_INVALID;
464         if (nbModeChanged) {
465             if (mNavigationBarMode != nbMode) {
466                 mNavigationBarMode = nbMode;
467                 checkNavBarModes();
468             }
469             mAutoHideController.touchAutoHide();
470         }
471 
472         mLightBarController.onNavigationVisibilityChanged(
473                 vis, mask, nbModeChanged, mNavigationBarMode, navbarColorManagedByIme);
474     }
475 
476     @BarTransitions.TransitionMode
computeNavBarMode(int oldVis, int newVis)477     private int computeNavBarMode(int oldVis, int newVis) {
478         int oldMode = navBarMode(oldVis);
479         int newMode = navBarMode(newVis);
480         if (oldMode == newMode) {
481             return -1; // no mode change
482         }
483         return newMode;
484     }
485 
486     @BarTransitions.TransitionMode
navBarMode(int vis)487     private int navBarMode(int vis) {
488         if ((vis & View.NAVIGATION_BAR_TRANSIENT) != 0) {
489             return MODE_SEMI_TRANSPARENT;
490         } else {
491             return MODE_OPAQUE;
492         }
493     }
494 
495     /**
496      * Checks current navigation bar mode and make transitions.
497      */
checkNavBarModes()498     private void checkNavBarModes() {
499         boolean anim = isDeviceInteractive() && mBottomNavBarVisible;
500         mNavBarTransitions.transitionTo(mNavigationBarMode, anim);
501     }
502 
503     @Override
setUpQuickSettingsTilePanel()504     protected void setUpQuickSettingsTilePanel() {
505         // ignore.
506     }
507 
registerBootCompletedReceiver()508     private void registerBootCompletedReceiver() {
509         IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
510         bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
511         mContext.registerReceiver(mBootCompletedReceiver, bootCompletedFilter);
512     }
513 
unregisterBootCompletedListener()514     private void unregisterBootCompletedListener() {
515         mContext.unregisterReceiver(mBootCompletedReceiver);
516     }
517 
restartNavBarsIfNecessary()518     private void restartNavBarsIfNecessary() {
519         boolean currentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
520         boolean currentUserSetupInProgress = mSUWProgressController.isCurrentUserSetupInProgress();
521         if (mIsUserSetupInProgress != currentUserSetupInProgress
522                 || mDeviceIsSetUpForUser != currentUserSetup) {
523             mDeviceIsSetUpForUser = currentUserSetup;
524             mIsUserSetupInProgress = currentUserSetupInProgress;
525             restartNavBars();
526         }
527     }
528 
529     /**
530      * Remove all content from navbars and rebuild them. Used to allow for different nav bars
531      * before and after the device is provisioned. . Also for change of density and font size.
532      */
restartNavBars()533     private void restartNavBars() {
534         // remove and reattach all hvac components such that we don't keep a reference to unused
535         // ui elements
536         if (mHvacController != null) {
537             mHvacController.removeAllComponents();
538         }
539         mCarFacetButtonController.removeAll();
540 
541         if (mNavigationBarWindow != null) {
542             mNavigationBarWindow.removeAllViews();
543             mNavigationBarView = null;
544         }
545 
546         if (mLeftNavigationBarWindow != null) {
547             mLeftNavigationBarWindow.removeAllViews();
548             mLeftNavigationBarView = null;
549         }
550 
551         if (mRightNavigationBarWindow != null) {
552             mRightNavigationBarWindow.removeAllViews();
553             mRightNavigationBarView = null;
554         }
555 
556         buildNavBarContent();
557         if (mBootCompleted) {
558             initHvac();
559         }
560         // If the UI was rebuilt (day/night change) while the keyguard was up we need to
561         // correctly respect that state.
562         if (mIsKeyguard) {
563             updateNavBarForKeyguardContent();
564         }
565         // CarFacetButtonController was reset therefore we need to re-add the status bar elements
566         // to the controller.
567         mCarFacetButtonController.addAllFacetButtons(mStatusBarWindow);
568 
569         // Upon restarting the Navigation Bar, CarFacetButtonController should immediately apply the
570         // selection state that reflects the current task stack.
571         try {
572             mCarFacetButtonController.taskChanged(
573                     ActivityTaskManager.getService().getAllStackInfos());
574         } catch (Exception e) {
575             Log.e(TAG, "Getting StackInfo from activity manager failed", e);
576         }
577     }
578 
addTemperatureViewToController(View v)579     private void addTemperatureViewToController(View v) {
580         if (v == null) return;
581 
582         if (v instanceof TemperatureView) {
583             mHvacController.addHvacTextView((TemperatureView) v);
584         } else if (v instanceof ViewGroup) {
585             ViewGroup viewGroup = (ViewGroup) v;
586             for (int i = 0; i < viewGroup.getChildCount(); i++) {
587                 addTemperatureViewToController(viewGroup.getChildAt(i));
588             }
589         }
590     }
591     /**
592      * Allows for showing or hiding just the navigation bars. This is indented to be used when
593      * the full screen user selector is shown.
594      */
setNavBarVisibility(@iew.Visibility int visibility)595     void setNavBarVisibility(@View.Visibility int visibility) {
596         if (mNavigationBarWindow != null) {
597             mNavigationBarWindow.setVisibility(visibility);
598         }
599         if (mLeftNavigationBarWindow != null) {
600             mLeftNavigationBarWindow.setVisibility(visibility);
601         }
602         if (mRightNavigationBarWindow != null) {
603             mRightNavigationBarWindow.setVisibility(visibility);
604         }
605     }
606 
607 
608     @Override
hideKeyguard()609     public boolean hideKeyguard() {
610         boolean result = super.hideKeyguard();
611         if (mNavigationBarView != null) {
612             mNavigationBarView.hideKeyguardButtons();
613         }
614         if (mLeftNavigationBarView != null) {
615             mLeftNavigationBarView.hideKeyguardButtons();
616         }
617         if (mRightNavigationBarView != null) {
618             mRightNavigationBarView.hideKeyguardButtons();
619         }
620         return result;
621     }
622 
623     @Override
showKeyguard()624     public void showKeyguard() {
625         super.showKeyguard();
626         updateNavBarForKeyguardContent();
627     }
628 
629     /**
630      * Switch to the keyguard applicable content contained in the nav bars
631      */
updateNavBarForKeyguardContent()632     private void updateNavBarForKeyguardContent() {
633         if (mNavigationBarView != null) {
634             mNavigationBarView.showKeyguardButtons();
635         }
636         if (mLeftNavigationBarView != null) {
637             mLeftNavigationBarView.showKeyguardButtons();
638         }
639         if (mRightNavigationBarView != null) {
640             mRightNavigationBarView.showKeyguardButtons();
641         }
642     }
643 
644     @Override
makeStatusBarView(@ullable RegisterStatusBarResult result)645     protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
646         super.makeStatusBarView(result);
647 
648         CarSystemUIFactory factory = SystemUIFactory.getInstance();
649         mCarFacetButtonController = factory.getCarDependencyComponent()
650                 .getCarFacetButtonController();
651         mNotificationPanelBackground = getDefaultWallpaper();
652         mScrimController.setScrimBehindDrawable(mNotificationPanelBackground);
653 
654         FragmentHostManager manager = FragmentHostManager.get(mStatusBarWindow);
655         manager.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
656             mBatteryMeterView = fragment.getView().findViewById(R.id.battery);
657 
658             // By default, the BatteryMeterView should not be visible. It will be toggled
659             // when a device has connected by bluetooth.
660             mBatteryMeterView.setVisibility(View.GONE);
661         });
662 
663         connectNotificationsUI();
664     }
665 
666     /**
667      * Attach the notification listeners and controllers to the UI as well as build all the
668      * touch listeners needed for opening and closing the notification panel
669      */
connectNotificationsUI()670     private void connectNotificationsUI() {
671         // Attached to the top navigation bar (i.e. status bar) to detect pull down of the
672         // notification shade.
673         GestureDetector openGestureDetector = new GestureDetector(mContext,
674                 new OpenNotificationGestureListener() {
675                     @Override
676                     protected void openNotification() {
677                         animateExpandNotificationsPanel();
678                     }
679                 });
680         // Attached to the notification ui to detect close request of the notification shade.
681         GestureDetector closeGestureDetector = new GestureDetector(mContext,
682                 new CloseNotificationGestureListener() {
683                     @Override
684                     protected void close() {
685                         if (mPanelExpanded) {
686                             animateCollapsePanels();
687                         }
688                     }
689                 });
690         // Attached to the NavBars to close the notification shade
691         GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(mContext,
692                 new NavBarCloseNotificationGestureListener() {
693                     @Override
694                     protected void close() {
695                         if (mPanelExpanded) {
696                             animateCollapsePanels();
697                         }
698                     }
699                 });
700 
701         // Attached to the Handle bar to close the notification shade
702         GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext,
703                 new HandleBarCloseNotificationGestureListener());
704 
705         mTopNavBarNotificationTouchListener = (v, event) -> {
706             if (!mDeviceIsSetUpForUser || mIsUserSetupInProgress) {
707                 return true;
708             }
709             boolean consumed = openGestureDetector.onTouchEvent(event);
710             if (consumed) {
711                 return true;
712             }
713             maybeCompleteAnimation(event);
714             return true;
715         };
716 
717         mNavBarNotificationTouchListener =
718                 (v, event) -> {
719                     boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
720                     if (consumed) {
721                         return true;
722                     }
723                     maybeCompleteAnimation(event);
724                     return true;
725                 };
726 
727         mNotificationClickHandlerFactory = new NotificationClickHandlerFactory(
728                 mBarService,
729                 launchResult -> {
730                     if (launchResult == ActivityManager.START_TASK_TO_FRONT
731                             || launchResult == ActivityManager.START_SUCCESS) {
732                         animateCollapsePanels();
733                     }
734                 });
735 
736         mCarUxRestrictionManagerWrapper = new CarUxRestrictionManagerWrapper();
737 
738         if (mCarNotificationListener == null) {
739             // Only make and register a listener if we don't already have one since
740             // #connectNotificationsUI can be called multiple times on locale change.
741             mCarNotificationListener = new CarNotificationListener();
742             mNotificationDataManager = new NotificationDataManager();
743 
744             CarHeadsUpNotificationManager carHeadsUpNotificationManager =
745                     new CarSystemUIHeadsUpNotificationManager(mContext,
746                             mNotificationClickHandlerFactory, mNotificationDataManager);
747             mCarNotificationListener.registerAsSystemService(mContext,
748                     mCarUxRestrictionManagerWrapper,
749                     carHeadsUpNotificationManager, mNotificationDataManager);
750         }
751 
752         mNotificationDataManager.setOnUnseenCountUpdateListener(() -> {
753             if (mNavigationBarView != null && mNotificationDataManager != null) {
754                 onUseenCountUpdate(mNotificationDataManager.getUnseenNotificationCount());
755             }
756         });
757 
758         mEnableHeadsUpNotificationWhenNotificationShadeOpen = mContext.getResources().getBoolean(
759                 R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen);
760 
761         mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
762 
763         mNotificationView = mStatusBarWindow.findViewById(R.id.notification_view);
764         View glassPane = mStatusBarWindow.findViewById(R.id.glass_pane);
765         mHandleBar = mStatusBarWindow.findViewById(R.id.handle_bar);
766         mNotificationView.setClickHandlerFactory(mNotificationClickHandlerFactory);
767         mNotificationView.setNotificationDataManager(mNotificationDataManager);
768 
769         // The glass pane is used to view touch events before passed to the notification list.
770         // This allows us to initialize gesture listeners and detect when to close the notifications
771         glassPane.setOnTouchListener((v, event) -> {
772             if (event.getActionMasked() == MotionEvent.ACTION_UP) {
773                 mNotificationListAtBottomAtTimeOfTouch = false;
774             }
775             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
776                 mFirstTouchDownOnGlassPane = event.getRawX();
777                 mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom;
778                 // Reset the tracker when there is a touch down on the glass pane.
779                 mIsTracking = false;
780                 // Pass the down event to gesture detector so that it knows where the touch event
781                 // started.
782                 closeGestureDetector.onTouchEvent(event);
783             }
784             return false;
785         });
786 
787         mHandleBar.setOnTouchListener((v, event) -> {
788             handleBarCloseNotificationGestureDetector.onTouchEvent(event);
789             maybeCompleteAnimation(event);
790             return true;
791         });
792 
793         mNotificationList = mNotificationView.findViewById(R.id.notifications);
794         mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() {
795             @Override
796             public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
797                 super.onScrolled(recyclerView, dx, dy);
798                 if (!mNotificationList.canScrollVertically(1)) {
799                     mNotificationListAtBottom = true;
800                     return;
801                 }
802                 mNotificationListAtBottom = false;
803                 mIsSwipingVerticallyToClose = false;
804                 mNotificationListAtBottomAtTimeOfTouch = false;
805             }
806         });
807         mNotificationList.setOnTouchListener(new View.OnTouchListener() {
808             @Override
809             public boolean onTouch(View v, MotionEvent event) {
810                 mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX())
811                         > SWIPE_MAX_OFF_PATH;
812                 if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) {
813                     // We need to save the state here as if notification card is swiping we will
814                     // change the mNotificationListAtBottomAtTimeOfTouch. This is to protect
815                     // closing the notification shade while the notification card is being swiped.
816                     mIsSwipingVerticallyToClose = true;
817                 }
818 
819                 // If the card is swiping we should not allow the notification shade to close.
820                 // Hence setting mNotificationListAtBottomAtTimeOfTouch to false will stop that
821                 // for us. We are also checking for mIsTracking because while swiping the
822                 // notification shade to close if the user goes a bit horizontal while swiping
823                 // upwards then also this should close.
824                 if (mIsNotificationCardSwiping && !mIsTracking) {
825                     mNotificationListAtBottomAtTimeOfTouch = false;
826                 }
827 
828                 boolean handled = closeGestureDetector.onTouchEvent(event);
829                 boolean isTracking = mIsTracking;
830                 Rect rect = mNotificationView.getClipBounds();
831                 float clippedHeight = 0;
832                 if (rect != null) {
833                     clippedHeight = rect.bottom;
834                 }
835                 if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP
836                         && mIsSwipingVerticallyToClose) {
837                     if (mSettleClosePercentage < mPercentageFromBottom && isTracking) {
838                         animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
839                     } else if (clippedHeight != mNotificationView.getHeight() && isTracking) {
840                         // this can be caused when user is at the end of the list and trying to
841                         // fling to top of the list by scrolling down.
842                         animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
843                     }
844                 }
845 
846                 // Updating the mNotificationListAtBottomAtTimeOfTouch state has to be done after
847                 // the event has been passed to the closeGestureDetector above, such that the
848                 // closeGestureDetector sees the up event before the state has changed.
849                 if (event.getActionMasked() == MotionEvent.ACTION_UP) {
850                     mNotificationListAtBottomAtTimeOfTouch = false;
851                 }
852                 return handled || isTracking;
853             }
854         });
855         ((CarSystemUIFactory) SystemUIFactory.getInstance()).getCarServiceProvider(mContext)
856                 .addListener((car, ready) -> {
857                     if (!ready) {
858                         return;
859                     }
860                     CarUxRestrictionsManager carUxRestrictionsManager =
861                             (CarUxRestrictionsManager)
862                                     car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
863                     mCarUxRestrictionManagerWrapper.setCarUxRestrictionsManager(
864                             carUxRestrictionsManager);
865 
866                     mNotificationViewController = new NotificationViewController(
867                             mNotificationView,
868                             PreprocessingManager.getInstance(mContext),
869                             mCarNotificationListener,
870                             mCarUxRestrictionManagerWrapper,
871                             mNotificationDataManager);
872                     mNotificationViewController.enable();
873                 });
874     }
875 
876     /**
877      * This method is automatically called whenever there is an update to the number of unseen
878      * notifications. This method can be extended by OEMs to customize the desired logic.
879      */
onUseenCountUpdate(int unseenNotificationCount)880     protected void onUseenCountUpdate(int unseenNotificationCount) {
881         boolean hasUnseen = unseenNotificationCount > 0;
882 
883         if (mNavigationBarView != null) {
884             mNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
885         }
886 
887         if (mLeftNavigationBarView != null) {
888             mLeftNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
889         }
890 
891         if (mRightNavigationBarView != null) {
892             mRightNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
893         }
894     }
895 
896     /**
897      * @return true if the notification panel is currently visible
898      */
isNotificationPanelOpen()899     boolean isNotificationPanelOpen() {
900         return mPanelExpanded;
901     }
902 
903     @Override
animateExpandNotificationsPanel()904     public void animateExpandNotificationsPanel() {
905         if (!mCommandQueue.panelsEnabled() || !mUserSetup) {
906             return;
907         }
908         // scroll to top
909         mNotificationList.scrollToPosition(0);
910         mStatusBarWindowController.setPanelVisible(true);
911         mNotificationView.setVisibility(View.VISIBLE);
912         animateNotificationPanel(mOpeningVelocity, false);
913 
914         setPanelExpanded(true);
915     }
916 
917     @Override
animateCollapsePanels(int flags, boolean force, boolean delayed, float speedUpFactor)918     public void animateCollapsePanels(int flags, boolean force, boolean delayed,
919             float speedUpFactor) {
920         super.animateCollapsePanels(flags, force, delayed, speedUpFactor);
921         if (!mPanelExpanded || mNotificationView.getVisibility() == View.INVISIBLE) {
922             return;
923         }
924         mStatusBarWindowController.setStatusBarFocusable(false);
925         mStatusBarWindow.cancelExpandHelper();
926         mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor);
927 
928         animateNotificationPanel(mClosingVelocity, true);
929 
930         if (!mIsTracking) {
931             mStatusBarWindowController.setPanelVisible(false);
932             mNotificationView.setVisibility(View.INVISIBLE);
933         }
934 
935         setPanelExpanded(false);
936     }
937 
maybeCompleteAnimation(MotionEvent event)938     private void maybeCompleteAnimation(MotionEvent event) {
939         if (event.getActionMasked() == MotionEvent.ACTION_UP
940                 && mNotificationView.getVisibility() == View.VISIBLE) {
941             if (mSettleClosePercentage < mPercentageFromBottom) {
942                 animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
943             } else {
944                 animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
945             }
946         }
947     }
948 
949     /**
950      * Animates the notification shade from one position to other. This is used to either open or
951      * close the notification shade completely with a velocity. If the animation is to close the
952      * notification shade this method also makes the view invisible after animation ends.
953      */
animateNotificationPanel(float velocity, boolean isClosing)954     private void animateNotificationPanel(float velocity, boolean isClosing) {
955         float to = 0;
956         if (!isClosing) {
957             to = mNotificationView.getHeight();
958         }
959 
960         Rect rect = mNotificationView.getClipBounds();
961         if (rect != null) {
962             float from = rect.bottom;
963             animate(from, to, velocity, isClosing);
964             return;
965         }
966 
967         // We will only be here if the shade is being opened programmatically.
968         ViewTreeObserver notificationTreeObserver = mNotificationView.getViewTreeObserver();
969         notificationTreeObserver.addOnGlobalLayoutListener(
970                 new ViewTreeObserver.OnGlobalLayoutListener() {
971                     @Override
972                     public void onGlobalLayout() {
973                         ViewTreeObserver obs = mNotificationView.getViewTreeObserver();
974                         obs.removeOnGlobalLayoutListener(this);
975                         float to = mNotificationView.getHeight();
976                         animate(/* from= */ 0, to, velocity, isClosing);
977                     }
978                 });
979     }
980 
animate(float from, float to, float velocity, boolean isClosing)981     private void animate(float from, float to, float velocity, boolean isClosing) {
982         if (mIsNotificationAnimating) {
983             return;
984         }
985         mIsNotificationAnimating = true;
986         mIsTracking = true;
987         ValueAnimator animator = ValueAnimator.ofFloat(from, to);
988         animator.addUpdateListener(
989                 animation -> {
990                     float animatedValue = (Float) animation.getAnimatedValue();
991                     setNotificationViewClipBounds((int) animatedValue);
992                 });
993         animator.addListener(new AnimatorListenerAdapter() {
994             @Override
995             public void onAnimationEnd(Animator animation) {
996                 super.onAnimationEnd(animation);
997                 mIsNotificationAnimating = false;
998                 mIsTracking = false;
999                 mOpeningVelocity = DEFAULT_FLING_VELOCITY;
1000                 mClosingVelocity = DEFAULT_FLING_VELOCITY;
1001                 if (isClosing) {
1002                     mStatusBarWindowController.setPanelVisible(false);
1003                     mNotificationView.setVisibility(View.INVISIBLE);
1004                     mNotificationView.setClipBounds(null);
1005                     mNotificationViewController.setIsInForeground(false);
1006                     // let the status bar know that the panel is closed
1007                     setPanelExpanded(false);
1008                     mAutoHideController.userAutoHide();
1009                 } else {
1010                     mAutoHideController.cancelAutoHide();
1011                     mNotificationViewController.setIsInForeground(true);
1012                     // let the status bar know that the panel is open
1013                     mNotificationView.setVisibleNotificationsAsSeen();
1014                     setPanelExpanded(true);
1015                 }
1016             }
1017         });
1018         mFlingAnimationUtils.apply(animator, from, to, Math.abs(velocity));
1019         animator.start();
1020     }
1021 
1022     @Override
createDefaultQSFragment()1023     protected QS createDefaultQSFragment() {
1024         return new CarQSFragment();
1025     }
1026 
createBatteryController()1027     private BatteryController createBatteryController() {
1028         mCarBatteryController = new CarBatteryController(mContext);
1029         mCarBatteryController.addBatteryViewHandler(this);
1030         return mCarBatteryController;
1031     }
1032 
1033     @Override
createNavigationBar(@ullable RegisterStatusBarResult result)1034     protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
1035         mShowBottom = mContext.getResources().getBoolean(R.bool.config_enableBottomNavigationBar);
1036         mShowLeft = mContext.getResources().getBoolean(R.bool.config_enableLeftNavigationBar);
1037         mShowRight = mContext.getResources().getBoolean(R.bool.config_enableRightNavigationBar);
1038 
1039         buildNavBarWindows();
1040         buildNavBarContent();
1041         attachNavBarWindows();
1042 
1043         // Try setting up the initial state of the nav bar if applicable.
1044         if (result != null) {
1045             setImeWindowStatus(Display.DEFAULT_DISPLAY, result.mImeToken,
1046                     result.mImeWindowVis, result.mImeBackDisposition,
1047                     result.mShowImeSwitcher);
1048         }
1049 
1050         // There has been a car customized nav bar on the default display, so just create nav bars
1051         // on external displays.
1052         mNavigationBarController.createNavigationBars(false /* includeDefaultDisplay */, result);
1053     }
1054 
buildNavBarContent()1055     private void buildNavBarContent() {
1056         boolean shouldBuildNavBarContent = mDeviceIsSetUpForUser && !mIsUserSetupInProgress;
1057 
1058         // Always build top bar.
1059         buildTopBar(shouldBuildNavBarContent ? R.layout.car_top_navigation_bar :
1060                 R.layout.car_top_navigation_bar_unprovisioned);
1061 
1062         if (mShowBottom) {
1063             buildBottomBar(shouldBuildNavBarContent ? R.layout.car_navigation_bar :
1064                     R.layout.car_navigation_bar_unprovisioned);
1065         }
1066 
1067         if (mShowLeft) {
1068             buildLeft(shouldBuildNavBarContent ? R.layout.car_left_navigation_bar :
1069                     R.layout.car_left_navigation_bar_unprovisioned);
1070         }
1071 
1072         if (mShowRight) {
1073             buildRight(shouldBuildNavBarContent ? R.layout.car_right_navigation_bar :
1074                     R.layout.car_right_navigation_bar_unprovisioned);
1075         }
1076     }
1077 
buildNavBarWindows()1078     private void buildNavBarWindows() {
1079         mTopNavigationBarContainer = mStatusBarWindow
1080             .findViewById(R.id.car_top_navigation_bar_container);
1081 
1082         if (mShowBottom) {
1083             mNavigationBarWindow = (ViewGroup) View.inflate(mContext,
1084                     R.layout.navigation_bar_window, null);
1085             mNavBarTransitions = new BarTransitions(mNavigationBarWindow,
1086                     R.drawable.nav_background);
1087         }
1088         if (mShowLeft) {
1089             mLeftNavigationBarWindow = (ViewGroup) View.inflate(mContext,
1090                     R.layout.navigation_bar_window, null);
1091         }
1092         if (mShowRight) {
1093             mRightNavigationBarWindow = (ViewGroup) View.inflate(mContext,
1094                     R.layout.navigation_bar_window, null);
1095         }
1096 
1097     }
1098 
1099     /**
1100      * We register for soft keyboard visibility events such that we can hide the navigation bar
1101      * giving more screen space to the IME. Note: this is optional and controlled by
1102      * {@code com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard}.
1103      */
1104     @Override
setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher)1105     public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
1106             boolean showImeSwitcher) {
1107         if (!mHideNavBarForKeyboard) {
1108             return;
1109         }
1110 
1111         if (mContext.getDisplay().getDisplayId() != displayId) {
1112             return;
1113         }
1114 
1115         boolean isKeyboardVisible = (vis & InputMethodService.IME_VISIBLE) != 0;
1116         showBottomNavBarWindow(isKeyboardVisible);
1117     }
1118 
attachNavBarWindows()1119     private void attachNavBarWindows() {
1120         if (mShowBottom && !mBottomNavBarVisible) {
1121             mBottomNavBarVisible = true;
1122 
1123             WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
1124                     LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
1125                     WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
1126                     WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
1127                             | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
1128                             | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
1129                             | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
1130                     PixelFormat.TRANSLUCENT);
1131             lp.setTitle("CarNavigationBar");
1132             lp.windowAnimations = 0;
1133             mWindowManager.addView(mNavigationBarWindow, lp);
1134         }
1135 
1136         if (mShowLeft) {
1137             int width = mContext.getResources().getDimensionPixelSize(
1138                     R.dimen.car_left_navigation_bar_width);
1139             WindowManager.LayoutParams leftlp = new WindowManager.LayoutParams(
1140                     width, LayoutParams.MATCH_PARENT,
1141                     WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
1142                     WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
1143                             | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
1144                             | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
1145                             | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
1146                     PixelFormat.TRANSLUCENT);
1147             leftlp.setTitle("LeftCarNavigationBar");
1148             leftlp.windowAnimations = 0;
1149             leftlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
1150             leftlp.gravity = Gravity.LEFT;
1151             mWindowManager.addView(mLeftNavigationBarWindow, leftlp);
1152         }
1153         if (mShowRight) {
1154             int width = mContext.getResources().getDimensionPixelSize(
1155                     R.dimen.car_right_navigation_bar_width);
1156             WindowManager.LayoutParams rightlp = new WindowManager.LayoutParams(
1157                     width, LayoutParams.MATCH_PARENT,
1158                     WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
1159                     WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
1160                             | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
1161                             | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
1162                             | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
1163                     PixelFormat.TRANSLUCENT);
1164             rightlp.setTitle("RightCarNavigationBar");
1165             rightlp.windowAnimations = 0;
1166             rightlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
1167             rightlp.gravity = Gravity.RIGHT;
1168             mWindowManager.addView(mRightNavigationBarWindow, rightlp);
1169         }
1170     }
1171 
showBottomNavBarWindow(boolean isKeyboardVisible)1172     private void showBottomNavBarWindow(boolean isKeyboardVisible) {
1173         if (!mShowBottom) {
1174             return;
1175         }
1176 
1177         // If keyboard is visible and bottom nav bar not visible, this is the correct state, so do
1178         // nothing. Same with if keyboard is not visible and bottom nav bar is visible.
1179         if (isKeyboardVisible ^ mBottomNavBarVisible) {
1180             return;
1181         }
1182 
1183         mNavigationBarWindow.setVisibility(isKeyboardVisible ? View.GONE : View.VISIBLE);
1184         setNotificationViewBottomMargin(isKeyboardVisible ? 0 : mNavigationBarWindow.getHeight());
1185         mBottomNavBarVisible = !isKeyboardVisible;
1186     }
1187 
buildTopBar(int layout)1188     private void buildTopBar(int layout) {
1189         mTopNavigationBarContainer.removeAllViews();
1190         View.inflate(mContext, layout, mTopNavigationBarContainer);
1191         mTopNavigationBarView = (CarNavigationBarView) mTopNavigationBarContainer.getChildAt(0);
1192         if (mTopNavigationBarView == null) {
1193             Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_top_navigation_bar");
1194             throw new RuntimeException("Unable to build top nav bar due to missing layout");
1195         }
1196         mTopNavigationBarView.setStatusBar(this);
1197         mTopNavigationBarView.setStatusBarWindowTouchListener(mTopNavBarNotificationTouchListener);
1198     }
1199 
buildBottomBar(int layout)1200     private void buildBottomBar(int layout) {
1201         // SystemUI requires that the navigation bar view have a parent. Since the regular
1202         // StatusBar inflates navigation_bar_window as this parent view, use the same view for the
1203         // CarNavigationBarView.
1204         View.inflate(mContext, layout, mNavigationBarWindow);
1205         mNavigationBarView = (CarNavigationBarView) mNavigationBarWindow.getChildAt(0);
1206         if (mNavigationBarView == null) {
1207             Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
1208             throw new RuntimeException("Unable to build bottom nav bar due to missing layout");
1209         }
1210         mNavigationBarView.setStatusBar(this);
1211         mNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
1212     }
1213 
buildLeft(int layout)1214     private void buildLeft(int layout) {
1215         View.inflate(mContext, layout, mLeftNavigationBarWindow);
1216         mLeftNavigationBarView = (CarNavigationBarView) mLeftNavigationBarWindow.getChildAt(0);
1217         if (mLeftNavigationBarView == null) {
1218             Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_left_navigation_bar");
1219             throw new RuntimeException("Unable to build left nav bar due to missing layout");
1220         }
1221         mLeftNavigationBarView.setStatusBar(this);
1222         mLeftNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
1223     }
1224 
1225 
buildRight(int layout)1226     private void buildRight(int layout) {
1227         View.inflate(mContext, layout, mRightNavigationBarWindow);
1228         mRightNavigationBarView = (CarNavigationBarView) mRightNavigationBarWindow.getChildAt(0);
1229         if (mRightNavigationBarView == null) {
1230             Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_right_navigation_bar");
1231             throw new RuntimeException("Unable to build right nav bar due to missing layout");
1232         }
1233         mRightNavigationBarView.setStatusBar(this);
1234         mRightNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
1235     }
1236 
initHvac()1237     private void initHvac() {
1238         if (mHvacController == null) {
1239             mHvacController = Dependency.get(HvacController.class);
1240             mHvacController.connectToCarService();
1241         }
1242         addTemperatureViewToController(mTopNavigationBarView);
1243         addTemperatureViewToController(mNavigationBarView);
1244         addTemperatureViewToController(mLeftNavigationBarView);
1245         addTemperatureViewToController(mRightNavigationBarView);
1246     }
1247 
setNotificationViewBottomMargin(int bottomMargin)1248     private void setNotificationViewBottomMargin(int bottomMargin) {
1249         ViewGroup.MarginLayoutParams params =
1250                 (ViewGroup.MarginLayoutParams) mNotificationView.getLayoutParams();
1251         params.setMargins(params.leftMargin, params.topMargin, params.rightMargin, bottomMargin);
1252         mNotificationView.setLayoutParams(params);
1253     }
1254 
1255     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)1256     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1257         //When executing dump() function simultaneously, we need to serialize them
1258         //to get mStackScroller's position correctly.
1259         synchronized (mQueueLock) {
1260             pw.println("  mStackScroller: " + viewInfo(mStackScroller));
1261             pw.println("  mStackScroller: " + viewInfo(mStackScroller)
1262                     + " scroll " + mStackScroller.getScrollX()
1263                     + "," + mStackScroller.getScrollY());
1264         }
1265 
1266         pw.print("  mTaskStackListener=");
1267         pw.println(mTaskStackListener);
1268         pw.print("  mCarFacetButtonController=");
1269         pw.println(mCarFacetButtonController);
1270         pw.print("  mFullscreenUserSwitcher=");
1271         pw.println(mFullscreenUserSwitcher);
1272         pw.print("  mCarBatteryController=");
1273         pw.println(mCarBatteryController);
1274         pw.print("  mBatteryMeterView=");
1275         pw.println(mBatteryMeterView);
1276         pw.print("  mNavigationBarView=");
1277         pw.println(mNavigationBarView);
1278 
1279         if (KeyguardUpdateMonitor.getInstance(mContext) != null) {
1280             KeyguardUpdateMonitor.getInstance(mContext).dump(fd, pw, args);
1281         }
1282 
1283         Dependency.get(FalsingManager.class).dump(pw);
1284         FalsingLog.dump(pw);
1285 
1286         pw.println("SharedPreferences:");
1287         for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) {
1288             pw.print("  ");
1289             pw.print(entry.getKey());
1290             pw.print("=");
1291             pw.println(entry.getValue());
1292         }
1293     }
1294 
1295     @Override
showBatteryView()1296     public void showBatteryView() {
1297         if (Log.isLoggable(TAG, Log.DEBUG)) {
1298             Log.d(TAG, "showBatteryView(). mBatteryMeterView: " + mBatteryMeterView);
1299         }
1300 
1301         if (mBatteryMeterView != null) {
1302             mBatteryMeterView.setVisibility(View.VISIBLE);
1303         }
1304     }
1305 
1306     @Override
hideBatteryView()1307     public void hideBatteryView() {
1308         if (Log.isLoggable(TAG, Log.DEBUG)) {
1309             Log.d(TAG, "hideBatteryView(). mBatteryMeterView: " + mBatteryMeterView);
1310         }
1311 
1312         if (mBatteryMeterView != null) {
1313             mBatteryMeterView.setVisibility(View.GONE);
1314         }
1315     }
1316 
1317     /**
1318      * An implementation of TaskStackChangeListener, that listens for changes in the system
1319      * task stack and notifies the navigation bar.
1320      */
1321     private class TaskStackListenerImpl extends TaskStackChangeListener {
1322         @Override
onTaskStackChanged()1323         public void onTaskStackChanged() {
1324             try {
1325                 mCarFacetButtonController.taskChanged(
1326                         ActivityTaskManager.getService().getAllStackInfos());
1327             } catch (Exception e) {
1328                 Log.e(TAG, "Getting StackInfo from activity manager failed", e);
1329             }
1330         }
1331 
1332         @Override
onTaskDisplayChanged(int taskId, int newDisplayId)1333         public void onTaskDisplayChanged(int taskId, int newDisplayId) {
1334             try {
1335                 mCarFacetButtonController.taskChanged(
1336                         ActivityTaskManager.getService().getAllStackInfos());
1337             } catch (Exception e) {
1338                 Log.e(TAG, "Getting StackInfo from activity manager failed", e);
1339             }
1340         }
1341     }
1342 
onDrivingStateChanged(CarDrivingStateEvent notUsed)1343     private void onDrivingStateChanged(CarDrivingStateEvent notUsed) {
1344         // Check if we need to start the timer every time driving state changes.
1345         startSwitchToGuestTimerIfDrivingOnKeyguard();
1346     }
1347 
startSwitchToGuestTimerIfDrivingOnKeyguard()1348     private void startSwitchToGuestTimerIfDrivingOnKeyguard() {
1349         if (mDrivingStateHelper.isCurrentlyDriving() && mState != StatusBarState.SHADE) {
1350             // We're driving while keyguard is up.
1351             mSwitchToGuestTimer.start();
1352         } else {
1353             mSwitchToGuestTimer.cancel();
1354         }
1355     }
1356 
1357     @Override
createUserSwitcher()1358     protected void createUserSwitcher() {
1359         UserSwitcherController userSwitcherController =
1360                 Dependency.get(UserSwitcherController.class);
1361         if (userSwitcherController.useFullscreenUserSwitcher()) {
1362             mFullscreenUserSwitcher = new FullscreenUserSwitcher(this,
1363                     mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub), mContext);
1364         } else {
1365             super.createUserSwitcher();
1366         }
1367     }
1368 
1369     @Override
setLockscreenUser(int newUserId)1370     public void setLockscreenUser(int newUserId) {
1371         super.setLockscreenUser(newUserId);
1372         // Try to dismiss the keyguard after every user switch.
1373         dismissKeyguardWhenUserSwitcherNotDisplayed();
1374     }
1375 
1376     @Override
onStateChanged(int newState)1377     public void onStateChanged(int newState) {
1378         super.onStateChanged(newState);
1379 
1380         startSwitchToGuestTimerIfDrivingOnKeyguard();
1381 
1382         if (newState != StatusBarState.FULLSCREEN_USER_SWITCHER) {
1383             hideUserSwitcher();
1384         } else {
1385             dismissKeyguardWhenUserSwitcherNotDisplayed();
1386         }
1387     }
1388 
1389     /** Makes the full screen user switcher visible, if applicable. */
showUserSwitcher()1390     public void showUserSwitcher() {
1391         if (mFullscreenUserSwitcher != null && mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
1392             mFullscreenUserSwitcher.show(); // Makes the switcher visible.
1393         }
1394     }
1395 
hideUserSwitcher()1396     private void hideUserSwitcher() {
1397         if (mFullscreenUserSwitcher != null) {
1398             mFullscreenUserSwitcher.hide();
1399         }
1400     }
1401 
1402     final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
1403         @Override
1404         public void onScreenTurnedOn() {
1405             dismissKeyguardWhenUserSwitcherNotDisplayed();
1406         }
1407     };
1408 
1409     // We automatically dismiss keyguard unless user switcher is being shown on the keyguard.
dismissKeyguardWhenUserSwitcherNotDisplayed()1410     private void dismissKeyguardWhenUserSwitcherNotDisplayed() {
1411         if (mFullscreenUserSwitcher == null) {
1412             return; // Not using the full screen user switcher.
1413         }
1414 
1415         if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER
1416                 && !mFullscreenUserSwitcher.isVisible()) {
1417             // Current execution path continues to set state after this, thus we deffer the
1418             // dismissal to the next execution cycle.
1419             postDismissKeyguard(); // Dismiss the keyguard if switcher is not visible.
1420         }
1421     }
1422 
postDismissKeyguard()1423     public void postDismissKeyguard() {
1424         mHandler.post(this::dismissKeyguard);
1425     }
1426 
1427     /**
1428      * Dismisses the keyguard and shows bouncer if authentication is necessary.
1429      */
dismissKeyguard()1430     public void dismissKeyguard() {
1431         // Don't dismiss keyguard when the screen is off.
1432         if (mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_OFF) {
1433             return;
1434         }
1435         executeRunnableDismissingKeyguard(null/* runnable */, null /* cancelAction */,
1436                 true /* dismissShade */, true /* afterKeyguardGone */, true /* deferred */);
1437     }
1438 
1439     /**
1440      * Ensures that relevant child views are appropriately recreated when the device's density
1441      * changes.
1442      */
1443     @Override
onDensityOrFontScaleChanged()1444     public void onDensityOrFontScaleChanged() {
1445         super.onDensityOrFontScaleChanged();
1446         restartNavBars();
1447         // Need to update the background on density changed in case the change was due to night
1448         // mode.
1449         mNotificationPanelBackground = getDefaultWallpaper();
1450         mScrimController.setScrimBehindDrawable(mNotificationPanelBackground);
1451     }
1452 
1453     @Override
onLocaleListChanged()1454     public void onLocaleListChanged() {
1455         // When locale changes we need to reload the notification panel with the new language
1456         if (mNotificationView == null) {
1457             return;
1458         }
1459 
1460         LayoutParams params = mNotificationView.getLayoutParams();
1461         int index = mStatusBarWindow.indexOfChild(mNotificationView);
1462 
1463         mStatusBarWindow.removeView(mNotificationView);
1464 
1465         View v = View.inflate(mContext, R.layout.notification_center_activity, null);
1466         mStatusBarWindow.addView(v, index, params);
1467 
1468         restartNavBars();
1469         connectNotificationsUI();
1470     }
1471 
1472     /**
1473      * Returns the {@link Drawable} that represents the wallpaper that the user has currently set.
1474      */
getDefaultWallpaper()1475     private Drawable getDefaultWallpaper() {
1476         return mContext.getDrawable(com.android.internal.R.drawable.default_wallpaper);
1477     }
1478 
setNotificationViewClipBounds(int height)1479     private void setNotificationViewClipBounds(int height) {
1480         if (height > mNotificationView.getHeight()) {
1481             height = mNotificationView.getHeight();
1482         }
1483         Rect clipBounds = new Rect();
1484         clipBounds.set(0, 0, mNotificationView.getWidth(), height);
1485         // Sets the clip region on the notification list view.
1486         mNotificationView.setClipBounds(clipBounds);
1487         if (mHandleBar != null) {
1488             ViewGroup.MarginLayoutParams lp =
1489                     (ViewGroup.MarginLayoutParams) mHandleBar.getLayoutParams();
1490             mHandleBar.setTranslationY(height - mHandleBar.getHeight() - lp.bottomMargin);
1491         }
1492         if (mNotificationView.getHeight() > 0) {
1493             Drawable background = mNotificationView.getBackground().mutate();
1494             background.setAlpha((int) (getBackgroundAlpha(height) * 255));
1495             mNotificationView.setBackground(background);
1496         }
1497     }
1498 
1499     /**
1500      * Calculates the alpha value for the background based on how much of the notification
1501      * shade is visible to the user. When the notification shade is completely open then
1502      * alpha value will be 1.
1503      */
getBackgroundAlpha(int height)1504     private float getBackgroundAlpha(int height) {
1505         return mInitialBackgroundAlpha +
1506             ((float) height / mNotificationView.getHeight() * mBackgroundAlphaDiff);
1507     }
1508 
calculatePercentageFromBottom(float height)1509     private void calculatePercentageFromBottom(float height) {
1510         if (mNotificationView.getHeight() > 0) {
1511             mPercentageFromBottom = (int) Math.abs(
1512                     height / mNotificationView.getHeight() * 100);
1513         }
1514     }
1515 
1516     private static final int SWIPE_DOWN_MIN_DISTANCE = 25;
1517     private static final int SWIPE_MAX_OFF_PATH = 75;
1518     private static final int SWIPE_THRESHOLD_VELOCITY = 200;
1519 
1520     /**
1521      * Only responsible for open hooks. Since once the panel opens it covers all elements
1522      * there is no need to merge with close.
1523      */
1524     private abstract class OpenNotificationGestureListener extends
1525             GestureDetector.SimpleOnGestureListener {
1526 
1527         @Override
onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY)1528         public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
1529                 float distanceY) {
1530 
1531             if (mNotificationView.getVisibility() == View.INVISIBLE) {
1532                 // when the on-scroll is called for the first time to open.
1533                 mNotificationList.scrollToPosition(0);
1534             }
1535             mStatusBarWindowController.setPanelVisible(true);
1536             mNotificationView.setVisibility(View.VISIBLE);
1537 
1538             // clips the view for the notification shade when the user scrolls to open.
1539             setNotificationViewClipBounds((int) event2.getRawY());
1540 
1541             // Initially the scroll starts with height being zero. This checks protects from divide
1542             // by zero error.
1543             calculatePercentageFromBottom(event2.getRawY());
1544 
1545             mIsTracking = true;
1546             return true;
1547         }
1548 
1549 
1550         @Override
onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY)1551         public boolean onFling(MotionEvent event1, MotionEvent event2,
1552                 float velocityX, float velocityY) {
1553             if (velocityY > SWIPE_THRESHOLD_VELOCITY) {
1554                 mOpeningVelocity = velocityY;
1555                 openNotification();
1556                 return true;
1557             }
1558             animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
1559 
1560             return false;
1561         }
1562 
openNotification()1563         protected abstract void openNotification();
1564     }
1565 
1566     /**
1567      * To be installed on the open panel notification panel
1568      */
1569     private abstract class CloseNotificationGestureListener extends
1570             GestureDetector.SimpleOnGestureListener {
1571 
1572         @Override
onSingleTapUp(MotionEvent motionEvent)1573         public boolean onSingleTapUp(MotionEvent motionEvent) {
1574             if (mPanelExpanded) {
1575                 animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
1576             }
1577             return true;
1578         }
1579 
1580         @Override
onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY)1581         public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
1582                 float distanceY) {
1583             // should not clip while scroll to the bottom of the list.
1584             if (!mNotificationListAtBottomAtTimeOfTouch) {
1585                 return false;
1586             }
1587             float actualNotificationHeight =
1588                     mNotificationView.getHeight() - (event1.getRawY() - event2.getRawY());
1589             if (actualNotificationHeight > mNotificationView.getHeight()) {
1590                 actualNotificationHeight = mNotificationView.getHeight();
1591             }
1592             if (mNotificationView.getHeight() > 0) {
1593                 mPercentageFromBottom = (int) Math.abs(
1594                         actualNotificationHeight / mNotificationView.getHeight() * 100);
1595                 boolean isUp = distanceY > 0;
1596 
1597                 // This check is to figure out if onScroll was called while swiping the card at
1598                 // bottom of the list. At that time we should not allow notification shade to
1599                 // close. We are also checking for the upwards swipe gesture here because it is
1600                 // possible if a user is closing the notification shade and while swiping starts
1601                 // to open again but does not fling. At that time we should allow the
1602                 // notification shade to close fully or else it would stuck in between.
1603                 if (Math.abs(mNotificationView.getHeight() - actualNotificationHeight)
1604                         > SWIPE_DOWN_MIN_DISTANCE && isUp) {
1605                     setNotificationViewClipBounds((int) actualNotificationHeight);
1606                     mIsTracking = true;
1607                 } else if (!isUp) {
1608                     setNotificationViewClipBounds((int) actualNotificationHeight);
1609                 }
1610             }
1611             // if we return true the the items in RV won't be scrollable.
1612             return false;
1613         }
1614 
1615 
1616         @Override
onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY)1617         public boolean onFling(MotionEvent event1, MotionEvent event2,
1618                 float velocityX, float velocityY) {
1619             // should not fling if the touch does not start when view is at the bottom of the list.
1620             if (!mNotificationListAtBottomAtTimeOfTouch) {
1621                 return false;
1622             }
1623             if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH
1624                     || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) {
1625                 // swipe was not vertical or was not fast enough
1626                 return false;
1627             }
1628             boolean isUp = velocityY < 0;
1629             if (isUp) {
1630                 close();
1631                 return true;
1632             } else {
1633                 // we should close the shade
1634                 animateNotificationPanel(velocityY, false);
1635             }
1636             return false;
1637         }
1638 
1639         protected abstract void close();
1640     }
1641 
1642     /**
1643      * To be installed on the nav bars.
1644      */
1645     private abstract class NavBarCloseNotificationGestureListener extends
1646             CloseNotificationGestureListener {
1647         @Override
1648         public boolean onSingleTapUp(MotionEvent e) {
1649             mClosingVelocity = DEFAULT_FLING_VELOCITY;
1650             if (mPanelExpanded) {
1651                 close();
1652             }
1653             return super.onSingleTapUp(e);
1654         }
1655 
1656         @Override
1657         public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
1658                 float distanceY) {
1659             calculatePercentageFromBottom(event2.getRawY());
1660             setNotificationViewClipBounds((int) event2.getRawY());
1661             return true;
1662         }
1663 
1664         @Override
1665         public void onLongPress(MotionEvent e) {
1666             mClosingVelocity = DEFAULT_FLING_VELOCITY;
1667             close();
1668             super.onLongPress(e);
1669         }
1670     }
1671 
1672     /**
1673      * To be installed on the handle bar.
1674      */
1675     private class HandleBarCloseNotificationGestureListener extends
1676             GestureDetector.SimpleOnGestureListener {
1677 
1678         @Override
1679         public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
1680                 float distanceY) {
1681             calculatePercentageFromBottom(event2.getRawY());
1682             // To prevent the jump in the clip bounds while closing the notification shade using
1683             // the handle bar we should calculate the height using the diff of event1 and event2.
1684             // This will help the notification shade to clip smoothly as the event2 value changes
1685             // as event1 value will be fixed.
1686             int clipHeight =
1687                     mNotificationView.getHeight() - (int) (event1.getRawY() - event2.getRawY());
1688             setNotificationViewClipBounds(clipHeight);
1689             return true;
1690         }
1691     }
1692 
1693     /**
1694      * SystemUi version of the notification manager that overrides methods such that the
1695      * notifications end up in the status bar layouts instead of a standalone window.
1696      */
1697     private class CarSystemUIHeadsUpNotificationManager extends CarHeadsUpNotificationManager {
1698 
1699         CarSystemUIHeadsUpNotificationManager(Context context,
1700                 NotificationClickHandlerFactory clickHandlerFactory,
1701                 NotificationDataManager notificationDataManager) {
1702             super(context, clickHandlerFactory, notificationDataManager);
1703         }
1704 
1705         @Override
1706         protected View createHeadsUpPanel() {
1707             // In SystemUi the view is already in the window so just return a reference.
1708             return mStatusBarWindow.findViewById(R.id.notification_headsup);
1709         }
1710 
1711         @Override
1712         protected void addHeadsUpPanelToDisplay() {
1713             // Set the panel initial state to invisible
1714             mHeadsUpPanel.setVisibility(View.INVISIBLE);
1715         }
1716 
1717         @Override
1718         protected void setInternalInsetsInfo(ViewTreeObserver.InternalInsetsInfo info,
1719                 HeadsUpEntry currentNotification, boolean panelExpanded) {
1720             super.setInternalInsetsInfo(info, currentNotification, mPanelExpanded);
1721         }
1722 
1723         @Override
1724         protected void setHeadsUpVisible() {
1725             // if the Notifications panel is showing or SUW for user is in progress then don't show
1726             // heads up notifications
1727             if ((!mEnableHeadsUpNotificationWhenNotificationShadeOpen && mPanelExpanded)
1728                     || !mDeviceIsSetUpForUser || mIsUserSetupInProgress) {
1729                 return;
1730             }
1731 
1732             super.setHeadsUpVisible();
1733             if (mHeadsUpPanel.getVisibility() == View.VISIBLE) {
1734                 mStatusBarWindowController.setHeadsUpShowing(true);
1735                 mStatusBarWindowController.setForceStatusBarVisible(true);
1736             }
1737         }
1738 
1739         @Override
1740         protected void removeNotificationFromPanel(HeadsUpEntry currentHeadsUpNotification) {
1741             super.removeNotificationFromPanel(currentHeadsUpNotification);
1742             // If the panel ended up empty and hidden we can remove it from SystemUi
1743             if (mHeadsUpPanel.getVisibility() != View.VISIBLE) {
1744                 mStatusBarWindowController.setHeadsUpShowing(false);
1745                 mStatusBarWindowController.setForceStatusBarVisible(false);
1746             }
1747         }
1748     }
1749 }
1750