blob: b7aa496b3d5d5331fec253a1c8435f2bb79e87b7 [file] [log] [blame]
Rakesh Iyer1186faa2015-12-07 16:48:46 -08001/*
Brad Stenning8d1a51c2018-11-20 17:34:16 -08002 * Copyright (C) 2018 The Android Open Source Project
Rakesh Iyer1186faa2015-12-07 16:48:46 -08003 *
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
17package com.android.systemui.statusbar.car;
18
Priyank Singhb0241582019-04-19 18:46:26 -070019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ValueAnimator;
Matthew Ng94380652019-04-08 13:43:07 -070022import android.annotation.Nullable;
Brad Stenning3b0d7642019-03-28 11:04:30 -070023import android.app.ActivityManager;
Wale Ogunwale04d9cb52018-04-30 13:55:07 -070024import android.app.ActivityTaskManager;
Brad Stenningd3ede642019-04-23 21:19:27 -070025import android.car.Car;
jovanakedba98c2018-09-14 15:46:24 -070026import android.car.drivingstate.CarDrivingStateEvent;
Brad Stenningd3ede642019-04-23 21:19:27 -070027import android.car.drivingstate.CarUxRestrictionsManager;
Jonathan Koo007ff072019-05-10 09:24:08 -070028import android.car.hardware.power.CarPowerManager.CarPowerStateListener;
Erin Yan8182bfd2019-08-14 12:03:12 -070029import android.car.trust.CarTrustAgentEnrollmentManager;
Brad Stenning2ccc2a42019-04-15 10:17:01 -070030import android.content.Context;
Rakesh Iyer1186faa2015-12-07 16:48:46 -080031import android.graphics.PixelFormat;
Priyank Singhb0241582019-04-19 18:46:26 -070032import android.graphics.Rect;
Anthony Chene658cc22017-04-27 11:17:35 -070033import android.graphics.drawable.Drawable;
Heemin Seog28097c42019-08-15 13:18:41 -070034import android.inputmethodservice.InputMethodService;
35import android.os.IBinder;
Rakesh Iyer74ebabe2016-03-11 10:10:43 -080036import android.util.Log;
Heemin Seog28097c42019-08-15 13:18:41 -070037import android.view.Display;
Brad Stenning3b0d7642019-03-28 11:04:30 -070038import android.view.GestureDetector;
Brad Stenning078235b2017-12-18 08:25:10 -080039import android.view.Gravity;
Brad Stenning3b0d7642019-03-28 11:04:30 -070040import android.view.MotionEvent;
Rakesh Iyer1186faa2015-12-07 16:48:46 -080041import android.view.View;
Anthony Chen0801a5c2017-03-22 09:54:37 -070042import android.view.ViewGroup;
Rakesh Iyer1186faa2015-12-07 16:48:46 -080043import android.view.ViewGroup.LayoutParams;
Priyank Singhb9ab0522019-05-03 15:36:25 -070044import android.view.ViewTreeObserver;
Rakesh Iyer1186faa2015-12-07 16:48:46 -080045import android.view.WindowManager;
Anthony Chene658cc22017-04-27 11:17:35 -070046
Brad Stenning3b0d7642019-03-28 11:04:30 -070047import androidx.annotation.NonNull;
48import androidx.recyclerview.widget.RecyclerView;
49
Brad Stenning2ccc2a42019-04-15 10:17:01 -070050import com.android.car.notification.CarHeadsUpNotificationManager;
Brad Stenning3b0d7642019-03-28 11:04:30 -070051import com.android.car.notification.CarNotificationListener;
52import com.android.car.notification.CarNotificationView;
53import com.android.car.notification.CarUxRestrictionManagerWrapper;
Brad Stenning2ccc2a42019-04-15 10:17:01 -070054import com.android.car.notification.HeadsUpEntry;
Brad Stenning3b0d7642019-03-28 11:04:30 -070055import com.android.car.notification.NotificationClickHandlerFactory;
Brad Stenning2ccc2a42019-04-15 10:17:01 -070056import com.android.car.notification.NotificationDataManager;
Brad Stenning3b0d7642019-03-28 11:04:30 -070057import com.android.car.notification.NotificationViewController;
58import com.android.car.notification.PreprocessingManager;
Matthew Ng94380652019-04-08 13:43:07 -070059import com.android.internal.statusbar.RegisterStatusBarResult;
Winson Chung2db35572017-10-09 15:08:30 -070060import com.android.keyguard.KeyguardUpdateMonitor;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070061import com.android.systemui.BatteryMeterView;
Jason Monk27d01a622018-12-10 15:57:09 -050062import com.android.systemui.CarSystemUIFactory;
Jason Monk9c7844c2017-01-18 15:21:53 -050063import com.android.systemui.Dependency;
Winson Chung2db35572017-10-09 15:08:30 -070064import com.android.systemui.Prefs;
Rakesh Iyer1186faa2015-12-07 16:48:46 -080065import com.android.systemui.R;
Jason Monk27d01a622018-12-10 15:57:09 -050066import com.android.systemui.SystemUIFactory;
Winson Chung2db35572017-10-09 15:08:30 -070067import com.android.systemui.classifier.FalsingLog;
Jason Monkaa573e92017-01-27 17:00:29 -050068import com.android.systemui.fragments.FragmentHostManager;
jovanak622def22019-06-11 11:51:54 -070069import com.android.systemui.keyguard.ScreenLifecycle;
Dave Mankoff781ef7e2019-06-28 16:33:25 -040070import com.android.systemui.plugins.FalsingManager;
Brad Stenning8d1a51c2018-11-20 17:34:16 -080071import com.android.systemui.plugins.qs.QS;
72import com.android.systemui.qs.car.CarQSFragment;
Winson Chungb05b3982017-11-01 18:02:43 -070073import com.android.systemui.shared.system.ActivityManagerWrapper;
Winson Chung67f5c8b2018-09-24 12:09:19 -070074import com.android.systemui.shared.system.TaskStackChangeListener;
Priyank Singhb0241582019-04-19 18:46:26 -070075import com.android.systemui.statusbar.FlingAnimationUtils;
Rakesh Iyer2790a372016-01-22 15:33:39 -080076import com.android.systemui.statusbar.StatusBarState;
Brad Stenning38b46f82018-03-27 13:57:29 -070077import com.android.systemui.statusbar.car.hvac.HvacController;
Brad Stenningf084d882018-08-08 13:34:21 -070078import com.android.systemui.statusbar.car.hvac.TemperatureView;
Jason Monkaa573e92017-01-27 17:00:29 -050079import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
Jason Monk2a6ea9c2017-01-26 11:14:51 -050080import com.android.systemui.statusbar.phone.StatusBar;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070081import com.android.systemui.statusbar.policy.BatteryController;
Brad Stenning224b5b32018-03-28 21:26:57 -070082import com.android.systemui.statusbar.policy.DeviceProvisionedController;
Jason Monk9c7844c2017-01-18 15:21:53 -050083import com.android.systemui.statusbar.policy.UserSwitcherController;
Rakesh Iyer1186faa2015-12-07 16:48:46 -080084
Qiming Shib9dfb922017-04-13 16:23:18 +080085import java.io.FileDescriptor;
86import java.io.PrintWriter;
87import java.util.Map;
davidln54739152018-07-02 13:25:44 -070088
Rakesh Iyer1186faa2015-12-07 16:48:46 -080089/**
90 * A status bar (and navigation bar) tailored for the automotive use case.
91 */
Heemin Seog28097c42019-08-15 13:18:41 -070092public class CarStatusBar extends StatusBar implements CarBatteryController.BatteryViewHandler {
Rakesh Iyer74ebabe2016-03-11 10:10:43 -080093 private static final String TAG = "CarStatusBar";
Priyank Singhb0241582019-04-19 18:46:26 -070094 // used to calculate how fast to open or close the window
95 private static final float DEFAULT_FLING_VELOCITY = 0;
96 // max time a fling animation takes
97 private static final float FLING_ANIMATION_MAX_TIME = 0.5f;
98 // acceleration rate for the fling animation
99 private static final float FLING_SPEED_UP_FACTOR = 0.6f;
100
101 private float mOpeningVelocity = DEFAULT_FLING_VELOCITY;
102 private float mClosingVelocity = DEFAULT_FLING_VELOCITY;
Rakesh Iyer74ebabe2016-03-11 10:10:43 -0800103
Victor Chan1c6d0582016-01-09 16:26:37 -0800104 private TaskStackListenerImpl mTaskStackListener;
Victor Chan1c6d0582016-01-09 16:26:37 -0800105
Rakesh Iyer2790a372016-01-22 15:33:39 -0800106 private FullscreenUserSwitcher mFullscreenUserSwitcher;
Victor Chan1c6d0582016-01-09 16:26:37 -0800107
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700108 private CarBatteryController mCarBatteryController;
109 private BatteryMeterView mBatteryMeterView;
Anthony Chene658cc22017-04-27 11:17:35 -0700110 private Drawable mNotificationPanelBackground;
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700111
Heemin Seog1a13af52019-10-03 16:15:50 -0700112 private ViewGroup mTopNavigationBarContainer;
Scott Randolphf4085822017-06-27 15:23:01 -0700113 private ViewGroup mNavigationBarWindow;
Brad Stenning078235b2017-12-18 08:25:10 -0800114 private ViewGroup mLeftNavigationBarWindow;
115 private ViewGroup mRightNavigationBarWindow;
Heemin Seog1a13af52019-10-03 16:15:50 -0700116 private CarNavigationBarView mTopNavigationBarView;
Jason Monk49fa0162017-01-11 09:21:56 -0500117 private CarNavigationBarView mNavigationBarView;
Brad Stenning078235b2017-12-18 08:25:10 -0800118 private CarNavigationBarView mLeftNavigationBarView;
119 private CarNavigationBarView mRightNavigationBarView;
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700120
Qiming Shib9dfb922017-04-13 16:23:18 +0800121 private final Object mQueueLock = new Object();
Brad Stenning078235b2017-12-18 08:25:10 -0800122 private boolean mShowLeft;
123 private boolean mShowRight;
124 private boolean mShowBottom;
125 private CarFacetButtonController mCarFacetButtonController;
Brad Stenning224b5b32018-03-28 21:26:57 -0700126 private ActivityManagerWrapper mActivityManagerWrapper;
127 private DeviceProvisionedController mDeviceProvisionedController;
Heemin Seog1a13af52019-10-03 16:15:50 -0700128 private boolean mDeviceIsSetUpForUser = true;
Brad Stenningf084d882018-08-08 13:34:21 -0700129 private HvacController mHvacController;
jovanakedba98c2018-09-14 15:46:24 -0700130 private DrivingStateHelper mDrivingStateHelper;
Jonathan Koo007ff072019-05-10 09:24:08 -0700131 private PowerManagerHelper mPowerManagerHelper;
132 private FlingAnimationUtils mFlingAnimationUtils;
jovanakedba98c2018-09-14 15:46:24 -0700133 private SwitchToGuestTimer mSwitchToGuestTimer;
Jonathan Koo007ff072019-05-10 09:24:08 -0700134 private NotificationDataManager mNotificationDataManager;
135 private NotificationClickHandlerFactory mNotificationClickHandlerFactory;
jovanak622def22019-06-11 11:51:54 -0700136 private ScreenLifecycle mScreenLifecycle;
Brad Stenning078235b2017-12-18 08:25:10 -0800137
Brad Stenning3b0d7642019-03-28 11:04:30 -0700138 // The container for the notifications.
139 private CarNotificationView mNotificationView;
140 private RecyclerView mNotificationList;
Priyank Singh647f9e62019-05-16 15:19:53 -0700141 // The handler bar view at the bottom of notification shade.
142 private View mHandleBar;
Adora Zhangdaea9112019-04-11 17:47:40 -0700143 // The controller for the notification view.
144 private NotificationViewController mNotificationViewController;
Brad Stenning3b0d7642019-03-28 11:04:30 -0700145 // The state of if the notification list is currently showing the bottom.
146 private boolean mNotificationListAtBottom;
147 // Was the notification list at the bottom when the user first touched the screen
148 private boolean mNotificationListAtBottomAtTimeOfTouch;
Heemin Seog1a13af52019-10-03 16:15:50 -0700149 // To be attached to the top navigation bar (i.e. status bar) to pull down the notification
150 // panel.
151 private View.OnTouchListener mTopNavBarNotificationTouchListener;
Brad Stenning3b0d7642019-03-28 11:04:30 -0700152 // To be attached to the navigation bars such that they can close the notification panel if
153 // it's open.
154 private View.OnTouchListener mNavBarNotificationTouchListener;
155
Priyank Singhb0241582019-04-19 18:46:26 -0700156 // Percentage from top of the screen after which the notification shade will open. This value
157 // will be used while opening the notification shade.
158 private int mSettleOpenPercentage;
159 // Percentage from top of the screen below which the notification shade will close. This
160 // value will be used while closing the notification shade.
161 private int mSettleClosePercentage;
162 // Percentage of notification shade open from top of the screen.
163 private int mPercentageFromBottom;
164 // If notification shade is animation to close or to open.
165 private boolean mIsNotificationAnimating;
166
167 // Tracks when the notification shade is being scrolled. This refers to the glass pane being
168 // scrolled not the recycler view.
169 private boolean mIsTracking;
170 private float mFirstTouchDownOnGlassPane;
171
172 // If the notification card inside the recycler view is being swiped.
173 private boolean mIsNotificationCardSwiping;
174 // If notification shade is being swiped vertically to close.
175 private boolean mIsSwipingVerticallyToClose;
Priyank Singh8bd920f2019-06-04 15:21:46 -0700176 // Whether heads-up notifications should be shown when shade is open.
177 private boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen;
Heemin Seog28097c42019-08-15 13:18:41 -0700178 // If the nav bar should be hidden when the soft keyboard is visible.
179 private boolean mHideNavBarForKeyboard;
180 private boolean mBottomNavBarVisible;
Priyank Singhb0241582019-04-19 18:46:26 -0700181
Jonathan Koo007ff072019-05-10 09:24:08 -0700182 private final CarPowerStateListener mCarPowerStateListener =
183 (int state) -> {
184 // When the car powers on, clear all notifications and mute/unread states.
185 Log.d(TAG, "New car power state: " + state);
186 if (state == CarPowerStateListener.ON) {
187 if (mNotificationClickHandlerFactory != null) {
188 mNotificationClickHandlerFactory.clearAllNotifications();
189 }
190 if (mNotificationDataManager != null) {
191 mNotificationDataManager.clearAll();
192 }
193 }
194 };
195
Victor Chan1c6d0582016-01-09 16:26:37 -0800196 @Override
197 public void start() {
Brad Stenningeb6051c2019-02-27 13:46:11 -0800198 // get the provisioned state before calling the parent class since it's that flow that
199 // builds the nav bar
200 mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
Heemin Seog1a13af52019-10-03 16:15:50 -0700201 mDeviceIsSetUpForUser = mDeviceProvisionedController.isCurrentUserSetup();
Heemin Seog28097c42019-08-15 13:18:41 -0700202
203 // Keyboard related setup, before nav bars are created.
204 mHideNavBarForKeyboard = mContext.getResources().getBoolean(
205 com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard);
206 mBottomNavBarVisible = false;
207
jovanak504e7532019-08-19 16:32:50 -0700208 // Need to initialize screen lifecycle before calling super.start - before switcher is
209 // created.
210 mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
211 mScreenLifecycle.addObserver(mScreenObserver);
212
Heemin Seog1a13af52019-10-03 16:15:50 -0700213 // Need to initialize HVAC controller before calling super.start - before system bars are
214 // created.
215 mHvacController = new HvacController(mContext);
216
Victor Chan1c6d0582016-01-09 16:26:37 -0800217 super.start();
Jaewan Kim938a50b2016-03-14 17:35:43 +0900218 mTaskStackListener = new TaskStackListenerImpl();
Brad Stenning224b5b32018-03-28 21:26:57 -0700219 mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
220 mActivityManagerWrapper.registerTaskStackListener(mTaskStackListener);
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700221
Jason Monke59dc402018-08-16 12:05:01 -0400222 mNotificationPanel.setScrollingEnabled(true);
Priyank Singhb0241582019-04-19 18:46:26 -0700223 mSettleOpenPercentage = mContext.getResources().getInteger(
224 R.integer.notification_settle_open_percentage);
225 mSettleClosePercentage = mContext.getResources().getInteger(
226 R.integer.notification_settle_close_percentage);
Jonathan Koo007ff072019-05-10 09:24:08 -0700227 mFlingAnimationUtils = new FlingAnimationUtils(mContext,
Priyank Singhb0241582019-04-19 18:46:26 -0700228 FLING_ANIMATION_MAX_TIME, FLING_SPEED_UP_FACTOR);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800229
Rakesh Iyer545e47d2017-01-23 18:10:21 -0800230 createBatteryController();
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700231 mCarBatteryController.startListening();
Brad Stenning38b46f82018-03-27 13:57:29 -0700232
Brad Stenningf084d882018-08-08 13:34:21 -0700233 mHvacController.connectToCarService();
davidln54739152018-07-02 13:25:44 -0700234
Heemin Seog1a13af52019-10-03 16:15:50 -0700235 mDeviceProvisionedController.addCallback(
236 new DeviceProvisionedController.DeviceProvisionedListener() {
237 @Override
238 public void onUserSetupChanged() {
239 mHandler.post(() -> restartNavBarsIfNecessary());
240 }
241
242 @Override
243 public void onUserSwitched() {
244 mHandler.post(() -> restartNavBarsIfNecessary());
245 }
246 });
jovanakedba98c2018-09-14 15:46:24 -0700247
248 // Register a listener for driving state changes.
249 mDrivingStateHelper = new DrivingStateHelper(mContext, this::onDrivingStateChanged);
250 mDrivingStateHelper.connectToCarService();
251
Jonathan Koo007ff072019-05-10 09:24:08 -0700252 mPowerManagerHelper = new PowerManagerHelper(mContext, mCarPowerStateListener);
253 mPowerManagerHelper.connectToCarService();
254
jovanakedba98c2018-09-14 15:46:24 -0700255 mSwitchToGuestTimer = new SwitchToGuestTimer(mContext);
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700256 }
257
Heemin Seog1a13af52019-10-03 16:15:50 -0700258 private void restartNavBarsIfNecessary() {
259 boolean currentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
260 if (mDeviceIsSetUpForUser != currentUserSetup) {
261 mDeviceIsSetUpForUser = currentUserSetup;
262 restartNavBars();
263 }
264 }
265
Brad Stenning224b5b32018-03-28 21:26:57 -0700266 /**
267 * Remove all content from navbars and rebuild them. Used to allow for different nav bars
Guobin Zhangeef0e0c2019-02-01 11:54:24 -0800268 * before and after the device is provisioned. . Also for change of density and font size.
Brad Stenning224b5b32018-03-28 21:26:57 -0700269 */
270 private void restartNavBars() {
Brad Stenningf084d882018-08-08 13:34:21 -0700271 // remove and reattach all hvac components such that we don't keep a reference to unused
272 // ui elements
273 mHvacController.removeAllComponents();
Brad Stenning224b5b32018-03-28 21:26:57 -0700274 mCarFacetButtonController.removeAll();
Heemin Seog1a13af52019-10-03 16:15:50 -0700275
Brad Stenning224b5b32018-03-28 21:26:57 -0700276 if (mNavigationBarWindow != null) {
277 mNavigationBarWindow.removeAllViews();
278 mNavigationBarView = null;
279 }
280
281 if (mLeftNavigationBarWindow != null) {
282 mLeftNavigationBarWindow.removeAllViews();
283 mLeftNavigationBarView = null;
284 }
285
286 if (mRightNavigationBarWindow != null) {
287 mRightNavigationBarWindow.removeAllViews();
288 mRightNavigationBarView = null;
289 }
Brad Stenningf084d882018-08-08 13:34:21 -0700290
Brad Stenning224b5b32018-03-28 21:26:57 -0700291 buildNavBarContent();
Brad Stenning75221642019-03-13 07:33:24 -0700292 // If the UI was rebuilt (day/night change) while the keyguard was up we need to
293 // correctly respect that state.
294 if (mIsKeyguard) {
295 updateNavBarForKeyguardContent();
296 }
Brad Stenning4534f442019-04-01 21:43:32 -0700297 // CarFacetButtonController was reset therefore we need to re-add the status bar elements
298 // to the controller.
299 mCarFacetButtonController.addAllFacetButtons(mStatusBarWindow);
Brad Stenning224b5b32018-03-28 21:26:57 -0700300 }
301
Brad Stenningf084d882018-08-08 13:34:21 -0700302 private void addTemperatureViewToController(View v) {
303 if (v instanceof TemperatureView) {
Brad Stenningf084d882018-08-08 13:34:21 -0700304 mHvacController.addHvacTextView((TemperatureView) v);
305 } else if (v instanceof ViewGroup) {
306 ViewGroup viewGroup = (ViewGroup) v;
307 for (int i = 0; i < viewGroup.getChildCount(); i++) {
308 addTemperatureViewToController(viewGroup.getChildAt(i));
309 }
310 }
311 }
312
Brad Stenning7e411812018-04-13 22:52:39 -0700313 /**
314 * Allows for showing or hiding just the navigation bars. This is indented to be used when
315 * the full screen user selector is shown.
316 */
davidln54739152018-07-02 13:25:44 -0700317 void setNavBarVisibility(@View.Visibility int visibility) {
Brad Stenning7e411812018-04-13 22:52:39 -0700318 if (mNavigationBarWindow != null) {
319 mNavigationBarWindow.setVisibility(visibility);
320 }
321 if (mLeftNavigationBarWindow != null) {
322 mLeftNavigationBarWindow.setVisibility(visibility);
323 }
324 if (mRightNavigationBarWindow != null) {
325 mRightNavigationBarWindow.setVisibility(visibility);
326 }
327 }
328
329
330 @Override
331 public boolean hideKeyguard() {
332 boolean result = super.hideKeyguard();
333 if (mNavigationBarView != null) {
334 mNavigationBarView.hideKeyguardButtons();
335 }
336 if (mLeftNavigationBarView != null) {
337 mLeftNavigationBarView.hideKeyguardButtons();
338 }
339 if (mRightNavigationBarView != null) {
340 mRightNavigationBarView.hideKeyguardButtons();
341 }
342 return result;
343 }
344
Brad Stenning7e411812018-04-13 22:52:39 -0700345 @Override
346 public void showKeyguard() {
347 super.showKeyguard();
Brad Stenning75221642019-03-13 07:33:24 -0700348 updateNavBarForKeyguardContent();
349 }
350
351 /**
352 * Switch to the keyguard applicable content contained in the nav bars
353 */
354 private void updateNavBarForKeyguardContent() {
Brad Stenning7e411812018-04-13 22:52:39 -0700355 if (mNavigationBarView != null) {
356 mNavigationBarView.showKeyguardButtons();
357 }
358 if (mLeftNavigationBarView != null) {
359 mLeftNavigationBarView.showKeyguardButtons();
360 }
361 if (mRightNavigationBarView != null) {
362 mRightNavigationBarView.showKeyguardButtons();
363 }
364 }
Brad Stenning224b5b32018-03-28 21:26:57 -0700365
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700366 @Override
Matthew Ng94380652019-04-08 13:43:07 -0700367 protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
368 super.makeStatusBarView(result);
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700369
Guobin Zhangeef0e0c2019-02-01 11:54:24 -0800370 CarSystemUIFactory factory = SystemUIFactory.getInstance();
371 mCarFacetButtonController = factory.getCarDependencyComponent()
davidln1ceedb52019-05-30 14:25:01 -0700372 .getCarFacetButtonController();
Anthony Chene658cc22017-04-27 11:17:35 -0700373 mNotificationPanelBackground = getDefaultWallpaper();
374 mScrimController.setScrimBehindDrawable(mNotificationPanelBackground);
375
Jason Monkaa573e92017-01-27 17:00:29 -0500376 FragmentHostManager manager = FragmentHostManager.get(mStatusBarWindow);
377 manager.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
Anthony Chene658cc22017-04-27 11:17:35 -0700378 mBatteryMeterView = fragment.getView().findViewById(R.id.battery);
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700379
Jason Monkaa573e92017-01-27 17:00:29 -0500380 // By default, the BatteryMeterView should not be visible. It will be toggled
381 // when a device has connected by bluetooth.
382 mBatteryMeterView.setVisibility(View.GONE);
Jason Monkaa573e92017-01-27 17:00:29 -0500383 });
Brad Stenning3b0d7642019-03-28 11:04:30 -0700384
385 connectNotificationsUI();
386 }
387
388 /**
389 * Attach the notification listeners and controllers to the UI as well as build all the
390 * touch listeners needed for opening and closing the notification panel
391 */
392 private void connectNotificationsUI() {
Heemin Seog1a13af52019-10-03 16:15:50 -0700393 // Attached to the top navigation bar (i.e. status bar) to detect pull down of the
394 // notification shade.
Brad Stenning3b0d7642019-03-28 11:04:30 -0700395 GestureDetector openGestureDetector = new GestureDetector(mContext,
396 new OpenNotificationGestureListener() {
397 @Override
398 protected void openNotification() {
399 animateExpandNotificationsPanel();
400 }
401 });
402 // Attached to the notification ui to detect close request of the notification shade.
403 GestureDetector closeGestureDetector = new GestureDetector(mContext,
404 new CloseNotificationGestureListener() {
405 @Override
406 protected void close() {
Brad Stenning44d39072019-04-20 11:53:17 -0700407 if (mPanelExpanded) {
408 animateCollapsePanels();
409 }
Brad Stenning3b0d7642019-03-28 11:04:30 -0700410 }
411 });
412 // Attached to the NavBars to close the notification shade
413 GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(mContext,
414 new NavBarCloseNotificationGestureListener() {
415 @Override
416 protected void close() {
Brad Stenning44d39072019-04-20 11:53:17 -0700417 if (mPanelExpanded) {
418 animateCollapsePanels();
419 }
Brad Stenning3b0d7642019-03-28 11:04:30 -0700420 }
421 });
Priyank Singha47b6f72019-05-28 13:02:47 -0700422
423 // Attached to the Handle bar to close the notification shade
424 GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext,
425 new HandleBarCloseNotificationGestureListener());
426
Heemin Seog1a13af52019-10-03 16:15:50 -0700427 mTopNavBarNotificationTouchListener = (v, event) -> {
428 if (!mDeviceIsSetUpForUser) {
429 return true;
430 }
431 boolean consumed = openGestureDetector.onTouchEvent(event);
432 if (consumed) {
433 return true;
434 }
435 maybeCompleteAnimation(event);
436 return true;
437 };
438
Brad Stenning3b0d7642019-03-28 11:04:30 -0700439 mNavBarNotificationTouchListener =
Priyank Singhb0241582019-04-19 18:46:26 -0700440 (v, event) -> {
441 boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
442 if (consumed) {
443 return true;
444 }
Priyank Singha47b6f72019-05-28 13:02:47 -0700445 maybeCompleteAnimation(event);
Priyank Singhb0241582019-04-19 18:46:26 -0700446 return true;
447 };
Brad Stenning3b0d7642019-03-28 11:04:30 -0700448
Jonathan Koo007ff072019-05-10 09:24:08 -0700449 mNotificationClickHandlerFactory = new NotificationClickHandlerFactory(
Brad Stenning3b0d7642019-03-28 11:04:30 -0700450 mBarService,
451 launchResult -> {
452 if (launchResult == ActivityManager.START_TASK_TO_FRONT
453 || launchResult == ActivityManager.START_SUCCESS) {
454 animateCollapsePanels();
455 }
456 });
Brad Stenningd3ede642019-04-23 21:19:27 -0700457 Car car = Car.createCar(mContext);
458 CarUxRestrictionsManager carUxRestrictionsManager = (CarUxRestrictionsManager)
459 car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
Brad Stenning3b0d7642019-03-28 11:04:30 -0700460 CarNotificationListener carNotificationListener = new CarNotificationListener();
461 CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper =
462 new CarUxRestrictionManagerWrapper();
Brad Stenningd3ede642019-04-23 21:19:27 -0700463 carUxRestrictionManagerWrapper.setCarUxRestrictionsManager(carUxRestrictionsManager);
Jonathan Kooc9f71aa2019-05-06 08:20:14 -0700464
Jonathan Koo007ff072019-05-10 09:24:08 -0700465 mNotificationDataManager = new NotificationDataManager();
466 mNotificationDataManager.setOnUnseenCountUpdateListener(
Jonathan Kooc9f71aa2019-05-06 08:20:14 -0700467 () -> {
Jonathan Koo307bda92019-05-14 16:53:48 -0700468 if (mNavigationBarView != null && mNotificationDataManager != null) {
469 Boolean hasUnseen =
470 mNotificationDataManager.getUnseenNotificationCount() > 0;
471 if (mNavigationBarView != null) {
472 mNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
473 }
474
475 if (mLeftNavigationBarView != null) {
476 mLeftNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
477 }
478
479 if (mRightNavigationBarView != null) {
480 mRightNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
481 }
482 }
Jonathan Kooc9f71aa2019-05-06 08:20:14 -0700483 });
484
Priyank Singh8bd920f2019-06-04 15:21:46 -0700485 mEnableHeadsUpNotificationWhenNotificationShadeOpen = mContext.getResources().getBoolean(
486 R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen);
Brad Stenning2ccc2a42019-04-15 10:17:01 -0700487 CarHeadsUpNotificationManager carHeadsUpNotificationManager =
Jonathan Koo007ff072019-05-10 09:24:08 -0700488 new CarSystemUIHeadsUpNotificationManager(mContext,
489 mNotificationClickHandlerFactory, mNotificationDataManager);
490 mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
Brad Stenning2ccc2a42019-04-15 10:17:01 -0700491
Brad Stenning3b0d7642019-03-28 11:04:30 -0700492 carNotificationListener.registerAsSystemService(mContext, carUxRestrictionManagerWrapper,
Jonathan Koo007ff072019-05-10 09:24:08 -0700493 carHeadsUpNotificationManager, mNotificationDataManager);
Brad Stenning3b0d7642019-03-28 11:04:30 -0700494
495 mNotificationView = mStatusBarWindow.findViewById(R.id.notification_view);
496 View glassPane = mStatusBarWindow.findViewById(R.id.glass_pane);
Priyank Singh647f9e62019-05-16 15:19:53 -0700497 mHandleBar = mStatusBarWindow.findViewById(R.id.handle_bar);
Jonathan Koo007ff072019-05-10 09:24:08 -0700498 mNotificationView.setClickHandlerFactory(mNotificationClickHandlerFactory);
499 mNotificationView.setNotificationDataManager(mNotificationDataManager);
Brad Stenning3b0d7642019-03-28 11:04:30 -0700500
501 // The glass pane is used to view touch events before passed to the notification list.
502 // This allows us to initialize gesture listeners and detect when to close the notifications
503 glassPane.setOnTouchListener((v, event) -> {
504 if (event.getActionMasked() == MotionEvent.ACTION_UP) {
505 mNotificationListAtBottomAtTimeOfTouch = false;
506 }
507 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
Priyank Singhb0241582019-04-19 18:46:26 -0700508 mFirstTouchDownOnGlassPane = event.getRawX();
Brad Stenning3b0d7642019-03-28 11:04:30 -0700509 mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom;
Priyank Singhb0241582019-04-19 18:46:26 -0700510 // Reset the tracker when there is a touch down on the glass pane.
511 mIsTracking = false;
Brad Stenning3b0d7642019-03-28 11:04:30 -0700512 // Pass the down event to gesture detector so that it knows where the touch event
513 // started.
514 closeGestureDetector.onTouchEvent(event);
515 }
516 return false;
517 });
Priyank Singha47b6f72019-05-28 13:02:47 -0700518
519 mHandleBar.setOnTouchListener((v, event) -> {
520 handleBarCloseNotificationGestureDetector.onTouchEvent(event);
521 maybeCompleteAnimation(event);
522 return true;
523 });
524
Brad Stenning3b0d7642019-03-28 11:04:30 -0700525 mNotificationList = mNotificationView.findViewById(R.id.notifications);
526 mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() {
527 @Override
528 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
529 super.onScrolled(recyclerView, dx, dy);
530 if (!mNotificationList.canScrollVertically(1)) {
531 mNotificationListAtBottom = true;
532 return;
533 }
534 mNotificationListAtBottom = false;
Priyank Singhb0241582019-04-19 18:46:26 -0700535 mIsSwipingVerticallyToClose = false;
Brad Stenning3b0d7642019-03-28 11:04:30 -0700536 mNotificationListAtBottomAtTimeOfTouch = false;
537 }
538 });
539 mNotificationList.setOnTouchListener(new View.OnTouchListener() {
540 @Override
541 public boolean onTouch(View v, MotionEvent event) {
Priyank Singhb0241582019-04-19 18:46:26 -0700542 mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX())
543 > SWIPE_MAX_OFF_PATH;
544 if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) {
545 // We need to save the state here as if notification card is swiping we will
546 // change the mNotificationListAtBottomAtTimeOfTouch. This is to protect
547 // closing the notification shade while the notification card is being swiped.
548 mIsSwipingVerticallyToClose = true;
549 }
550
551 // If the card is swiping we should not allow the notification shade to close.
552 // Hence setting mNotificationListAtBottomAtTimeOfTouch to false will stop that
553 // for us. We are also checking for mIsTracking because while swiping the
554 // notification shade to close if the user goes a bit horizontal while swiping
555 // upwards then also this should close.
556 if (mIsNotificationCardSwiping && !mIsTracking) {
557 mNotificationListAtBottomAtTimeOfTouch = false;
558 }
559
Priyank Singhbae09ae2019-04-25 18:17:02 -0700560 boolean handled = closeGestureDetector.onTouchEvent(event);
Priyank Singhb0241582019-04-19 18:46:26 -0700561 boolean isTracking = mIsTracking;
Priyank Singh647f9e62019-05-16 15:19:53 -0700562 Rect rect = mNotificationView.getClipBounds();
Priyank Singhf3912862019-04-22 10:41:58 -0700563 float clippedHeight = 0;
564 if (rect != null) {
565 clippedHeight = rect.bottom;
566 }
Priyank Singhb0241582019-04-19 18:46:26 -0700567 if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP
568 && mIsSwipingVerticallyToClose) {
569 if (mSettleClosePercentage < mPercentageFromBottom && isTracking) {
570 animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
571 } else if (clippedHeight != mNotificationView.getHeight() && isTracking) {
572 // this can be caused when user is at the end of the list and trying to
573 // fling to top of the list by scrolling down.
574 animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
575 }
576 }
577
Brad Stenning3b0d7642019-03-28 11:04:30 -0700578 // Updating the mNotificationListAtBottomAtTimeOfTouch state has to be done after
579 // the event has been passed to the closeGestureDetector above, such that the
580 // closeGestureDetector sees the up event before the state has changed.
581 if (event.getActionMasked() == MotionEvent.ACTION_UP) {
582 mNotificationListAtBottomAtTimeOfTouch = false;
583 }
Priyank Singhb0241582019-04-19 18:46:26 -0700584 return handled || isTracking;
Brad Stenning3b0d7642019-03-28 11:04:30 -0700585 }
586 });
587
Adora Zhangdaea9112019-04-11 17:47:40 -0700588 mNotificationViewController = new NotificationViewController(
Brad Stenning3b0d7642019-03-28 11:04:30 -0700589 mNotificationView,
590 PreprocessingManager.getInstance(mContext),
591 carNotificationListener,
Jonathan Kooc9f71aa2019-05-06 08:20:14 -0700592 carUxRestrictionManagerWrapper,
Jonathan Koo007ff072019-05-10 09:24:08 -0700593 mNotificationDataManager);
Brad Stenning3b0d7642019-03-28 11:04:30 -0700594 mNotificationViewController.enable();
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700595 }
596
Brad Stenning44d39072019-04-20 11:53:17 -0700597 /**
598 * @return true if the notification panel is currently visible
599 */
600 boolean isNotificationPanelOpen() {
601 return mPanelExpanded;
602 }
603
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800604 @Override
Brad Stenning3b0d7642019-03-28 11:04:30 -0700605 public void animateExpandNotificationsPanel() {
606 if (!mCommandQueue.panelsEnabled() || !mUserSetup) {
607 return;
608 }
609 // scroll to top
610 mNotificationList.scrollToPosition(0);
611 mStatusBarWindowController.setPanelVisible(true);
612 mNotificationView.setVisibility(View.VISIBLE);
Priyank Singhb0241582019-04-19 18:46:26 -0700613 animateNotificationPanel(mOpeningVelocity, false);
614
Brad Stenning3b0d7642019-03-28 11:04:30 -0700615 setPanelExpanded(true);
616 }
617
618 @Override
619 public void animateCollapsePanels(int flags, boolean force, boolean delayed,
620 float speedUpFactor) {
621 super.animateCollapsePanels(flags, force, delayed, speedUpFactor);
622 if (!mPanelExpanded || mNotificationView.getVisibility() == View.INVISIBLE) {
623 return;
624 }
625 mStatusBarWindowController.setStatusBarFocusable(false);
626 mStatusBarWindow.cancelExpandHelper();
627 mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor);
Priyank Singhb0241582019-04-19 18:46:26 -0700628
629 animateNotificationPanel(mClosingVelocity, true);
630
631 if (!mIsTracking) {
632 mStatusBarWindowController.setPanelVisible(false);
633 mNotificationView.setVisibility(View.INVISIBLE);
634 }
635
Brad Stenning3b0d7642019-03-28 11:04:30 -0700636 setPanelExpanded(false);
637 }
638
Priyank Singha47b6f72019-05-28 13:02:47 -0700639 private void maybeCompleteAnimation(MotionEvent event) {
640 if (event.getActionMasked() == MotionEvent.ACTION_UP
641 && mNotificationView.getVisibility() == View.VISIBLE) {
642 if (mSettleClosePercentage < mPercentageFromBottom) {
643 animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
644 } else {
645 animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
646 }
647 }
648 }
649
Priyank Singhb0241582019-04-19 18:46:26 -0700650 /**
651 * Animates the notification shade from one position to other. This is used to either open or
Priyank Singh245bd122019-04-21 15:42:44 -0700652 * close the notification shade completely with a velocity. If the animation is to close the
Priyank Singhb0241582019-04-19 18:46:26 -0700653 * notification shade this method also makes the view invisible after animation ends.
654 */
655 private void animateNotificationPanel(float velocity, boolean isClosing) {
Priyank Singhb0241582019-04-19 18:46:26 -0700656 float to = 0;
657 if (!isClosing) {
658 to = mNotificationView.getHeight();
659 }
Priyank Singhb9ab0522019-05-03 15:36:25 -0700660
Priyank Singh647f9e62019-05-16 15:19:53 -0700661 Rect rect = mNotificationView.getClipBounds();
Priyank Singhb9ab0522019-05-03 15:36:25 -0700662 if (rect != null) {
663 float from = rect.bottom;
664 animate(from, to, velocity, isClosing);
665 return;
666 }
667
668 // We will only be here if the shade is being opened programmatically.
669 ViewTreeObserver notificationTreeObserver = mNotificationView.getViewTreeObserver();
670 notificationTreeObserver.addOnGlobalLayoutListener(
671 new ViewTreeObserver.OnGlobalLayoutListener() {
672 @Override
673 public void onGlobalLayout() {
674 ViewTreeObserver obs = mNotificationView.getViewTreeObserver();
675 obs.removeOnGlobalLayoutListener(this);
676 float to = mNotificationView.getHeight();
677 animate(/* from= */ 0, to, velocity, isClosing);
678 }
679 });
680 }
681
682 private void animate(float from, float to, float velocity, boolean isClosing) {
Priyank Singhb0241582019-04-19 18:46:26 -0700683 if (mIsNotificationAnimating) {
684 return;
685 }
686 mIsNotificationAnimating = true;
687 mIsTracking = true;
688 ValueAnimator animator = ValueAnimator.ofFloat(from, to);
689 animator.addUpdateListener(
690 animation -> {
691 float animatedValue = (Float) animation.getAnimatedValue();
692 setNotificationViewClipBounds((int) animatedValue);
693 });
694 animator.addListener(new AnimatorListenerAdapter() {
695 @Override
696 public void onAnimationEnd(Animator animation) {
697 super.onAnimationEnd(animation);
698 mIsNotificationAnimating = false;
699 mIsTracking = false;
700 mOpeningVelocity = DEFAULT_FLING_VELOCITY;
701 mClosingVelocity = DEFAULT_FLING_VELOCITY;
702 if (isClosing) {
703 mStatusBarWindowController.setPanelVisible(false);
704 mNotificationView.setVisibility(View.INVISIBLE);
Priyank Singh647f9e62019-05-16 15:19:53 -0700705 mNotificationView.setClipBounds(null);
Adora Zhangdaea9112019-04-11 17:47:40 -0700706 mNotificationViewController.setIsInForeground(false);
Priyank Singhb0241582019-04-19 18:46:26 -0700707 // let the status bar know that the panel is closed
708 setPanelExpanded(false);
709 } else {
Adora Zhangdaea9112019-04-11 17:47:40 -0700710 mNotificationViewController.setIsInForeground(true);
Priyank Singhb0241582019-04-19 18:46:26 -0700711 // let the status bar know that the panel is open
Jonathan Kooc9f71aa2019-05-06 08:20:14 -0700712 mNotificationView.setVisibleNotificationsAsSeen();
Priyank Singhb0241582019-04-19 18:46:26 -0700713 setPanelExpanded(true);
714 }
715 }
716 });
Jonathan Koo007ff072019-05-10 09:24:08 -0700717 mFlingAnimationUtils.apply(animator, from, to, Math.abs(velocity));
Priyank Singhb0241582019-04-19 18:46:26 -0700718 animator.start();
719 }
Brad Stenning3b0d7642019-03-28 11:04:30 -0700720
721 @Override
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800722 protected QS createDefaultQSFragment() {
723 return new CarQSFragment();
724 }
725
Rakesh Iyer545e47d2017-01-23 18:10:21 -0800726 private BatteryController createBatteryController() {
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700727 mCarBatteryController = new CarBatteryController(mContext);
728 mCarBatteryController.addBatteryViewHandler(this);
729 return mCarBatteryController;
Victor Chan1c6d0582016-01-09 16:26:37 -0800730 }
731
Rakesh Iyer1186faa2015-12-07 16:48:46 -0800732 @Override
Matthew Ng94380652019-04-08 13:43:07 -0700733 protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
Brad Stenning078235b2017-12-18 08:25:10 -0800734 mShowBottom = mContext.getResources().getBoolean(R.bool.config_enableBottomNavigationBar);
Brad Stenning078235b2017-12-18 08:25:10 -0800735 mShowLeft = mContext.getResources().getBoolean(R.bool.config_enableLeftNavigationBar);
Brad Stenning078235b2017-12-18 08:25:10 -0800736 mShowRight = mContext.getResources().getBoolean(R.bool.config_enableRightNavigationBar);
737
Brad Stenning224b5b32018-03-28 21:26:57 -0700738 buildNavBarWindows();
739 buildNavBarContent();
740 attachNavBarWindows();
Andrii Kulian3ddd7de2018-10-10 00:33:19 -0700741
Heemin Seog28097c42019-08-15 13:18:41 -0700742 // Try setting up the initial state of the nav bar if applicable.
743 if (result != null) {
744 setImeWindowStatus(Display.DEFAULT_DISPLAY, result.mImeToken,
745 result.mImeWindowVis, result.mImeBackDisposition,
746 result.mShowImeSwitcher);
747 }
748
Charles Chen10ca70b2018-11-28 00:03:38 +0800749 // There has been a car customized nav bar on the default display, so just create nav bars
750 // on external displays.
Matthew Ng94380652019-04-08 13:43:07 -0700751 mNavigationBarController.createNavigationBars(false /* includeDefaultDisplay */, result);
Brad Stenning224b5b32018-03-28 21:26:57 -0700752 }
753
754 private void buildNavBarContent() {
Heemin Seog1a13af52019-10-03 16:15:50 -0700755 // Always build top bar.
756 buildTopBar((mDeviceIsSetUpForUser) ? R.layout.car_top_navigation_bar :
757 R.layout.car_top_navigation_bar_unprovisioned);
758
Brad Stenning224b5b32018-03-28 21:26:57 -0700759 if (mShowBottom) {
Heemin Seog1a13af52019-10-03 16:15:50 -0700760 buildBottomBar((mDeviceIsSetUpForUser) ? R.layout.car_navigation_bar :
Brad Stenning224b5b32018-03-28 21:26:57 -0700761 R.layout.car_navigation_bar_unprovisioned);
762 }
763
764 if (mShowLeft) {
Heemin Seog1a13af52019-10-03 16:15:50 -0700765 buildLeft((mDeviceIsSetUpForUser) ? R.layout.car_left_navigation_bar :
Brad Stenning224b5b32018-03-28 21:26:57 -0700766 R.layout.car_left_navigation_bar_unprovisioned);
767 }
768
Brad Stenning078235b2017-12-18 08:25:10 -0800769 if (mShowRight) {
Heemin Seog1a13af52019-10-03 16:15:50 -0700770 buildRight((mDeviceIsSetUpForUser) ? R.layout.car_right_navigation_bar :
Brad Stenning224b5b32018-03-28 21:26:57 -0700771 R.layout.car_right_navigation_bar_unprovisioned);
772 }
773 }
774
775 private void buildNavBarWindows() {
Heemin Seog1a13af52019-10-03 16:15:50 -0700776 mTopNavigationBarContainer = mStatusBarWindow
777 .findViewById(R.id.car_top_navigation_bar_container);
778
Brad Stenning224b5b32018-03-28 21:26:57 -0700779 if (mShowBottom) {
davidln54739152018-07-02 13:25:44 -0700780 mNavigationBarWindow = (ViewGroup) View.inflate(mContext,
Brad Stenning224b5b32018-03-28 21:26:57 -0700781 R.layout.navigation_bar_window, null);
782 }
783 if (mShowLeft) {
784 mLeftNavigationBarWindow = (ViewGroup) View.inflate(mContext,
davidln54739152018-07-02 13:25:44 -0700785 R.layout.navigation_bar_window, null);
Brad Stenning224b5b32018-03-28 21:26:57 -0700786 }
787 if (mShowRight) {
788 mRightNavigationBarWindow = (ViewGroup) View.inflate(mContext,
789 R.layout.navigation_bar_window, null);
Brad Stenning078235b2017-12-18 08:25:10 -0800790 }
791
792 }
793
Heemin Seog28097c42019-08-15 13:18:41 -0700794 /**
795 * We register for soft keyboard visibility events such that we can hide the navigation bar
796 * giving more screen space to the IME. Note: this is optional and controlled by
797 * {@code com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard}.
798 */
799 @Override
800 public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
801 boolean showImeSwitcher) {
802 if (!mHideNavBarForKeyboard) {
803 return;
Brad Stenning224b5b32018-03-28 21:26:57 -0700804 }
Brad Stenning3b0d7642019-03-28 11:04:30 -0700805
Heemin Seog28097c42019-08-15 13:18:41 -0700806 if (mContext.getDisplay().getDisplayId() != displayId) {
807 return;
808 }
809
810 boolean isKeyboardVisible = (vis & InputMethodService.IME_VISIBLE) != 0;
JianYang Liu2c00fcf2019-10-10 15:37:29 -0700811 showBottomNavBarWindow(isKeyboardVisible);
Heemin Seog28097c42019-08-15 13:18:41 -0700812 }
813
814 private void attachNavBarWindows() {
JianYang Liu2c00fcf2019-10-10 15:37:29 -0700815 if (mShowBottom && !mBottomNavBarVisible) {
816 mBottomNavBarVisible = true;
817
818 WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
819 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
820 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
821 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
822 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
823 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
824 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
825 PixelFormat.TRANSLUCENT);
826 lp.setTitle("CarNavigationBar");
827 lp.windowAnimations = 0;
828 mWindowManager.addView(mNavigationBarWindow, lp);
829 }
Heemin Seog28097c42019-08-15 13:18:41 -0700830
Brad Stenning224b5b32018-03-28 21:26:57 -0700831 if (mShowLeft) {
832 int width = mContext.getResources().getDimensionPixelSize(
833 R.dimen.car_left_navigation_bar_width);
834 WindowManager.LayoutParams leftlp = new WindowManager.LayoutParams(
835 width, LayoutParams.MATCH_PARENT,
836 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
837 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
838 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
839 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
840 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
841 PixelFormat.TRANSLUCENT);
842 leftlp.setTitle("LeftCarNavigationBar");
843 leftlp.windowAnimations = 0;
844 leftlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
845 leftlp.gravity = Gravity.LEFT;
846 mWindowManager.addView(mLeftNavigationBarWindow, leftlp);
847 }
848 if (mShowRight) {
849 int width = mContext.getResources().getDimensionPixelSize(
850 R.dimen.car_right_navigation_bar_width);
851 WindowManager.LayoutParams rightlp = new WindowManager.LayoutParams(
852 width, LayoutParams.MATCH_PARENT,
853 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
854 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
855 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
856 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
857 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
858 PixelFormat.TRANSLUCENT);
859 rightlp.setTitle("RightCarNavigationBar");
860 rightlp.windowAnimations = 0;
861 rightlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
862 rightlp.gravity = Gravity.RIGHT;
863 mWindowManager.addView(mRightNavigationBarWindow, rightlp);
864 }
Heemin Seog28097c42019-08-15 13:18:41 -0700865 }
Brad Stenning224b5b32018-03-28 21:26:57 -0700866
JianYang Liu2c00fcf2019-10-10 15:37:29 -0700867 private void showBottomNavBarWindow(boolean isKeyboardVisible) {
Heemin Seog28097c42019-08-15 13:18:41 -0700868 if (!mShowBottom) {
869 return;
870 }
871
JianYang Liu2c00fcf2019-10-10 15:37:29 -0700872 // If keyboard is visible and bottom nav bar not visible, this is the correct state, so do
873 // nothing. Same with if keyboard is not visible and bottom nav bar is visible.
874 if (isKeyboardVisible ^ mBottomNavBarVisible) {
Heemin Seog28097c42019-08-15 13:18:41 -0700875 return;
876 }
877
JianYang Liu2c00fcf2019-10-10 15:37:29 -0700878 mNavigationBarWindow.setVisibility(isKeyboardVisible ? View.GONE : View.VISIBLE);
879 mBottomNavBarVisible = !isKeyboardVisible;
Brad Stenning224b5b32018-03-28 21:26:57 -0700880 }
881
Heemin Seog1a13af52019-10-03 16:15:50 -0700882 private void buildTopBar(int layout) {
883 mTopNavigationBarContainer.removeAllViews();
884 View.inflate(mContext, layout, mTopNavigationBarContainer);
885 mTopNavigationBarView = (CarNavigationBarView) mTopNavigationBarContainer.getChildAt(0);
886 if (mTopNavigationBarView == null) {
887 Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_top_navigation_bar");
888 throw new RuntimeException("Unable to build top nav bar due to missing layout");
889 }
890 mTopNavigationBarView.setStatusBar(this);
891 addTemperatureViewToController(mTopNavigationBarView);
892 mTopNavigationBarView.setStatusBarWindowTouchListener(mTopNavBarNotificationTouchListener);
893 }
894
Brad Stenning224b5b32018-03-28 21:26:57 -0700895 private void buildBottomBar(int layout) {
Anthony Chen0801a5c2017-03-22 09:54:37 -0700896 // SystemUI requires that the navigation bar view have a parent. Since the regular
897 // StatusBar inflates navigation_bar_window as this parent view, use the same view for the
898 // CarNavigationBarView.
Brad Stenning224b5b32018-03-28 21:26:57 -0700899 View.inflate(mContext, layout, mNavigationBarWindow);
Scott Randolphf4085822017-06-27 15:23:01 -0700900 mNavigationBarView = (CarNavigationBarView) mNavigationBarWindow.getChildAt(0);
901 if (mNavigationBarView == null) {
902 Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
Heemin Seog1a13af52019-10-03 16:15:50 -0700903 throw new RuntimeException("Unable to build bottom nav bar due to missing layout");
Scott Randolphf4085822017-06-27 15:23:01 -0700904 }
Brad Stenning078235b2017-12-18 08:25:10 -0800905 mNavigationBarView.setStatusBar(this);
Brad Stenningf084d882018-08-08 13:34:21 -0700906 addTemperatureViewToController(mNavigationBarView);
Brad Stenning3b0d7642019-03-28 11:04:30 -0700907 mNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
Rakesh Iyer1186faa2015-12-07 16:48:46 -0800908 }
909
Brad Stenning224b5b32018-03-28 21:26:57 -0700910 private void buildLeft(int layout) {
911 View.inflate(mContext, layout, mLeftNavigationBarWindow);
Brad Stenning078235b2017-12-18 08:25:10 -0800912 mLeftNavigationBarView = (CarNavigationBarView) mLeftNavigationBarWindow.getChildAt(0);
913 if (mLeftNavigationBarView == null) {
Heemin Seog1a13af52019-10-03 16:15:50 -0700914 Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_left_navigation_bar");
Brad Stenning078235b2017-12-18 08:25:10 -0800915 throw new RuntimeException("Unable to build left nav bar due to missing layout");
916 }
917 mLeftNavigationBarView.setStatusBar(this);
Brad Stenningf084d882018-08-08 13:34:21 -0700918 addTemperatureViewToController(mLeftNavigationBarView);
Brad Stenning3b0d7642019-03-28 11:04:30 -0700919 mLeftNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
Brad Stenning078235b2017-12-18 08:25:10 -0800920 }
921
922
Brad Stenning224b5b32018-03-28 21:26:57 -0700923 private void buildRight(int layout) {
924 View.inflate(mContext, layout, mRightNavigationBarWindow);
Brad Stenning078235b2017-12-18 08:25:10 -0800925 mRightNavigationBarView = (CarNavigationBarView) mRightNavigationBarWindow.getChildAt(0);
926 if (mRightNavigationBarView == null) {
Heemin Seog1a13af52019-10-03 16:15:50 -0700927 Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_right_navigation_bar");
Brad Stenning078235b2017-12-18 08:25:10 -0800928 throw new RuntimeException("Unable to build right nav bar due to missing layout");
929 }
Brad Stenningf084d882018-08-08 13:34:21 -0700930 mRightNavigationBarView.setStatusBar(this);
931 addTemperatureViewToController(mRightNavigationBarView);
Brad Stenning3b0d7642019-03-28 11:04:30 -0700932 mRightNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
Brad Stenning078235b2017-12-18 08:25:10 -0800933 }
934
Rakesh Iyer1186faa2015-12-07 16:48:46 -0800935 @Override
Qiming Shib9dfb922017-04-13 16:23:18 +0800936 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Priyank Singhb0241582019-04-19 18:46:26 -0700937 //When executing dump() function simultaneously, we need to serialize them
Qiming Shib9dfb922017-04-13 16:23:18 +0800938 //to get mStackScroller's position correctly.
939 synchronized (mQueueLock) {
940 pw.println(" mStackScroller: " + viewInfo(mStackScroller));
941 pw.println(" mStackScroller: " + viewInfo(mStackScroller)
942 + " scroll " + mStackScroller.getScrollX()
943 + "," + mStackScroller.getScrollY());
944 }
945
davidln54739152018-07-02 13:25:44 -0700946 pw.print(" mTaskStackListener=");
947 pw.println(mTaskStackListener);
Brad Stenning078235b2017-12-18 08:25:10 -0800948 pw.print(" mCarFacetButtonController=");
949 pw.println(mCarFacetButtonController);
davidln54739152018-07-02 13:25:44 -0700950 pw.print(" mFullscreenUserSwitcher=");
951 pw.println(mFullscreenUserSwitcher);
Qiming Shib9dfb922017-04-13 16:23:18 +0800952 pw.print(" mCarBatteryController=");
953 pw.println(mCarBatteryController);
954 pw.print(" mBatteryMeterView=");
955 pw.println(mBatteryMeterView);
Qiming Shib9dfb922017-04-13 16:23:18 +0800956 pw.print(" mNavigationBarView=");
957 pw.println(mNavigationBarView);
958
959 if (KeyguardUpdateMonitor.getInstance(mContext) != null) {
960 KeyguardUpdateMonitor.getInstance(mContext).dump(fd, pw, args);
961 }
962
Dave Mankoff781ef7e2019-06-28 16:33:25 -0400963 Dependency.get(FalsingManager.class).dump(pw);
Qiming Shib9dfb922017-04-13 16:23:18 +0800964 FalsingLog.dump(pw);
965
966 pw.println("SharedPreferences:");
967 for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) {
davidln54739152018-07-02 13:25:44 -0700968 pw.print(" ");
969 pw.print(entry.getKey());
970 pw.print("=");
971 pw.println(entry.getValue());
Qiming Shib9dfb922017-04-13 16:23:18 +0800972 }
973 }
974
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800975 @Override
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700976 public void showBatteryView() {
977 if (Log.isLoggable(TAG, Log.DEBUG)) {
978 Log.d(TAG, "showBatteryView(). mBatteryMeterView: " + mBatteryMeterView);
979 }
980
981 if (mBatteryMeterView != null) {
982 mBatteryMeterView.setVisibility(View.VISIBLE);
983 }
984 }
985
986 @Override
987 public void hideBatteryView() {
988 if (Log.isLoggable(TAG, Log.DEBUG)) {
989 Log.d(TAG, "hideBatteryView(). mBatteryMeterView: " + mBatteryMeterView);
990 }
991
992 if (mBatteryMeterView != null) {
993 mBatteryMeterView.setVisibility(View.GONE);
994 }
995 }
996
Victor Chan1c6d0582016-01-09 16:26:37 -0800997 /**
Winson Chung67f5c8b2018-09-24 12:09:19 -0700998 * An implementation of TaskStackChangeListener, that listens for changes in the system
davidln54739152018-07-02 13:25:44 -0700999 * task stack and notifies the navigation bar.
Victor Chan1c6d0582016-01-09 16:26:37 -08001000 */
Winson Chung67f5c8b2018-09-24 12:09:19 -07001001 private class TaskStackListenerImpl extends TaskStackChangeListener {
Wale Ogunwale480dca02016-02-06 13:58:29 -08001002 @Override
Victor Chan1c6d0582016-01-09 16:26:37 -08001003 public void onTaskStackChanged() {
Brad Stenninga5e714c2018-04-12 19:23:39 -07001004 try {
1005 mCarFacetButtonController.taskChanged(
Wale Ogunwale04d9cb52018-04-30 13:55:07 -07001006 ActivityTaskManager.getService().getAllStackInfos());
Brad Stenninga5e714c2018-04-12 19:23:39 -07001007 } catch (Exception e) {
1008 Log.e(TAG, "Getting StackInfo from activity manager failed", e);
1009 }
Victor Chan1c6d0582016-01-09 16:26:37 -08001010 }
davidln1ceedb52019-05-30 14:25:01 -07001011
1012 @Override
1013 public void onTaskDisplayChanged(int taskId, int newDisplayId) {
1014 try {
1015 mCarFacetButtonController.taskChanged(
1016 ActivityTaskManager.getService().getAllStackInfos());
1017 } catch (Exception e) {
1018 Log.e(TAG, "Getting StackInfo from activity manager failed", e);
1019 }
1020 }
Victor Chan1c6d0582016-01-09 16:26:37 -08001021 }
Rakesh Iyer2790a372016-01-22 15:33:39 -08001022
jovanakedba98c2018-09-14 15:46:24 -07001023 private void onDrivingStateChanged(CarDrivingStateEvent notUsed) {
1024 // Check if we need to start the timer every time driving state changes.
1025 startSwitchToGuestTimerIfDrivingOnKeyguard();
1026 }
1027
1028 private void startSwitchToGuestTimerIfDrivingOnKeyguard() {
1029 if (mDrivingStateHelper.isCurrentlyDriving() && mState != StatusBarState.SHADE) {
1030 // We're driving while keyguard is up.
1031 mSwitchToGuestTimer.start();
1032 } else {
1033 mSwitchToGuestTimer.cancel();
1034 }
1035 }
1036
Rakesh Iyer2790a372016-01-22 15:33:39 -08001037 @Override
1038 protected void createUserSwitcher() {
Jason Monk9c7844c2017-01-18 15:21:53 -05001039 UserSwitcherController userSwitcherController =
1040 Dependency.get(UserSwitcherController.class);
1041 if (userSwitcherController.useFullscreenUserSwitcher()) {
Erin Yan8182bfd2019-08-14 12:03:12 -07001042 Car car = Car.createCar(mContext);
1043 CarTrustAgentEnrollmentManager enrollmentManager = (CarTrustAgentEnrollmentManager) car
1044 .getCarManager(Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE);
Jason Monk9c7844c2017-01-18 15:21:53 -05001045 mFullscreenUserSwitcher = new FullscreenUserSwitcher(this,
Erin Yan8182bfd2019-08-14 12:03:12 -07001046 mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub),
1047 enrollmentManager, mContext);
Rakesh Iyer2790a372016-01-22 15:33:39 -08001048 } else {
1049 super.createUserSwitcher();
1050 }
1051 }
1052
1053 @Override
jovanak168dbdb2019-02-18 01:00:43 -08001054 public void setLockscreenUser(int newUserId) {
1055 super.setLockscreenUser(newUserId);
1056 // Try to dismiss the keyguard after every user switch.
1057 dismissKeyguardWhenUserSwitcherNotDisplayed();
1058 }
1059
1060 @Override
Jason Monk1fd3fc32018-08-14 17:20:09 -04001061 public void onStateChanged(int newState) {
1062 super.onStateChanged(newState);
jovanakedba98c2018-09-14 15:46:24 -07001063
1064 startSwitchToGuestTimerIfDrivingOnKeyguard();
1065
jovanak168dbdb2019-02-18 01:00:43 -08001066 if (newState != StatusBarState.FULLSCREEN_USER_SWITCHER) {
1067 hideUserSwitcher();
jovanak604ad512018-08-14 18:41:27 -07001068 } else {
jovanak168dbdb2019-02-18 01:00:43 -08001069 dismissKeyguardWhenUserSwitcherNotDisplayed();
1070 }
1071 }
1072
1073 /** Makes the full screen user switcher visible, if applicable. */
1074 public void showUserSwitcher() {
1075 if (mFullscreenUserSwitcher != null && mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
1076 mFullscreenUserSwitcher.show(); // Makes the switcher visible.
1077 }
1078 }
1079
1080 private void hideUserSwitcher() {
1081 if (mFullscreenUserSwitcher != null) {
jovanak604ad512018-08-14 18:41:27 -07001082 mFullscreenUserSwitcher.hide();
Aarthi Balachander0a427ef2018-07-13 15:00:58 -07001083 }
1084 }
1085
jovanak622def22019-06-11 11:51:54 -07001086 final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
1087 @Override
1088 public void onScreenTurnedOn() {
1089 dismissKeyguardWhenUserSwitcherNotDisplayed();
1090 }
1091 };
1092
jovanak168dbdb2019-02-18 01:00:43 -08001093 // We automatically dismiss keyguard unless user switcher is being shown on the keyguard.
1094 private void dismissKeyguardWhenUserSwitcherNotDisplayed() {
1095 if (mFullscreenUserSwitcher == null) {
1096 return; // Not using the full screen user switcher.
1097 }
1098
1099 if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER
1100 && !mFullscreenUserSwitcher.isVisible()) {
1101 // Current execution path continues to set state after this, thus we deffer the
1102 // dismissal to the next execution cycle.
1103 postDismissKeyguard(); // Dismiss the keyguard if switcher is not visible.
Rakesh Iyer2790a372016-01-22 15:33:39 -08001104 }
1105 }
Rakesh Iyer74ebabe2016-03-11 10:10:43 -08001106
jovanak604ad512018-08-14 18:41:27 -07001107 public void postDismissKeyguard() {
1108 mHandler.post(this::dismissKeyguard);
1109 }
1110
1111 /**
1112 * Dismisses the keyguard and shows bouncer if authentication is necessary.
1113 */
1114 public void dismissKeyguard() {
jovanak622def22019-06-11 11:51:54 -07001115 // Don't dismiss keyguard when the screen is off.
1116 if (mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_OFF) {
1117 return;
1118 }
jovanak604ad512018-08-14 18:41:27 -07001119 executeRunnableDismissingKeyguard(null/* runnable */, null /* cancelAction */,
Brad Stenning8d1a51c2018-11-20 17:34:16 -08001120 true /* dismissShade */, true /* afterKeyguardGone */, true /* deferred */);
jovanak604ad512018-08-14 18:41:27 -07001121 }
1122
Anthony Chen3cb3ad92016-12-01 10:58:47 -08001123 /**
1124 * Ensures that relevant child views are appropriately recreated when the device's density
1125 * changes.
1126 */
1127 @Override
Jason Monk9cac5ff2017-06-02 11:35:50 -04001128 public void onDensityOrFontScaleChanged() {
Anthony Chen3cb3ad92016-12-01 10:58:47 -08001129 super.onDensityOrFontScaleChanged();
Guobin Zhangeef0e0c2019-02-01 11:54:24 -08001130 restartNavBars();
Anthony Chene658cc22017-04-27 11:17:35 -07001131 // Need to update the background on density changed in case the change was due to night
1132 // mode.
1133 mNotificationPanelBackground = getDefaultWallpaper();
1134 mScrimController.setScrimBehindDrawable(mNotificationPanelBackground);
1135 }
1136
1137 /**
1138 * Returns the {@link Drawable} that represents the wallpaper that the user has currently set.
1139 */
1140 private Drawable getDefaultWallpaper() {
1141 return mContext.getDrawable(com.android.internal.R.drawable.default_wallpaper);
Anthony Chen3cb3ad92016-12-01 10:58:47 -08001142 }
Brad Stenning19f236a2018-12-11 14:12:30 -08001143
Priyank Singhb0241582019-04-19 18:46:26 -07001144 private void setNotificationViewClipBounds(int height) {
Priyank Singhea4af132019-05-01 17:57:06 -07001145 if (height > mNotificationView.getHeight()) {
1146 height = mNotificationView.getHeight();
1147 }
Priyank Singhb0241582019-04-19 18:46:26 -07001148 Rect clipBounds = new Rect();
1149 clipBounds.set(0, 0, mNotificationView.getWidth(), height);
Priyank Singh245bd122019-04-21 15:42:44 -07001150 // Sets the clip region on the notification list view.
Priyank Singh647f9e62019-05-16 15:19:53 -07001151 mNotificationView.setClipBounds(clipBounds);
1152 if (mHandleBar != null) {
1153 ViewGroup.MarginLayoutParams lp =
1154 (ViewGroup.MarginLayoutParams) mHandleBar.getLayoutParams();
1155 mHandleBar.setTranslationY(height - mHandleBar.getHeight() - lp.bottomMargin);
1156 }
Priyank Singh245bd122019-04-21 15:42:44 -07001157 if (mNotificationView.getHeight() > 0) {
1158 // Calculates the alpha value for the background based on how much of the notification
1159 // shade is visible to the user. When the notification shade is completely open then
1160 // alpha value will be 1.
1161 float alpha = (float) height / mNotificationView.getHeight();
Brad Stenning8650f412019-07-16 16:46:28 -07001162 Drawable background = mNotificationView.getBackground().mutate();
Priyank Singh245bd122019-04-21 15:42:44 -07001163
1164 background.setAlpha((int) (alpha * 255));
Brad Stenning8650f412019-07-16 16:46:28 -07001165 mNotificationView.setBackground(background);
Priyank Singh245bd122019-04-21 15:42:44 -07001166 }
Priyank Singhb0241582019-04-19 18:46:26 -07001167 }
Brad Stenning3b0d7642019-03-28 11:04:30 -07001168
Priyank Singhb0241582019-04-19 18:46:26 -07001169 private void calculatePercentageFromBottom(float height) {
1170 if (mNotificationView.getHeight() > 0) {
1171 mPercentageFromBottom = (int) Math.abs(
1172 height / mNotificationView.getHeight() * 100);
1173 }
1174 }
1175
Brad Stenning3b0d7642019-03-28 11:04:30 -07001176 private static final int SWIPE_DOWN_MIN_DISTANCE = 25;
1177 private static final int SWIPE_MAX_OFF_PATH = 75;
1178 private static final int SWIPE_THRESHOLD_VELOCITY = 200;
Priyank Singhb0241582019-04-19 18:46:26 -07001179
Priyank Singha47b6f72019-05-28 13:02:47 -07001180 /**
1181 * Only responsible for open hooks. Since once the panel opens it covers all elements
1182 * there is no need to merge with close.
1183 */
Brad Stenning3b0d7642019-03-28 11:04:30 -07001184 private abstract class OpenNotificationGestureListener extends
1185 GestureDetector.SimpleOnGestureListener {
1186
1187 @Override
Priyank Singhb0241582019-04-19 18:46:26 -07001188 public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
1189 float distanceY) {
1190
1191 if (mNotificationView.getVisibility() == View.INVISIBLE) {
1192 // when the on-scroll is called for the first time to open.
1193 mNotificationList.scrollToPosition(0);
1194 }
1195 mStatusBarWindowController.setPanelVisible(true);
1196 mNotificationView.setVisibility(View.VISIBLE);
1197
1198 // clips the view for the notification shade when the user scrolls to open.
1199 setNotificationViewClipBounds((int) event2.getRawY());
1200
1201 // Initially the scroll starts with height being zero. This checks protects from divide
1202 // by zero error.
1203 calculatePercentageFromBottom(event2.getRawY());
1204
1205 mIsTracking = true;
1206 return true;
1207 }
1208
1209
1210 @Override
Brad Stenning3b0d7642019-03-28 11:04:30 -07001211 public boolean onFling(MotionEvent event1, MotionEvent event2,
1212 float velocityX, float velocityY) {
Priyank Singhb0241582019-04-19 18:46:26 -07001213 if (velocityY > SWIPE_THRESHOLD_VELOCITY) {
1214 mOpeningVelocity = velocityY;
Brad Stenning3b0d7642019-03-28 11:04:30 -07001215 openNotification();
1216 return true;
1217 }
Priyank Singhb0241582019-04-19 18:46:26 -07001218 animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
Brad Stenning3b0d7642019-03-28 11:04:30 -07001219
1220 return false;
1221 }
Priyank Singhb0241582019-04-19 18:46:26 -07001222
Brad Stenning3b0d7642019-03-28 11:04:30 -07001223 protected abstract void openNotification();
Brad Stenning19f236a2018-12-11 14:12:30 -08001224 }
1225
Priyank Singha47b6f72019-05-28 13:02:47 -07001226 /**
1227 * To be installed on the open panel notification panel
1228 */
Brad Stenning3b0d7642019-03-28 11:04:30 -07001229 private abstract class CloseNotificationGestureListener extends
1230 GestureDetector.SimpleOnGestureListener {
1231
1232 @Override
Priyank Singhbae09ae2019-04-25 18:17:02 -07001233 public boolean onSingleTapUp(MotionEvent motionEvent) {
Priyank Singhb9ab0522019-05-03 15:36:25 -07001234 if (mPanelExpanded) {
1235 animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
1236 }
1237 return true;
Priyank Singhbae09ae2019-04-25 18:17:02 -07001238 }
1239
1240 @Override
Priyank Singhb0241582019-04-19 18:46:26 -07001241 public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
1242 float distanceY) {
Priyank Singhbae09ae2019-04-25 18:17:02 -07001243 // should not clip while scroll to the bottom of the list.
Priyank Singhb0241582019-04-19 18:46:26 -07001244 if (!mNotificationListAtBottomAtTimeOfTouch) {
1245 return false;
1246 }
1247 float actualNotificationHeight =
1248 mNotificationView.getHeight() - (event1.getRawY() - event2.getRawY());
1249 if (actualNotificationHeight > mNotificationView.getHeight()) {
1250 actualNotificationHeight = mNotificationView.getHeight();
1251 }
1252 if (mNotificationView.getHeight() > 0) {
1253 mPercentageFromBottom = (int) Math.abs(
1254 actualNotificationHeight / mNotificationView.getHeight() * 100);
1255 boolean isUp = distanceY > 0;
1256
1257 // This check is to figure out if onScroll was called while swiping the card at
1258 // bottom of the list. At that time we should not allow notification shade to
1259 // close. We are also checking for the upwards swipe gesture here because it is
1260 // possible if a user is closing the notification shade and while swiping starts
1261 // to open again but does not fling. At that time we should allow the
1262 // notification shade to close fully or else it would stuck in between.
1263 if (Math.abs(mNotificationView.getHeight() - actualNotificationHeight)
1264 > SWIPE_DOWN_MIN_DISTANCE && isUp) {
1265 setNotificationViewClipBounds((int) actualNotificationHeight);
1266 mIsTracking = true;
1267 } else if (!isUp) {
1268 setNotificationViewClipBounds((int) actualNotificationHeight);
1269 }
1270 }
1271 // if we return true the the items in RV won't be scrollable.
1272 return false;
1273 }
1274
1275
1276 @Override
Brad Stenning3b0d7642019-03-28 11:04:30 -07001277 public boolean onFling(MotionEvent event1, MotionEvent event2,
1278 float velocityX, float velocityY) {
Priyank Singha01f2432019-05-17 16:33:57 -07001279 // should not fling if the touch does not start when view is at the bottom of the list.
1280 if (!mNotificationListAtBottomAtTimeOfTouch) {
Priyank Singhbae09ae2019-04-25 18:17:02 -07001281 return false;
1282 }
Brad Stenning3b0d7642019-03-28 11:04:30 -07001283 if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH
1284 || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) {
1285 // swipe was not vertical or was not fast enough
1286 return false;
1287 }
1288 boolean isUp = velocityY < 0;
Priyank Singhb0241582019-04-19 18:46:26 -07001289 if (isUp) {
Brad Stenning3b0d7642019-03-28 11:04:30 -07001290 close();
1291 return true;
Priyank Singhb0241582019-04-19 18:46:26 -07001292 } else {
1293 // we should close the shade
1294 animateNotificationPanel(velocityY, false);
Brad Stenning3b0d7642019-03-28 11:04:30 -07001295 }
1296 return false;
1297 }
Priyank Singhb0241582019-04-19 18:46:26 -07001298
Brad Stenning3b0d7642019-03-28 11:04:30 -07001299 protected abstract void close();
1300 }
1301
Priyank Singha47b6f72019-05-28 13:02:47 -07001302 /**
1303 * To be installed on the nav bars.
1304 */
Brad Stenning3b0d7642019-03-28 11:04:30 -07001305 private abstract class NavBarCloseNotificationGestureListener extends
1306 CloseNotificationGestureListener {
1307 @Override
1308 public boolean onSingleTapUp(MotionEvent e) {
Priyank Singhb0241582019-04-19 18:46:26 -07001309 mClosingVelocity = DEFAULT_FLING_VELOCITY;
Priyank Singhb9ab0522019-05-03 15:36:25 -07001310 if (mPanelExpanded) {
1311 close();
1312 }
Brad Stenning3b0d7642019-03-28 11:04:30 -07001313 return super.onSingleTapUp(e);
1314 }
1315
1316 @Override
Priyank Singhb0241582019-04-19 18:46:26 -07001317 public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
1318 float distanceY) {
1319 calculatePercentageFromBottom(event2.getRawY());
1320 setNotificationViewClipBounds((int) event2.getRawY());
1321 return true;
1322 }
1323
1324 @Override
Brad Stenning3b0d7642019-03-28 11:04:30 -07001325 public void onLongPress(MotionEvent e) {
Priyank Singhb0241582019-04-19 18:46:26 -07001326 mClosingVelocity = DEFAULT_FLING_VELOCITY;
Brad Stenning3b0d7642019-03-28 11:04:30 -07001327 close();
1328 super.onLongPress(e);
1329 }
Adora Zhang0237b9c2019-03-08 12:27:09 -08001330 }
Brad Stenning2ccc2a42019-04-15 10:17:01 -07001331
1332 /**
Priyank Singha47b6f72019-05-28 13:02:47 -07001333 * To be installed on the handle bar.
1334 */
1335 private class HandleBarCloseNotificationGestureListener extends
1336 GestureDetector.SimpleOnGestureListener {
1337
1338 @Override
1339 public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
1340 float distanceY) {
1341 calculatePercentageFromBottom(event2.getRawY());
1342 // To prevent the jump in the clip bounds while closing the notification shade using
1343 // the handle bar we should calculate the height using the diff of event1 and event2.
1344 // This will help the notification shade to clip smoothly as the event2 value changes
1345 // as event1 value will be fixed.
1346 int clipHeight =
1347 mNotificationView.getHeight() - (int) (event1.getRawY() - event2.getRawY());
1348 setNotificationViewClipBounds(clipHeight);
1349 return true;
1350 }
1351 }
1352
1353 /**
1354 * SystemUi version of the notification manager that overrides methods such that the
Brad Stenning2ccc2a42019-04-15 10:17:01 -07001355 * notifications end up in the status bar layouts instead of a standalone window.
1356 */
1357 private class CarSystemUIHeadsUpNotificationManager extends CarHeadsUpNotificationManager {
1358
1359 CarSystemUIHeadsUpNotificationManager(Context context,
1360 NotificationClickHandlerFactory clickHandlerFactory,
1361 NotificationDataManager notificationDataManager) {
1362 super(context, clickHandlerFactory, notificationDataManager);
1363 }
1364
1365 @Override
1366 protected View createHeadsUpPanel() {
1367 // In SystemUi the view is already in the window so just return a reference.
1368 return mStatusBarWindow.findViewById(R.id.notification_headsup);
1369 }
1370
1371 @Override
1372 protected void addHeadsUpPanelToDisplay() {
1373 // Set the panel initial state to invisible
1374 mHeadsUpPanel.setVisibility(View.INVISIBLE);
1375 }
1376
1377 @Override
Priyank Singh8bd920f2019-06-04 15:21:46 -07001378 protected void setInternalInsetsInfo(ViewTreeObserver.InternalInsetsInfo info,
1379 HeadsUpEntry currentNotification, boolean panelExpanded) {
1380 super.setInternalInsetsInfo(info, currentNotification, mPanelExpanded);
1381 }
1382
1383 @Override
Brad Stenning2ccc2a42019-04-15 10:17:01 -07001384 protected void setHeadsUpVisible() {
Priyank Singh7e9344f2019-05-07 14:30:13 -07001385 // if the Notifications panel is showing don't show the Heads up
Priyank Singh8bd920f2019-06-04 15:21:46 -07001386 if (!mEnableHeadsUpNotificationWhenNotificationShadeOpen && mPanelExpanded) {
Priyank Singh7e9344f2019-05-07 14:30:13 -07001387 return;
1388 }
Priyank Singh8bd920f2019-06-04 15:21:46 -07001389
Brad Stenning2ccc2a42019-04-15 10:17:01 -07001390 super.setHeadsUpVisible();
1391 if (mHeadsUpPanel.getVisibility() == View.VISIBLE) {
1392 mStatusBarWindowController.setHeadsUpShowing(true);
1393 mStatusBarWindowController.setForceStatusBarVisible(true);
1394 }
1395 }
1396
1397 @Override
1398 protected void removeNotificationFromPanel(HeadsUpEntry currentHeadsUpNotification) {
1399 super.removeNotificationFromPanel(currentHeadsUpNotification);
1400 // If the panel ended up empty and hidden we can remove it from SystemUi
1401 if (mHeadsUpPanel.getVisibility() != View.VISIBLE) {
1402 mStatusBarWindowController.setHeadsUpShowing(false);
1403 mStatusBarWindowController.setForceStatusBarVisible(false);
1404 }
1405 }
1406 }
Rakesh Iyer1186faa2015-12-07 16:48:46 -08001407}