blob: 1a1a8ea501bb10870050d419805460af9d043366 [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;
Brad Stenning2ccc2a42019-04-15 10:17:01 -070029import android.content.Context;
Rakesh Iyer1186faa2015-12-07 16:48:46 -080030import android.graphics.PixelFormat;
Priyank Singhb0241582019-04-19 18:46:26 -070031import android.graphics.Rect;
Anthony Chene658cc22017-04-27 11:17:35 -070032import android.graphics.drawable.Drawable;
Heemin Seog28097c42019-08-15 13:18:41 -070033import android.inputmethodservice.InputMethodService;
34import android.os.IBinder;
Rakesh Iyer74ebabe2016-03-11 10:10:43 -080035import android.util.Log;
Heemin Seog28097c42019-08-15 13:18:41 -070036import android.view.Display;
Brad Stenning3b0d7642019-03-28 11:04:30 -070037import android.view.GestureDetector;
Brad Stenning078235b2017-12-18 08:25:10 -080038import android.view.Gravity;
Brad Stenning3b0d7642019-03-28 11:04:30 -070039import android.view.MotionEvent;
Rakesh Iyer1186faa2015-12-07 16:48:46 -080040import android.view.View;
Anthony Chen0801a5c2017-03-22 09:54:37 -070041import android.view.ViewGroup;
Rakesh Iyer1186faa2015-12-07 16:48:46 -080042import android.view.ViewGroup.LayoutParams;
Priyank Singhb9ab0522019-05-03 15:36:25 -070043import android.view.ViewTreeObserver;
Rakesh Iyer1186faa2015-12-07 16:48:46 -080044import android.view.WindowManager;
Anthony Chene658cc22017-04-27 11:17:35 -070045
Brad Stenning3b0d7642019-03-28 11:04:30 -070046import androidx.annotation.NonNull;
47import androidx.recyclerview.widget.RecyclerView;
48
Brad Stenning2ccc2a42019-04-15 10:17:01 -070049import com.android.car.notification.CarHeadsUpNotificationManager;
Brad Stenning3b0d7642019-03-28 11:04:30 -070050import com.android.car.notification.CarNotificationListener;
51import com.android.car.notification.CarNotificationView;
52import com.android.car.notification.CarUxRestrictionManagerWrapper;
Brad Stenning2ccc2a42019-04-15 10:17:01 -070053import com.android.car.notification.HeadsUpEntry;
Brad Stenning3b0d7642019-03-28 11:04:30 -070054import com.android.car.notification.NotificationClickHandlerFactory;
Brad Stenning2ccc2a42019-04-15 10:17:01 -070055import com.android.car.notification.NotificationDataManager;
Brad Stenning3b0d7642019-03-28 11:04:30 -070056import com.android.car.notification.NotificationViewController;
57import com.android.car.notification.PreprocessingManager;
Matthew Ng94380652019-04-08 13:43:07 -070058import com.android.internal.statusbar.RegisterStatusBarResult;
Winson Chung2db35572017-10-09 15:08:30 -070059import com.android.keyguard.KeyguardUpdateMonitor;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070060import com.android.systemui.BatteryMeterView;
Jason Monk27d01a622018-12-10 15:57:09 -050061import com.android.systemui.CarSystemUIFactory;
Jason Monk9c7844c2017-01-18 15:21:53 -050062import com.android.systemui.Dependency;
Winson Chung2db35572017-10-09 15:08:30 -070063import com.android.systemui.Prefs;
Rakesh Iyer1186faa2015-12-07 16:48:46 -080064import com.android.systemui.R;
Jason Monk27d01a622018-12-10 15:57:09 -050065import com.android.systemui.SystemUIFactory;
Winson Chung2db35572017-10-09 15:08:30 -070066import com.android.systemui.classifier.FalsingLog;
Jason Monkaa573e92017-01-27 17:00:29 -050067import com.android.systemui.fragments.FragmentHostManager;
jovanak622def22019-06-11 11:51:54 -070068import com.android.systemui.keyguard.ScreenLifecycle;
Dave Mankoff781ef7e2019-06-28 16:33:25 -040069import com.android.systemui.plugins.FalsingManager;
Brad Stenning8d1a51c2018-11-20 17:34:16 -080070import com.android.systemui.plugins.qs.QS;
71import com.android.systemui.qs.car.CarQSFragment;
Winson Chungb05b3982017-11-01 18:02:43 -070072import com.android.systemui.shared.system.ActivityManagerWrapper;
Winson Chung67f5c8b2018-09-24 12:09:19 -070073import com.android.systemui.shared.system.TaskStackChangeListener;
Priyank Singhb0241582019-04-19 18:46:26 -070074import com.android.systemui.statusbar.FlingAnimationUtils;
Rakesh Iyer2790a372016-01-22 15:33:39 -080075import com.android.systemui.statusbar.StatusBarState;
Brad Stenning38b46f82018-03-27 13:57:29 -070076import com.android.systemui.statusbar.car.hvac.HvacController;
Brad Stenningf084d882018-08-08 13:34:21 -070077import com.android.systemui.statusbar.car.hvac.TemperatureView;
Jason Monkaa573e92017-01-27 17:00:29 -050078import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
Jason Monk2a6ea9c2017-01-26 11:14:51 -050079import com.android.systemui.statusbar.phone.StatusBar;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070080import com.android.systemui.statusbar.policy.BatteryController;
Brad Stenning224b5b32018-03-28 21:26:57 -070081import com.android.systemui.statusbar.policy.DeviceProvisionedController;
Jason Monk9c7844c2017-01-18 15:21:53 -050082import com.android.systemui.statusbar.policy.UserSwitcherController;
Rakesh Iyer1186faa2015-12-07 16:48:46 -080083
Qiming Shib9dfb922017-04-13 16:23:18 +080084import java.io.FileDescriptor;
85import java.io.PrintWriter;
86import java.util.Map;
davidln54739152018-07-02 13:25:44 -070087
Rakesh Iyer1186faa2015-12-07 16:48:46 -080088/**
89 * A status bar (and navigation bar) tailored for the automotive use case.
90 */
Heemin Seog28097c42019-08-15 13:18:41 -070091public class CarStatusBar extends StatusBar implements CarBatteryController.BatteryViewHandler {
Rakesh Iyer74ebabe2016-03-11 10:10:43 -080092 private static final String TAG = "CarStatusBar";
Priyank Singhb0241582019-04-19 18:46:26 -070093 // used to calculate how fast to open or close the window
94 private static final float DEFAULT_FLING_VELOCITY = 0;
95 // max time a fling animation takes
96 private static final float FLING_ANIMATION_MAX_TIME = 0.5f;
97 // acceleration rate for the fling animation
98 private static final float FLING_SPEED_UP_FACTOR = 0.6f;
99
100 private float mOpeningVelocity = DEFAULT_FLING_VELOCITY;
101 private float mClosingVelocity = DEFAULT_FLING_VELOCITY;
Rakesh Iyer74ebabe2016-03-11 10:10:43 -0800102
JianYang Liub923d1e2019-10-24 17:19:44 -0700103 private float mBackgroundAlphaDiff;
104 private float mInitialBackgroundAlpha;
105
Victor Chan1c6d0582016-01-09 16:26:37 -0800106 private TaskStackListenerImpl mTaskStackListener;
Victor Chan1c6d0582016-01-09 16:26:37 -0800107
Rakesh Iyer2790a372016-01-22 15:33:39 -0800108 private FullscreenUserSwitcher mFullscreenUserSwitcher;
Victor Chan1c6d0582016-01-09 16:26:37 -0800109
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700110 private CarBatteryController mCarBatteryController;
111 private BatteryMeterView mBatteryMeterView;
Anthony Chene658cc22017-04-27 11:17:35 -0700112 private Drawable mNotificationPanelBackground;
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700113
Heemin Seog1a13af52019-10-03 16:15:50 -0700114 private ViewGroup mTopNavigationBarContainer;
Scott Randolphf4085822017-06-27 15:23:01 -0700115 private ViewGroup mNavigationBarWindow;
Brad Stenning078235b2017-12-18 08:25:10 -0800116 private ViewGroup mLeftNavigationBarWindow;
117 private ViewGroup mRightNavigationBarWindow;
Heemin Seog1a13af52019-10-03 16:15:50 -0700118 private CarNavigationBarView mTopNavigationBarView;
Jason Monk49fa0162017-01-11 09:21:56 -0500119 private CarNavigationBarView mNavigationBarView;
Brad Stenning078235b2017-12-18 08:25:10 -0800120 private CarNavigationBarView mLeftNavigationBarView;
121 private CarNavigationBarView mRightNavigationBarView;
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700122
Qiming Shib9dfb922017-04-13 16:23:18 +0800123 private final Object mQueueLock = new Object();
Brad Stenning078235b2017-12-18 08:25:10 -0800124 private boolean mShowLeft;
125 private boolean mShowRight;
126 private boolean mShowBottom;
127 private CarFacetButtonController mCarFacetButtonController;
Brad Stenning224b5b32018-03-28 21:26:57 -0700128 private ActivityManagerWrapper mActivityManagerWrapper;
129 private DeviceProvisionedController mDeviceProvisionedController;
Heemin Seog1a13af52019-10-03 16:15:50 -0700130 private boolean mDeviceIsSetUpForUser = true;
Brad Stenningf084d882018-08-08 13:34:21 -0700131 private HvacController mHvacController;
jovanakedba98c2018-09-14 15:46:24 -0700132 private DrivingStateHelper mDrivingStateHelper;
Jonathan Koo007ff072019-05-10 09:24:08 -0700133 private PowerManagerHelper mPowerManagerHelper;
134 private FlingAnimationUtils mFlingAnimationUtils;
jovanakedba98c2018-09-14 15:46:24 -0700135 private SwitchToGuestTimer mSwitchToGuestTimer;
Jonathan Koo007ff072019-05-10 09:24:08 -0700136 private NotificationDataManager mNotificationDataManager;
137 private NotificationClickHandlerFactory mNotificationClickHandlerFactory;
jovanak622def22019-06-11 11:51:54 -0700138 private ScreenLifecycle mScreenLifecycle;
Brad Stenning078235b2017-12-18 08:25:10 -0800139
Brad Stenning3b0d7642019-03-28 11:04:30 -0700140 // The container for the notifications.
141 private CarNotificationView mNotificationView;
142 private RecyclerView mNotificationList;
Priyank Singh647f9e62019-05-16 15:19:53 -0700143 // The handler bar view at the bottom of notification shade.
144 private View mHandleBar;
Adora Zhangdaea9112019-04-11 17:47:40 -0700145 // The controller for the notification view.
146 private NotificationViewController mNotificationViewController;
Brad Stenning3b0d7642019-03-28 11:04:30 -0700147 // The state of if the notification list is currently showing the bottom.
148 private boolean mNotificationListAtBottom;
149 // Was the notification list at the bottom when the user first touched the screen
150 private boolean mNotificationListAtBottomAtTimeOfTouch;
Heemin Seog1a13af52019-10-03 16:15:50 -0700151 // To be attached to the top navigation bar (i.e. status bar) to pull down the notification
152 // panel.
153 private View.OnTouchListener mTopNavBarNotificationTouchListener;
Brad Stenning3b0d7642019-03-28 11:04:30 -0700154 // To be attached to the navigation bars such that they can close the notification panel if
155 // it's open.
156 private View.OnTouchListener mNavBarNotificationTouchListener;
157
Priyank Singhb0241582019-04-19 18:46:26 -0700158 // Percentage from top of the screen after which the notification shade will open. This value
159 // will be used while opening the notification shade.
160 private int mSettleOpenPercentage;
161 // Percentage from top of the screen below which the notification shade will close. This
162 // value will be used while closing the notification shade.
163 private int mSettleClosePercentage;
164 // Percentage of notification shade open from top of the screen.
165 private int mPercentageFromBottom;
166 // If notification shade is animation to close or to open.
167 private boolean mIsNotificationAnimating;
168
169 // Tracks when the notification shade is being scrolled. This refers to the glass pane being
170 // scrolled not the recycler view.
171 private boolean mIsTracking;
172 private float mFirstTouchDownOnGlassPane;
173
174 // If the notification card inside the recycler view is being swiped.
175 private boolean mIsNotificationCardSwiping;
176 // If notification shade is being swiped vertically to close.
177 private boolean mIsSwipingVerticallyToClose;
Priyank Singh8bd920f2019-06-04 15:21:46 -0700178 // Whether heads-up notifications should be shown when shade is open.
179 private boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen;
Heemin Seog28097c42019-08-15 13:18:41 -0700180 // If the nav bar should be hidden when the soft keyboard is visible.
181 private boolean mHideNavBarForKeyboard;
182 private boolean mBottomNavBarVisible;
Priyank Singhb0241582019-04-19 18:46:26 -0700183
Keun young Park9e395bb2019-10-11 20:00:22 -0700184 private CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper;
185
Jonathan Koo007ff072019-05-10 09:24:08 -0700186 private final CarPowerStateListener mCarPowerStateListener =
187 (int state) -> {
188 // When the car powers on, clear all notifications and mute/unread states.
189 Log.d(TAG, "New car power state: " + state);
190 if (state == CarPowerStateListener.ON) {
191 if (mNotificationClickHandlerFactory != null) {
192 mNotificationClickHandlerFactory.clearAllNotifications();
193 }
194 if (mNotificationDataManager != null) {
195 mNotificationDataManager.clearAll();
196 }
197 }
198 };
199
Victor Chan1c6d0582016-01-09 16:26:37 -0800200 @Override
201 public void start() {
Brad Stenningeb6051c2019-02-27 13:46:11 -0800202 // get the provisioned state before calling the parent class since it's that flow that
203 // builds the nav bar
204 mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
Heemin Seog1a13af52019-10-03 16:15:50 -0700205 mDeviceIsSetUpForUser = mDeviceProvisionedController.isCurrentUserSetup();
Heemin Seog28097c42019-08-15 13:18:41 -0700206
207 // Keyboard related setup, before nav bars are created.
208 mHideNavBarForKeyboard = mContext.getResources().getBoolean(
209 com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard);
210 mBottomNavBarVisible = false;
211
jovanak504e7532019-08-19 16:32:50 -0700212 // Need to initialize screen lifecycle before calling super.start - before switcher is
213 // created.
214 mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
215 mScreenLifecycle.addObserver(mScreenObserver);
216
Heemin Seog1a13af52019-10-03 16:15:50 -0700217 // Need to initialize HVAC controller before calling super.start - before system bars are
218 // created.
219 mHvacController = new HvacController(mContext);
220
JianYang Liub923d1e2019-10-24 17:19:44 -0700221 // Notification bar related setup.
222 mInitialBackgroundAlpha = (float) mContext.getResources().getInteger(
223 R.integer.config_initialNotificationBackgroundAlpha) / 100;
224 if (mInitialBackgroundAlpha < 0 || mInitialBackgroundAlpha > 100) {
225 throw new RuntimeException(
226 "Unable to setup notification bar due to incorrect initial background alpha"
227 + " percentage");
228 }
229 float finalBackgroundAlpha = Math.max(
230 mInitialBackgroundAlpha,
231 (float) mContext.getResources().getInteger(
232 R.integer.config_finalNotificationBackgroundAlpha) / 100);
233 if (finalBackgroundAlpha < 0 || finalBackgroundAlpha > 100) {
234 throw new RuntimeException(
235 "Unable to setup notification bar due to incorrect final background alpha"
236 + " percentage");
237 }
238 mBackgroundAlphaDiff = finalBackgroundAlpha - mInitialBackgroundAlpha;
239
Victor Chan1c6d0582016-01-09 16:26:37 -0800240 super.start();
Jaewan Kim938a50b2016-03-14 17:35:43 +0900241 mTaskStackListener = new TaskStackListenerImpl();
Brad Stenning224b5b32018-03-28 21:26:57 -0700242 mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
243 mActivityManagerWrapper.registerTaskStackListener(mTaskStackListener);
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700244
Jason Monke59dc402018-08-16 12:05:01 -0400245 mNotificationPanel.setScrollingEnabled(true);
Priyank Singhb0241582019-04-19 18:46:26 -0700246 mSettleOpenPercentage = mContext.getResources().getInteger(
247 R.integer.notification_settle_open_percentage);
248 mSettleClosePercentage = mContext.getResources().getInteger(
249 R.integer.notification_settle_close_percentage);
Jonathan Koo007ff072019-05-10 09:24:08 -0700250 mFlingAnimationUtils = new FlingAnimationUtils(mContext,
Priyank Singhb0241582019-04-19 18:46:26 -0700251 FLING_ANIMATION_MAX_TIME, FLING_SPEED_UP_FACTOR);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800252
Rakesh Iyer545e47d2017-01-23 18:10:21 -0800253 createBatteryController();
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700254 mCarBatteryController.startListening();
Brad Stenning38b46f82018-03-27 13:57:29 -0700255
Brad Stenningf084d882018-08-08 13:34:21 -0700256 mHvacController.connectToCarService();
davidln54739152018-07-02 13:25:44 -0700257
Heemin Seog1a13af52019-10-03 16:15:50 -0700258 mDeviceProvisionedController.addCallback(
259 new DeviceProvisionedController.DeviceProvisionedListener() {
260 @Override
261 public void onUserSetupChanged() {
262 mHandler.post(() -> restartNavBarsIfNecessary());
263 }
264
265 @Override
266 public void onUserSwitched() {
267 mHandler.post(() -> restartNavBarsIfNecessary());
268 }
269 });
jovanakedba98c2018-09-14 15:46:24 -0700270
Keun young Park9e395bb2019-10-11 20:00:22 -0700271 // Used by onDrivingStateChanged and it can be called inside
272 // DrivingStateHelper.connectToCarService()
273 mSwitchToGuestTimer = new SwitchToGuestTimer(mContext);
274
jovanakedba98c2018-09-14 15:46:24 -0700275 // Register a listener for driving state changes.
276 mDrivingStateHelper = new DrivingStateHelper(mContext, this::onDrivingStateChanged);
277 mDrivingStateHelper.connectToCarService();
278
Jonathan Koo007ff072019-05-10 09:24:08 -0700279 mPowerManagerHelper = new PowerManagerHelper(mContext, mCarPowerStateListener);
280 mPowerManagerHelper.connectToCarService();
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700281 }
282
Heemin Seog1a13af52019-10-03 16:15:50 -0700283 private void restartNavBarsIfNecessary() {
284 boolean currentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
285 if (mDeviceIsSetUpForUser != currentUserSetup) {
286 mDeviceIsSetUpForUser = currentUserSetup;
287 restartNavBars();
288 }
289 }
290
Brad Stenning224b5b32018-03-28 21:26:57 -0700291 /**
292 * Remove all content from navbars and rebuild them. Used to allow for different nav bars
Guobin Zhangeef0e0c2019-02-01 11:54:24 -0800293 * before and after the device is provisioned. . Also for change of density and font size.
Brad Stenning224b5b32018-03-28 21:26:57 -0700294 */
295 private void restartNavBars() {
Brad Stenningf084d882018-08-08 13:34:21 -0700296 // remove and reattach all hvac components such that we don't keep a reference to unused
297 // ui elements
298 mHvacController.removeAllComponents();
Brad Stenning224b5b32018-03-28 21:26:57 -0700299 mCarFacetButtonController.removeAll();
Heemin Seog1a13af52019-10-03 16:15:50 -0700300
Brad Stenning224b5b32018-03-28 21:26:57 -0700301 if (mNavigationBarWindow != null) {
302 mNavigationBarWindow.removeAllViews();
303 mNavigationBarView = null;
304 }
305
306 if (mLeftNavigationBarWindow != null) {
307 mLeftNavigationBarWindow.removeAllViews();
308 mLeftNavigationBarView = null;
309 }
310
311 if (mRightNavigationBarWindow != null) {
312 mRightNavigationBarWindow.removeAllViews();
313 mRightNavigationBarView = null;
314 }
Brad Stenningf084d882018-08-08 13:34:21 -0700315
Brad Stenning224b5b32018-03-28 21:26:57 -0700316 buildNavBarContent();
Brad Stenning75221642019-03-13 07:33:24 -0700317 // If the UI was rebuilt (day/night change) while the keyguard was up we need to
318 // correctly respect that state.
319 if (mIsKeyguard) {
320 updateNavBarForKeyguardContent();
321 }
Brad Stenning4534f442019-04-01 21:43:32 -0700322 // CarFacetButtonController was reset therefore we need to re-add the status bar elements
323 // to the controller.
324 mCarFacetButtonController.addAllFacetButtons(mStatusBarWindow);
Brad Stenning224b5b32018-03-28 21:26:57 -0700325 }
326
Brad Stenningf084d882018-08-08 13:34:21 -0700327 private void addTemperatureViewToController(View v) {
328 if (v instanceof TemperatureView) {
Brad Stenningf084d882018-08-08 13:34:21 -0700329 mHvacController.addHvacTextView((TemperatureView) v);
330 } else if (v instanceof ViewGroup) {
331 ViewGroup viewGroup = (ViewGroup) v;
332 for (int i = 0; i < viewGroup.getChildCount(); i++) {
333 addTemperatureViewToController(viewGroup.getChildAt(i));
334 }
335 }
336 }
337
Brad Stenning7e411812018-04-13 22:52:39 -0700338 /**
339 * Allows for showing or hiding just the navigation bars. This is indented to be used when
340 * the full screen user selector is shown.
341 */
davidln54739152018-07-02 13:25:44 -0700342 void setNavBarVisibility(@View.Visibility int visibility) {
Brad Stenning7e411812018-04-13 22:52:39 -0700343 if (mNavigationBarWindow != null) {
344 mNavigationBarWindow.setVisibility(visibility);
345 }
346 if (mLeftNavigationBarWindow != null) {
347 mLeftNavigationBarWindow.setVisibility(visibility);
348 }
349 if (mRightNavigationBarWindow != null) {
350 mRightNavigationBarWindow.setVisibility(visibility);
351 }
352 }
353
354
355 @Override
356 public boolean hideKeyguard() {
357 boolean result = super.hideKeyguard();
358 if (mNavigationBarView != null) {
359 mNavigationBarView.hideKeyguardButtons();
360 }
361 if (mLeftNavigationBarView != null) {
362 mLeftNavigationBarView.hideKeyguardButtons();
363 }
364 if (mRightNavigationBarView != null) {
365 mRightNavigationBarView.hideKeyguardButtons();
366 }
367 return result;
368 }
369
Brad Stenning7e411812018-04-13 22:52:39 -0700370 @Override
371 public void showKeyguard() {
372 super.showKeyguard();
Brad Stenning75221642019-03-13 07:33:24 -0700373 updateNavBarForKeyguardContent();
374 }
375
376 /**
377 * Switch to the keyguard applicable content contained in the nav bars
378 */
379 private void updateNavBarForKeyguardContent() {
Brad Stenning7e411812018-04-13 22:52:39 -0700380 if (mNavigationBarView != null) {
381 mNavigationBarView.showKeyguardButtons();
382 }
383 if (mLeftNavigationBarView != null) {
384 mLeftNavigationBarView.showKeyguardButtons();
385 }
386 if (mRightNavigationBarView != null) {
387 mRightNavigationBarView.showKeyguardButtons();
388 }
389 }
Brad Stenning224b5b32018-03-28 21:26:57 -0700390
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700391 @Override
Matthew Ng94380652019-04-08 13:43:07 -0700392 protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
393 super.makeStatusBarView(result);
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700394
Guobin Zhangeef0e0c2019-02-01 11:54:24 -0800395 CarSystemUIFactory factory = SystemUIFactory.getInstance();
396 mCarFacetButtonController = factory.getCarDependencyComponent()
davidln1ceedb52019-05-30 14:25:01 -0700397 .getCarFacetButtonController();
Anthony Chene658cc22017-04-27 11:17:35 -0700398 mNotificationPanelBackground = getDefaultWallpaper();
399 mScrimController.setScrimBehindDrawable(mNotificationPanelBackground);
400
Jason Monkaa573e92017-01-27 17:00:29 -0500401 FragmentHostManager manager = FragmentHostManager.get(mStatusBarWindow);
402 manager.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
Anthony Chene658cc22017-04-27 11:17:35 -0700403 mBatteryMeterView = fragment.getView().findViewById(R.id.battery);
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700404
Jason Monkaa573e92017-01-27 17:00:29 -0500405 // By default, the BatteryMeterView should not be visible. It will be toggled
406 // when a device has connected by bluetooth.
407 mBatteryMeterView.setVisibility(View.GONE);
Jason Monkaa573e92017-01-27 17:00:29 -0500408 });
Brad Stenning3b0d7642019-03-28 11:04:30 -0700409
410 connectNotificationsUI();
411 }
412
413 /**
414 * Attach the notification listeners and controllers to the UI as well as build all the
415 * touch listeners needed for opening and closing the notification panel
416 */
417 private void connectNotificationsUI() {
Heemin Seog1a13af52019-10-03 16:15:50 -0700418 // Attached to the top navigation bar (i.e. status bar) to detect pull down of the
419 // notification shade.
Brad Stenning3b0d7642019-03-28 11:04:30 -0700420 GestureDetector openGestureDetector = new GestureDetector(mContext,
421 new OpenNotificationGestureListener() {
422 @Override
423 protected void openNotification() {
424 animateExpandNotificationsPanel();
425 }
426 });
427 // Attached to the notification ui to detect close request of the notification shade.
428 GestureDetector closeGestureDetector = new GestureDetector(mContext,
429 new CloseNotificationGestureListener() {
430 @Override
431 protected void close() {
Brad Stenning44d39072019-04-20 11:53:17 -0700432 if (mPanelExpanded) {
433 animateCollapsePanels();
434 }
Brad Stenning3b0d7642019-03-28 11:04:30 -0700435 }
436 });
437 // Attached to the NavBars to close the notification shade
438 GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(mContext,
439 new NavBarCloseNotificationGestureListener() {
440 @Override
441 protected void close() {
Brad Stenning44d39072019-04-20 11:53:17 -0700442 if (mPanelExpanded) {
443 animateCollapsePanels();
444 }
Brad Stenning3b0d7642019-03-28 11:04:30 -0700445 }
446 });
Priyank Singha47b6f72019-05-28 13:02:47 -0700447
448 // Attached to the Handle bar to close the notification shade
449 GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext,
450 new HandleBarCloseNotificationGestureListener());
451
Heemin Seog1a13af52019-10-03 16:15:50 -0700452 mTopNavBarNotificationTouchListener = (v, event) -> {
453 if (!mDeviceIsSetUpForUser) {
454 return true;
455 }
456 boolean consumed = openGestureDetector.onTouchEvent(event);
457 if (consumed) {
458 return true;
459 }
460 maybeCompleteAnimation(event);
461 return true;
462 };
463
Brad Stenning3b0d7642019-03-28 11:04:30 -0700464 mNavBarNotificationTouchListener =
Priyank Singhb0241582019-04-19 18:46:26 -0700465 (v, event) -> {
466 boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
467 if (consumed) {
468 return true;
469 }
Priyank Singha47b6f72019-05-28 13:02:47 -0700470 maybeCompleteAnimation(event);
Priyank Singhb0241582019-04-19 18:46:26 -0700471 return true;
472 };
Brad Stenning3b0d7642019-03-28 11:04:30 -0700473
Jonathan Koo007ff072019-05-10 09:24:08 -0700474 mNotificationClickHandlerFactory = new NotificationClickHandlerFactory(
Brad Stenning3b0d7642019-03-28 11:04:30 -0700475 mBarService,
476 launchResult -> {
477 if (launchResult == ActivityManager.START_TASK_TO_FRONT
478 || launchResult == ActivityManager.START_SUCCESS) {
479 animateCollapsePanels();
480 }
481 });
Keun young Park9e395bb2019-10-11 20:00:22 -0700482
Brad Stenning3b0d7642019-03-28 11:04:30 -0700483 CarNotificationListener carNotificationListener = new CarNotificationListener();
Keun young Park9e395bb2019-10-11 20:00:22 -0700484 mCarUxRestrictionManagerWrapper = new CarUxRestrictionManagerWrapper();
485 // This can take time if car service is not ready up to this time.
486 // TODO(b/142808072) Refactor CarUxRestrictionManagerWrapper to allow setting
487 // CarUxRestrictionsManager later and switch to Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT.
488 Car.createCar(mContext, /* handler= */ null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER,
489 (car, ready) -> {
490 if (!ready) {
491 return;
492 }
493 CarUxRestrictionsManager carUxRestrictionsManager =
494 (CarUxRestrictionsManager)
495 car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
496 mCarUxRestrictionManagerWrapper.setCarUxRestrictionsManager(
497 carUxRestrictionsManager);
498 });
Jonathan Kooc9f71aa2019-05-06 08:20:14 -0700499
Jonathan Koo007ff072019-05-10 09:24:08 -0700500 mNotificationDataManager = new NotificationDataManager();
501 mNotificationDataManager.setOnUnseenCountUpdateListener(
Jonathan Kooc9f71aa2019-05-06 08:20:14 -0700502 () -> {
Jonathan Koo307bda92019-05-14 16:53:48 -0700503 if (mNavigationBarView != null && mNotificationDataManager != null) {
504 Boolean hasUnseen =
505 mNotificationDataManager.getUnseenNotificationCount() > 0;
506 if (mNavigationBarView != null) {
507 mNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
508 }
509
510 if (mLeftNavigationBarView != null) {
511 mLeftNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
512 }
513
514 if (mRightNavigationBarView != null) {
515 mRightNavigationBarView.toggleNotificationUnseenIndicator(hasUnseen);
516 }
517 }
Jonathan Kooc9f71aa2019-05-06 08:20:14 -0700518 });
519
Priyank Singh8bd920f2019-06-04 15:21:46 -0700520 mEnableHeadsUpNotificationWhenNotificationShadeOpen = mContext.getResources().getBoolean(
521 R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen);
Brad Stenning2ccc2a42019-04-15 10:17:01 -0700522 CarHeadsUpNotificationManager carHeadsUpNotificationManager =
Jonathan Koo007ff072019-05-10 09:24:08 -0700523 new CarSystemUIHeadsUpNotificationManager(mContext,
524 mNotificationClickHandlerFactory, mNotificationDataManager);
525 mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
Brad Stenning2ccc2a42019-04-15 10:17:01 -0700526
Keun young Park9e395bb2019-10-11 20:00:22 -0700527 carNotificationListener.registerAsSystemService(mContext, mCarUxRestrictionManagerWrapper,
Jonathan Koo007ff072019-05-10 09:24:08 -0700528 carHeadsUpNotificationManager, mNotificationDataManager);
Brad Stenning3b0d7642019-03-28 11:04:30 -0700529
530 mNotificationView = mStatusBarWindow.findViewById(R.id.notification_view);
531 View glassPane = mStatusBarWindow.findViewById(R.id.glass_pane);
Priyank Singh647f9e62019-05-16 15:19:53 -0700532 mHandleBar = mStatusBarWindow.findViewById(R.id.handle_bar);
Jonathan Koo007ff072019-05-10 09:24:08 -0700533 mNotificationView.setClickHandlerFactory(mNotificationClickHandlerFactory);
534 mNotificationView.setNotificationDataManager(mNotificationDataManager);
Brad Stenning3b0d7642019-03-28 11:04:30 -0700535
536 // The glass pane is used to view touch events before passed to the notification list.
537 // This allows us to initialize gesture listeners and detect when to close the notifications
538 glassPane.setOnTouchListener((v, event) -> {
539 if (event.getActionMasked() == MotionEvent.ACTION_UP) {
540 mNotificationListAtBottomAtTimeOfTouch = false;
541 }
542 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
Priyank Singhb0241582019-04-19 18:46:26 -0700543 mFirstTouchDownOnGlassPane = event.getRawX();
Brad Stenning3b0d7642019-03-28 11:04:30 -0700544 mNotificationListAtBottomAtTimeOfTouch = mNotificationListAtBottom;
Priyank Singhb0241582019-04-19 18:46:26 -0700545 // Reset the tracker when there is a touch down on the glass pane.
546 mIsTracking = false;
Brad Stenning3b0d7642019-03-28 11:04:30 -0700547 // Pass the down event to gesture detector so that it knows where the touch event
548 // started.
549 closeGestureDetector.onTouchEvent(event);
550 }
551 return false;
552 });
Priyank Singha47b6f72019-05-28 13:02:47 -0700553
554 mHandleBar.setOnTouchListener((v, event) -> {
555 handleBarCloseNotificationGestureDetector.onTouchEvent(event);
556 maybeCompleteAnimation(event);
557 return true;
558 });
559
Brad Stenning3b0d7642019-03-28 11:04:30 -0700560 mNotificationList = mNotificationView.findViewById(R.id.notifications);
561 mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() {
562 @Override
563 public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
564 super.onScrolled(recyclerView, dx, dy);
565 if (!mNotificationList.canScrollVertically(1)) {
566 mNotificationListAtBottom = true;
567 return;
568 }
569 mNotificationListAtBottom = false;
Priyank Singhb0241582019-04-19 18:46:26 -0700570 mIsSwipingVerticallyToClose = false;
Brad Stenning3b0d7642019-03-28 11:04:30 -0700571 mNotificationListAtBottomAtTimeOfTouch = false;
572 }
573 });
574 mNotificationList.setOnTouchListener(new View.OnTouchListener() {
575 @Override
576 public boolean onTouch(View v, MotionEvent event) {
Priyank Singhb0241582019-04-19 18:46:26 -0700577 mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX())
578 > SWIPE_MAX_OFF_PATH;
579 if (mNotificationListAtBottomAtTimeOfTouch && mNotificationListAtBottom) {
580 // We need to save the state here as if notification card is swiping we will
581 // change the mNotificationListAtBottomAtTimeOfTouch. This is to protect
582 // closing the notification shade while the notification card is being swiped.
583 mIsSwipingVerticallyToClose = true;
584 }
585
586 // If the card is swiping we should not allow the notification shade to close.
587 // Hence setting mNotificationListAtBottomAtTimeOfTouch to false will stop that
588 // for us. We are also checking for mIsTracking because while swiping the
589 // notification shade to close if the user goes a bit horizontal while swiping
590 // upwards then also this should close.
591 if (mIsNotificationCardSwiping && !mIsTracking) {
592 mNotificationListAtBottomAtTimeOfTouch = false;
593 }
594
Priyank Singhbae09ae2019-04-25 18:17:02 -0700595 boolean handled = closeGestureDetector.onTouchEvent(event);
Priyank Singhb0241582019-04-19 18:46:26 -0700596 boolean isTracking = mIsTracking;
Priyank Singh647f9e62019-05-16 15:19:53 -0700597 Rect rect = mNotificationView.getClipBounds();
Priyank Singhf3912862019-04-22 10:41:58 -0700598 float clippedHeight = 0;
599 if (rect != null) {
600 clippedHeight = rect.bottom;
601 }
Priyank Singhb0241582019-04-19 18:46:26 -0700602 if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP
603 && mIsSwipingVerticallyToClose) {
604 if (mSettleClosePercentage < mPercentageFromBottom && isTracking) {
605 animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
606 } else if (clippedHeight != mNotificationView.getHeight() && isTracking) {
607 // this can be caused when user is at the end of the list and trying to
608 // fling to top of the list by scrolling down.
609 animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
610 }
611 }
612
Brad Stenning3b0d7642019-03-28 11:04:30 -0700613 // Updating the mNotificationListAtBottomAtTimeOfTouch state has to be done after
614 // the event has been passed to the closeGestureDetector above, such that the
615 // closeGestureDetector sees the up event before the state has changed.
616 if (event.getActionMasked() == MotionEvent.ACTION_UP) {
617 mNotificationListAtBottomAtTimeOfTouch = false;
618 }
Priyank Singhb0241582019-04-19 18:46:26 -0700619 return handled || isTracking;
Brad Stenning3b0d7642019-03-28 11:04:30 -0700620 }
621 });
622
Adora Zhangdaea9112019-04-11 17:47:40 -0700623 mNotificationViewController = new NotificationViewController(
Brad Stenning3b0d7642019-03-28 11:04:30 -0700624 mNotificationView,
625 PreprocessingManager.getInstance(mContext),
626 carNotificationListener,
Keun young Park9e395bb2019-10-11 20:00:22 -0700627 mCarUxRestrictionManagerWrapper,
Jonathan Koo007ff072019-05-10 09:24:08 -0700628 mNotificationDataManager);
Brad Stenning3b0d7642019-03-28 11:04:30 -0700629 mNotificationViewController.enable();
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700630 }
631
Brad Stenning44d39072019-04-20 11:53:17 -0700632 /**
633 * @return true if the notification panel is currently visible
634 */
635 boolean isNotificationPanelOpen() {
636 return mPanelExpanded;
637 }
638
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800639 @Override
Brad Stenning3b0d7642019-03-28 11:04:30 -0700640 public void animateExpandNotificationsPanel() {
641 if (!mCommandQueue.panelsEnabled() || !mUserSetup) {
642 return;
643 }
644 // scroll to top
645 mNotificationList.scrollToPosition(0);
646 mStatusBarWindowController.setPanelVisible(true);
647 mNotificationView.setVisibility(View.VISIBLE);
Priyank Singhb0241582019-04-19 18:46:26 -0700648 animateNotificationPanel(mOpeningVelocity, false);
649
Brad Stenning3b0d7642019-03-28 11:04:30 -0700650 setPanelExpanded(true);
651 }
652
653 @Override
654 public void animateCollapsePanels(int flags, boolean force, boolean delayed,
655 float speedUpFactor) {
656 super.animateCollapsePanels(flags, force, delayed, speedUpFactor);
657 if (!mPanelExpanded || mNotificationView.getVisibility() == View.INVISIBLE) {
658 return;
659 }
660 mStatusBarWindowController.setStatusBarFocusable(false);
661 mStatusBarWindow.cancelExpandHelper();
662 mStatusBarView.collapsePanel(true /* animate */, delayed, speedUpFactor);
Priyank Singhb0241582019-04-19 18:46:26 -0700663
664 animateNotificationPanel(mClosingVelocity, true);
665
666 if (!mIsTracking) {
667 mStatusBarWindowController.setPanelVisible(false);
668 mNotificationView.setVisibility(View.INVISIBLE);
669 }
670
Brad Stenning3b0d7642019-03-28 11:04:30 -0700671 setPanelExpanded(false);
672 }
673
Priyank Singha47b6f72019-05-28 13:02:47 -0700674 private void maybeCompleteAnimation(MotionEvent event) {
675 if (event.getActionMasked() == MotionEvent.ACTION_UP
676 && mNotificationView.getVisibility() == View.VISIBLE) {
677 if (mSettleClosePercentage < mPercentageFromBottom) {
678 animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
679 } else {
680 animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
681 }
682 }
683 }
684
Priyank Singhb0241582019-04-19 18:46:26 -0700685 /**
686 * Animates the notification shade from one position to other. This is used to either open or
Priyank Singh245bd122019-04-21 15:42:44 -0700687 * close the notification shade completely with a velocity. If the animation is to close the
Priyank Singhb0241582019-04-19 18:46:26 -0700688 * notification shade this method also makes the view invisible after animation ends.
689 */
690 private void animateNotificationPanel(float velocity, boolean isClosing) {
Priyank Singhb0241582019-04-19 18:46:26 -0700691 float to = 0;
692 if (!isClosing) {
693 to = mNotificationView.getHeight();
694 }
Priyank Singhb9ab0522019-05-03 15:36:25 -0700695
Priyank Singh647f9e62019-05-16 15:19:53 -0700696 Rect rect = mNotificationView.getClipBounds();
Priyank Singhb9ab0522019-05-03 15:36:25 -0700697 if (rect != null) {
698 float from = rect.bottom;
699 animate(from, to, velocity, isClosing);
700 return;
701 }
702
703 // We will only be here if the shade is being opened programmatically.
704 ViewTreeObserver notificationTreeObserver = mNotificationView.getViewTreeObserver();
705 notificationTreeObserver.addOnGlobalLayoutListener(
706 new ViewTreeObserver.OnGlobalLayoutListener() {
707 @Override
708 public void onGlobalLayout() {
709 ViewTreeObserver obs = mNotificationView.getViewTreeObserver();
710 obs.removeOnGlobalLayoutListener(this);
711 float to = mNotificationView.getHeight();
712 animate(/* from= */ 0, to, velocity, isClosing);
713 }
714 });
715 }
716
717 private void animate(float from, float to, float velocity, boolean isClosing) {
Priyank Singhb0241582019-04-19 18:46:26 -0700718 if (mIsNotificationAnimating) {
719 return;
720 }
721 mIsNotificationAnimating = true;
722 mIsTracking = true;
723 ValueAnimator animator = ValueAnimator.ofFloat(from, to);
724 animator.addUpdateListener(
725 animation -> {
726 float animatedValue = (Float) animation.getAnimatedValue();
727 setNotificationViewClipBounds((int) animatedValue);
728 });
729 animator.addListener(new AnimatorListenerAdapter() {
730 @Override
731 public void onAnimationEnd(Animator animation) {
732 super.onAnimationEnd(animation);
733 mIsNotificationAnimating = false;
734 mIsTracking = false;
735 mOpeningVelocity = DEFAULT_FLING_VELOCITY;
736 mClosingVelocity = DEFAULT_FLING_VELOCITY;
737 if (isClosing) {
738 mStatusBarWindowController.setPanelVisible(false);
739 mNotificationView.setVisibility(View.INVISIBLE);
Priyank Singh647f9e62019-05-16 15:19:53 -0700740 mNotificationView.setClipBounds(null);
Adora Zhangdaea9112019-04-11 17:47:40 -0700741 mNotificationViewController.setIsInForeground(false);
Priyank Singhb0241582019-04-19 18:46:26 -0700742 // let the status bar know that the panel is closed
743 setPanelExpanded(false);
744 } else {
Adora Zhangdaea9112019-04-11 17:47:40 -0700745 mNotificationViewController.setIsInForeground(true);
Priyank Singhb0241582019-04-19 18:46:26 -0700746 // let the status bar know that the panel is open
Jonathan Kooc9f71aa2019-05-06 08:20:14 -0700747 mNotificationView.setVisibleNotificationsAsSeen();
Priyank Singhb0241582019-04-19 18:46:26 -0700748 setPanelExpanded(true);
749 }
750 }
751 });
Jonathan Koo007ff072019-05-10 09:24:08 -0700752 mFlingAnimationUtils.apply(animator, from, to, Math.abs(velocity));
Priyank Singhb0241582019-04-19 18:46:26 -0700753 animator.start();
754 }
Brad Stenning3b0d7642019-03-28 11:04:30 -0700755
756 @Override
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800757 protected QS createDefaultQSFragment() {
758 return new CarQSFragment();
759 }
760
Rakesh Iyer545e47d2017-01-23 18:10:21 -0800761 private BatteryController createBatteryController() {
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700762 mCarBatteryController = new CarBatteryController(mContext);
763 mCarBatteryController.addBatteryViewHandler(this);
764 return mCarBatteryController;
Victor Chan1c6d0582016-01-09 16:26:37 -0800765 }
766
Rakesh Iyer1186faa2015-12-07 16:48:46 -0800767 @Override
Matthew Ng94380652019-04-08 13:43:07 -0700768 protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
Brad Stenning078235b2017-12-18 08:25:10 -0800769 mShowBottom = mContext.getResources().getBoolean(R.bool.config_enableBottomNavigationBar);
Brad Stenning078235b2017-12-18 08:25:10 -0800770 mShowLeft = mContext.getResources().getBoolean(R.bool.config_enableLeftNavigationBar);
Brad Stenning078235b2017-12-18 08:25:10 -0800771 mShowRight = mContext.getResources().getBoolean(R.bool.config_enableRightNavigationBar);
772
Brad Stenning224b5b32018-03-28 21:26:57 -0700773 buildNavBarWindows();
774 buildNavBarContent();
775 attachNavBarWindows();
Andrii Kulian3ddd7de2018-10-10 00:33:19 -0700776
Heemin Seog28097c42019-08-15 13:18:41 -0700777 // Try setting up the initial state of the nav bar if applicable.
778 if (result != null) {
779 setImeWindowStatus(Display.DEFAULT_DISPLAY, result.mImeToken,
780 result.mImeWindowVis, result.mImeBackDisposition,
781 result.mShowImeSwitcher);
782 }
783
Charles Chen10ca70b2018-11-28 00:03:38 +0800784 // There has been a car customized nav bar on the default display, so just create nav bars
785 // on external displays.
Matthew Ng94380652019-04-08 13:43:07 -0700786 mNavigationBarController.createNavigationBars(false /* includeDefaultDisplay */, result);
Brad Stenning224b5b32018-03-28 21:26:57 -0700787 }
788
789 private void buildNavBarContent() {
Heemin Seog1a13af52019-10-03 16:15:50 -0700790 // Always build top bar.
791 buildTopBar((mDeviceIsSetUpForUser) ? R.layout.car_top_navigation_bar :
792 R.layout.car_top_navigation_bar_unprovisioned);
793
Brad Stenning224b5b32018-03-28 21:26:57 -0700794 if (mShowBottom) {
Heemin Seog1a13af52019-10-03 16:15:50 -0700795 buildBottomBar((mDeviceIsSetUpForUser) ? R.layout.car_navigation_bar :
Brad Stenning224b5b32018-03-28 21:26:57 -0700796 R.layout.car_navigation_bar_unprovisioned);
797 }
798
799 if (mShowLeft) {
Heemin Seog1a13af52019-10-03 16:15:50 -0700800 buildLeft((mDeviceIsSetUpForUser) ? R.layout.car_left_navigation_bar :
Brad Stenning224b5b32018-03-28 21:26:57 -0700801 R.layout.car_left_navigation_bar_unprovisioned);
802 }
803
Brad Stenning078235b2017-12-18 08:25:10 -0800804 if (mShowRight) {
Heemin Seog1a13af52019-10-03 16:15:50 -0700805 buildRight((mDeviceIsSetUpForUser) ? R.layout.car_right_navigation_bar :
Brad Stenning224b5b32018-03-28 21:26:57 -0700806 R.layout.car_right_navigation_bar_unprovisioned);
807 }
808 }
809
810 private void buildNavBarWindows() {
Heemin Seog1a13af52019-10-03 16:15:50 -0700811 mTopNavigationBarContainer = mStatusBarWindow
812 .findViewById(R.id.car_top_navigation_bar_container);
813
Brad Stenning224b5b32018-03-28 21:26:57 -0700814 if (mShowBottom) {
davidln54739152018-07-02 13:25:44 -0700815 mNavigationBarWindow = (ViewGroup) View.inflate(mContext,
Brad Stenning224b5b32018-03-28 21:26:57 -0700816 R.layout.navigation_bar_window, null);
817 }
818 if (mShowLeft) {
819 mLeftNavigationBarWindow = (ViewGroup) View.inflate(mContext,
davidln54739152018-07-02 13:25:44 -0700820 R.layout.navigation_bar_window, null);
Brad Stenning224b5b32018-03-28 21:26:57 -0700821 }
822 if (mShowRight) {
823 mRightNavigationBarWindow = (ViewGroup) View.inflate(mContext,
824 R.layout.navigation_bar_window, null);
Brad Stenning078235b2017-12-18 08:25:10 -0800825 }
826
827 }
828
Heemin Seog28097c42019-08-15 13:18:41 -0700829 /**
830 * We register for soft keyboard visibility events such that we can hide the navigation bar
831 * giving more screen space to the IME. Note: this is optional and controlled by
832 * {@code com.android.internal.R.bool.config_automotiveHideNavBarForKeyboard}.
833 */
834 @Override
835 public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
836 boolean showImeSwitcher) {
837 if (!mHideNavBarForKeyboard) {
838 return;
Brad Stenning224b5b32018-03-28 21:26:57 -0700839 }
Brad Stenning3b0d7642019-03-28 11:04:30 -0700840
Heemin Seog28097c42019-08-15 13:18:41 -0700841 if (mContext.getDisplay().getDisplayId() != displayId) {
842 return;
843 }
844
845 boolean isKeyboardVisible = (vis & InputMethodService.IME_VISIBLE) != 0;
JianYang Liu2c00fcf2019-10-10 15:37:29 -0700846 showBottomNavBarWindow(isKeyboardVisible);
Heemin Seog28097c42019-08-15 13:18:41 -0700847 }
848
849 private void attachNavBarWindows() {
JianYang Liu2c00fcf2019-10-10 15:37:29 -0700850 if (mShowBottom && !mBottomNavBarVisible) {
851 mBottomNavBarVisible = true;
852
853 WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
854 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
855 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
856 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
857 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
858 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
859 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
860 PixelFormat.TRANSLUCENT);
861 lp.setTitle("CarNavigationBar");
862 lp.windowAnimations = 0;
863 mWindowManager.addView(mNavigationBarWindow, lp);
864 }
Heemin Seog28097c42019-08-15 13:18:41 -0700865
Brad Stenning224b5b32018-03-28 21:26:57 -0700866 if (mShowLeft) {
867 int width = mContext.getResources().getDimensionPixelSize(
868 R.dimen.car_left_navigation_bar_width);
869 WindowManager.LayoutParams leftlp = new WindowManager.LayoutParams(
870 width, LayoutParams.MATCH_PARENT,
871 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
872 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
873 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
874 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
875 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
876 PixelFormat.TRANSLUCENT);
877 leftlp.setTitle("LeftCarNavigationBar");
878 leftlp.windowAnimations = 0;
879 leftlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
880 leftlp.gravity = Gravity.LEFT;
881 mWindowManager.addView(mLeftNavigationBarWindow, leftlp);
882 }
883 if (mShowRight) {
884 int width = mContext.getResources().getDimensionPixelSize(
885 R.dimen.car_right_navigation_bar_width);
886 WindowManager.LayoutParams rightlp = new WindowManager.LayoutParams(
887 width, LayoutParams.MATCH_PARENT,
888 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
889 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
890 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
891 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
892 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
893 PixelFormat.TRANSLUCENT);
894 rightlp.setTitle("RightCarNavigationBar");
895 rightlp.windowAnimations = 0;
896 rightlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
897 rightlp.gravity = Gravity.RIGHT;
898 mWindowManager.addView(mRightNavigationBarWindow, rightlp);
899 }
Heemin Seog28097c42019-08-15 13:18:41 -0700900 }
Brad Stenning224b5b32018-03-28 21:26:57 -0700901
JianYang Liu2c00fcf2019-10-10 15:37:29 -0700902 private void showBottomNavBarWindow(boolean isKeyboardVisible) {
Heemin Seog28097c42019-08-15 13:18:41 -0700903 if (!mShowBottom) {
904 return;
905 }
906
JianYang Liu2c00fcf2019-10-10 15:37:29 -0700907 // If keyboard is visible and bottom nav bar not visible, this is the correct state, so do
908 // nothing. Same with if keyboard is not visible and bottom nav bar is visible.
909 if (isKeyboardVisible ^ mBottomNavBarVisible) {
Heemin Seog28097c42019-08-15 13:18:41 -0700910 return;
911 }
912
JianYang Liu2c00fcf2019-10-10 15:37:29 -0700913 mNavigationBarWindow.setVisibility(isKeyboardVisible ? View.GONE : View.VISIBLE);
914 mBottomNavBarVisible = !isKeyboardVisible;
Brad Stenning224b5b32018-03-28 21:26:57 -0700915 }
916
Heemin Seog1a13af52019-10-03 16:15:50 -0700917 private void buildTopBar(int layout) {
918 mTopNavigationBarContainer.removeAllViews();
919 View.inflate(mContext, layout, mTopNavigationBarContainer);
920 mTopNavigationBarView = (CarNavigationBarView) mTopNavigationBarContainer.getChildAt(0);
921 if (mTopNavigationBarView == null) {
922 Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_top_navigation_bar");
923 throw new RuntimeException("Unable to build top nav bar due to missing layout");
924 }
925 mTopNavigationBarView.setStatusBar(this);
926 addTemperatureViewToController(mTopNavigationBarView);
927 mTopNavigationBarView.setStatusBarWindowTouchListener(mTopNavBarNotificationTouchListener);
928 }
929
Brad Stenning224b5b32018-03-28 21:26:57 -0700930 private void buildBottomBar(int layout) {
Anthony Chen0801a5c2017-03-22 09:54:37 -0700931 // SystemUI requires that the navigation bar view have a parent. Since the regular
932 // StatusBar inflates navigation_bar_window as this parent view, use the same view for the
933 // CarNavigationBarView.
Brad Stenning224b5b32018-03-28 21:26:57 -0700934 View.inflate(mContext, layout, mNavigationBarWindow);
Scott Randolphf4085822017-06-27 15:23:01 -0700935 mNavigationBarView = (CarNavigationBarView) mNavigationBarWindow.getChildAt(0);
936 if (mNavigationBarView == null) {
937 Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
Heemin Seog1a13af52019-10-03 16:15:50 -0700938 throw new RuntimeException("Unable to build bottom nav bar due to missing layout");
Scott Randolphf4085822017-06-27 15:23:01 -0700939 }
Brad Stenning078235b2017-12-18 08:25:10 -0800940 mNavigationBarView.setStatusBar(this);
Brad Stenningf084d882018-08-08 13:34:21 -0700941 addTemperatureViewToController(mNavigationBarView);
Brad Stenning3b0d7642019-03-28 11:04:30 -0700942 mNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
Rakesh Iyer1186faa2015-12-07 16:48:46 -0800943 }
944
Brad Stenning224b5b32018-03-28 21:26:57 -0700945 private void buildLeft(int layout) {
946 View.inflate(mContext, layout, mLeftNavigationBarWindow);
Brad Stenning078235b2017-12-18 08:25:10 -0800947 mLeftNavigationBarView = (CarNavigationBarView) mLeftNavigationBarWindow.getChildAt(0);
948 if (mLeftNavigationBarView == null) {
Heemin Seog1a13af52019-10-03 16:15:50 -0700949 Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_left_navigation_bar");
Brad Stenning078235b2017-12-18 08:25:10 -0800950 throw new RuntimeException("Unable to build left nav bar due to missing layout");
951 }
952 mLeftNavigationBarView.setStatusBar(this);
Brad Stenningf084d882018-08-08 13:34:21 -0700953 addTemperatureViewToController(mLeftNavigationBarView);
Brad Stenning3b0d7642019-03-28 11:04:30 -0700954 mLeftNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
Brad Stenning078235b2017-12-18 08:25:10 -0800955 }
956
957
Brad Stenning224b5b32018-03-28 21:26:57 -0700958 private void buildRight(int layout) {
959 View.inflate(mContext, layout, mRightNavigationBarWindow);
Brad Stenning078235b2017-12-18 08:25:10 -0800960 mRightNavigationBarView = (CarNavigationBarView) mRightNavigationBarWindow.getChildAt(0);
961 if (mRightNavigationBarView == null) {
Heemin Seog1a13af52019-10-03 16:15:50 -0700962 Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_right_navigation_bar");
Brad Stenning078235b2017-12-18 08:25:10 -0800963 throw new RuntimeException("Unable to build right nav bar due to missing layout");
964 }
Brad Stenningf084d882018-08-08 13:34:21 -0700965 mRightNavigationBarView.setStatusBar(this);
966 addTemperatureViewToController(mRightNavigationBarView);
Brad Stenning3b0d7642019-03-28 11:04:30 -0700967 mRightNavigationBarView.setStatusBarWindowTouchListener(mNavBarNotificationTouchListener);
Brad Stenning078235b2017-12-18 08:25:10 -0800968 }
969
Rakesh Iyer1186faa2015-12-07 16:48:46 -0800970 @Override
Qiming Shib9dfb922017-04-13 16:23:18 +0800971 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Priyank Singhb0241582019-04-19 18:46:26 -0700972 //When executing dump() function simultaneously, we need to serialize them
Qiming Shib9dfb922017-04-13 16:23:18 +0800973 //to get mStackScroller's position correctly.
974 synchronized (mQueueLock) {
975 pw.println(" mStackScroller: " + viewInfo(mStackScroller));
976 pw.println(" mStackScroller: " + viewInfo(mStackScroller)
977 + " scroll " + mStackScroller.getScrollX()
978 + "," + mStackScroller.getScrollY());
979 }
980
davidln54739152018-07-02 13:25:44 -0700981 pw.print(" mTaskStackListener=");
982 pw.println(mTaskStackListener);
Brad Stenning078235b2017-12-18 08:25:10 -0800983 pw.print(" mCarFacetButtonController=");
984 pw.println(mCarFacetButtonController);
davidln54739152018-07-02 13:25:44 -0700985 pw.print(" mFullscreenUserSwitcher=");
986 pw.println(mFullscreenUserSwitcher);
Qiming Shib9dfb922017-04-13 16:23:18 +0800987 pw.print(" mCarBatteryController=");
988 pw.println(mCarBatteryController);
989 pw.print(" mBatteryMeterView=");
990 pw.println(mBatteryMeterView);
Qiming Shib9dfb922017-04-13 16:23:18 +0800991 pw.print(" mNavigationBarView=");
992 pw.println(mNavigationBarView);
993
994 if (KeyguardUpdateMonitor.getInstance(mContext) != null) {
995 KeyguardUpdateMonitor.getInstance(mContext).dump(fd, pw, args);
996 }
997
Dave Mankoff781ef7e2019-06-28 16:33:25 -0400998 Dependency.get(FalsingManager.class).dump(pw);
Qiming Shib9dfb922017-04-13 16:23:18 +0800999 FalsingLog.dump(pw);
1000
1001 pw.println("SharedPreferences:");
1002 for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) {
davidln54739152018-07-02 13:25:44 -07001003 pw.print(" ");
1004 pw.print(entry.getKey());
1005 pw.print("=");
1006 pw.println(entry.getValue());
Qiming Shib9dfb922017-04-13 16:23:18 +08001007 }
1008 }
1009
Anthony Chen3cb3ad92016-12-01 10:58:47 -08001010 @Override
Anthony Chenda62fdcd52016-04-06 16:15:14 -07001011 public void showBatteryView() {
1012 if (Log.isLoggable(TAG, Log.DEBUG)) {
1013 Log.d(TAG, "showBatteryView(). mBatteryMeterView: " + mBatteryMeterView);
1014 }
1015
1016 if (mBatteryMeterView != null) {
1017 mBatteryMeterView.setVisibility(View.VISIBLE);
1018 }
1019 }
1020
1021 @Override
1022 public void hideBatteryView() {
1023 if (Log.isLoggable(TAG, Log.DEBUG)) {
1024 Log.d(TAG, "hideBatteryView(). mBatteryMeterView: " + mBatteryMeterView);
1025 }
1026
1027 if (mBatteryMeterView != null) {
1028 mBatteryMeterView.setVisibility(View.GONE);
1029 }
1030 }
1031
Victor Chan1c6d0582016-01-09 16:26:37 -08001032 /**
Winson Chung67f5c8b2018-09-24 12:09:19 -07001033 * An implementation of TaskStackChangeListener, that listens for changes in the system
davidln54739152018-07-02 13:25:44 -07001034 * task stack and notifies the navigation bar.
Victor Chan1c6d0582016-01-09 16:26:37 -08001035 */
Winson Chung67f5c8b2018-09-24 12:09:19 -07001036 private class TaskStackListenerImpl extends TaskStackChangeListener {
Wale Ogunwale480dca02016-02-06 13:58:29 -08001037 @Override
Victor Chan1c6d0582016-01-09 16:26:37 -08001038 public void onTaskStackChanged() {
Brad Stenninga5e714c2018-04-12 19:23:39 -07001039 try {
1040 mCarFacetButtonController.taskChanged(
Wale Ogunwale04d9cb52018-04-30 13:55:07 -07001041 ActivityTaskManager.getService().getAllStackInfos());
Brad Stenninga5e714c2018-04-12 19:23:39 -07001042 } catch (Exception e) {
1043 Log.e(TAG, "Getting StackInfo from activity manager failed", e);
1044 }
Victor Chan1c6d0582016-01-09 16:26:37 -08001045 }
davidln1ceedb52019-05-30 14:25:01 -07001046
1047 @Override
1048 public void onTaskDisplayChanged(int taskId, int newDisplayId) {
1049 try {
1050 mCarFacetButtonController.taskChanged(
1051 ActivityTaskManager.getService().getAllStackInfos());
1052 } catch (Exception e) {
1053 Log.e(TAG, "Getting StackInfo from activity manager failed", e);
1054 }
1055 }
Victor Chan1c6d0582016-01-09 16:26:37 -08001056 }
Rakesh Iyer2790a372016-01-22 15:33:39 -08001057
jovanakedba98c2018-09-14 15:46:24 -07001058 private void onDrivingStateChanged(CarDrivingStateEvent notUsed) {
1059 // Check if we need to start the timer every time driving state changes.
1060 startSwitchToGuestTimerIfDrivingOnKeyguard();
1061 }
1062
1063 private void startSwitchToGuestTimerIfDrivingOnKeyguard() {
1064 if (mDrivingStateHelper.isCurrentlyDriving() && mState != StatusBarState.SHADE) {
1065 // We're driving while keyguard is up.
1066 mSwitchToGuestTimer.start();
1067 } else {
1068 mSwitchToGuestTimer.cancel();
1069 }
1070 }
1071
Rakesh Iyer2790a372016-01-22 15:33:39 -08001072 @Override
1073 protected void createUserSwitcher() {
Jason Monk9c7844c2017-01-18 15:21:53 -05001074 UserSwitcherController userSwitcherController =
1075 Dependency.get(UserSwitcherController.class);
1076 if (userSwitcherController.useFullscreenUserSwitcher()) {
1077 mFullscreenUserSwitcher = new FullscreenUserSwitcher(this,
Keun young Park9e395bb2019-10-11 20:00:22 -07001078 mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub), mContext);
Rakesh Iyer2790a372016-01-22 15:33:39 -08001079 } else {
1080 super.createUserSwitcher();
1081 }
1082 }
1083
1084 @Override
jovanak168dbdb2019-02-18 01:00:43 -08001085 public void setLockscreenUser(int newUserId) {
1086 super.setLockscreenUser(newUserId);
1087 // Try to dismiss the keyguard after every user switch.
1088 dismissKeyguardWhenUserSwitcherNotDisplayed();
1089 }
1090
1091 @Override
Jason Monk1fd3fc32018-08-14 17:20:09 -04001092 public void onStateChanged(int newState) {
1093 super.onStateChanged(newState);
jovanakedba98c2018-09-14 15:46:24 -07001094
1095 startSwitchToGuestTimerIfDrivingOnKeyguard();
1096
jovanak168dbdb2019-02-18 01:00:43 -08001097 if (newState != StatusBarState.FULLSCREEN_USER_SWITCHER) {
1098 hideUserSwitcher();
jovanak604ad512018-08-14 18:41:27 -07001099 } else {
jovanak168dbdb2019-02-18 01:00:43 -08001100 dismissKeyguardWhenUserSwitcherNotDisplayed();
1101 }
1102 }
1103
1104 /** Makes the full screen user switcher visible, if applicable. */
1105 public void showUserSwitcher() {
1106 if (mFullscreenUserSwitcher != null && mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
1107 mFullscreenUserSwitcher.show(); // Makes the switcher visible.
1108 }
1109 }
1110
1111 private void hideUserSwitcher() {
1112 if (mFullscreenUserSwitcher != null) {
jovanak604ad512018-08-14 18:41:27 -07001113 mFullscreenUserSwitcher.hide();
Aarthi Balachander0a427ef2018-07-13 15:00:58 -07001114 }
1115 }
1116
jovanak622def22019-06-11 11:51:54 -07001117 final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
1118 @Override
1119 public void onScreenTurnedOn() {
1120 dismissKeyguardWhenUserSwitcherNotDisplayed();
1121 }
1122 };
1123
jovanak168dbdb2019-02-18 01:00:43 -08001124 // We automatically dismiss keyguard unless user switcher is being shown on the keyguard.
1125 private void dismissKeyguardWhenUserSwitcherNotDisplayed() {
1126 if (mFullscreenUserSwitcher == null) {
1127 return; // Not using the full screen user switcher.
1128 }
1129
1130 if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER
1131 && !mFullscreenUserSwitcher.isVisible()) {
1132 // Current execution path continues to set state after this, thus we deffer the
1133 // dismissal to the next execution cycle.
1134 postDismissKeyguard(); // Dismiss the keyguard if switcher is not visible.
Rakesh Iyer2790a372016-01-22 15:33:39 -08001135 }
1136 }
Rakesh Iyer74ebabe2016-03-11 10:10:43 -08001137
jovanak604ad512018-08-14 18:41:27 -07001138 public void postDismissKeyguard() {
1139 mHandler.post(this::dismissKeyguard);
1140 }
1141
1142 /**
1143 * Dismisses the keyguard and shows bouncer if authentication is necessary.
1144 */
1145 public void dismissKeyguard() {
jovanak622def22019-06-11 11:51:54 -07001146 // Don't dismiss keyguard when the screen is off.
1147 if (mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_OFF) {
1148 return;
1149 }
jovanak604ad512018-08-14 18:41:27 -07001150 executeRunnableDismissingKeyguard(null/* runnable */, null /* cancelAction */,
Brad Stenning8d1a51c2018-11-20 17:34:16 -08001151 true /* dismissShade */, true /* afterKeyguardGone */, true /* deferred */);
jovanak604ad512018-08-14 18:41:27 -07001152 }
1153
Anthony Chen3cb3ad92016-12-01 10:58:47 -08001154 /**
1155 * Ensures that relevant child views are appropriately recreated when the device's density
1156 * changes.
1157 */
1158 @Override
Jason Monk9cac5ff2017-06-02 11:35:50 -04001159 public void onDensityOrFontScaleChanged() {
Anthony Chen3cb3ad92016-12-01 10:58:47 -08001160 super.onDensityOrFontScaleChanged();
Guobin Zhangeef0e0c2019-02-01 11:54:24 -08001161 restartNavBars();
Anthony Chene658cc22017-04-27 11:17:35 -07001162 // Need to update the background on density changed in case the change was due to night
1163 // mode.
1164 mNotificationPanelBackground = getDefaultWallpaper();
1165 mScrimController.setScrimBehindDrawable(mNotificationPanelBackground);
1166 }
1167
1168 /**
1169 * Returns the {@link Drawable} that represents the wallpaper that the user has currently set.
1170 */
1171 private Drawable getDefaultWallpaper() {
1172 return mContext.getDrawable(com.android.internal.R.drawable.default_wallpaper);
Anthony Chen3cb3ad92016-12-01 10:58:47 -08001173 }
Brad Stenning19f236a2018-12-11 14:12:30 -08001174
Priyank Singhb0241582019-04-19 18:46:26 -07001175 private void setNotificationViewClipBounds(int height) {
Priyank Singhea4af132019-05-01 17:57:06 -07001176 if (height > mNotificationView.getHeight()) {
1177 height = mNotificationView.getHeight();
1178 }
Priyank Singhb0241582019-04-19 18:46:26 -07001179 Rect clipBounds = new Rect();
1180 clipBounds.set(0, 0, mNotificationView.getWidth(), height);
Priyank Singh245bd122019-04-21 15:42:44 -07001181 // Sets the clip region on the notification list view.
Priyank Singh647f9e62019-05-16 15:19:53 -07001182 mNotificationView.setClipBounds(clipBounds);
1183 if (mHandleBar != null) {
1184 ViewGroup.MarginLayoutParams lp =
1185 (ViewGroup.MarginLayoutParams) mHandleBar.getLayoutParams();
1186 mHandleBar.setTranslationY(height - mHandleBar.getHeight() - lp.bottomMargin);
1187 }
Priyank Singh245bd122019-04-21 15:42:44 -07001188 if (mNotificationView.getHeight() > 0) {
Brad Stenning8650f412019-07-16 16:46:28 -07001189 Drawable background = mNotificationView.getBackground().mutate();
JianYang Liub923d1e2019-10-24 17:19:44 -07001190 background.setAlpha((int) (getBackgroundAlpha(height) * 255));
Brad Stenning8650f412019-07-16 16:46:28 -07001191 mNotificationView.setBackground(background);
Priyank Singh245bd122019-04-21 15:42:44 -07001192 }
Priyank Singhb0241582019-04-19 18:46:26 -07001193 }
Brad Stenning3b0d7642019-03-28 11:04:30 -07001194
JianYang Liub923d1e2019-10-24 17:19:44 -07001195 /**
1196 * Calculates the alpha value for the background based on how much of the notification
1197 * shade is visible to the user. When the notification shade is completely open then
1198 * alpha value will be 1.
1199 */
1200 private float getBackgroundAlpha(int height) {
1201 return mInitialBackgroundAlpha +
1202 ((float) height / mNotificationView.getHeight() * mBackgroundAlphaDiff);
1203 }
1204
Priyank Singhb0241582019-04-19 18:46:26 -07001205 private void calculatePercentageFromBottom(float height) {
1206 if (mNotificationView.getHeight() > 0) {
1207 mPercentageFromBottom = (int) Math.abs(
1208 height / mNotificationView.getHeight() * 100);
1209 }
1210 }
1211
Brad Stenning3b0d7642019-03-28 11:04:30 -07001212 private static final int SWIPE_DOWN_MIN_DISTANCE = 25;
1213 private static final int SWIPE_MAX_OFF_PATH = 75;
1214 private static final int SWIPE_THRESHOLD_VELOCITY = 200;
Priyank Singhb0241582019-04-19 18:46:26 -07001215
Priyank Singha47b6f72019-05-28 13:02:47 -07001216 /**
1217 * Only responsible for open hooks. Since once the panel opens it covers all elements
1218 * there is no need to merge with close.
1219 */
Brad Stenning3b0d7642019-03-28 11:04:30 -07001220 private abstract class OpenNotificationGestureListener extends
1221 GestureDetector.SimpleOnGestureListener {
1222
1223 @Override
Priyank Singhb0241582019-04-19 18:46:26 -07001224 public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
1225 float distanceY) {
1226
1227 if (mNotificationView.getVisibility() == View.INVISIBLE) {
1228 // when the on-scroll is called for the first time to open.
1229 mNotificationList.scrollToPosition(0);
1230 }
1231 mStatusBarWindowController.setPanelVisible(true);
1232 mNotificationView.setVisibility(View.VISIBLE);
1233
1234 // clips the view for the notification shade when the user scrolls to open.
1235 setNotificationViewClipBounds((int) event2.getRawY());
1236
1237 // Initially the scroll starts with height being zero. This checks protects from divide
1238 // by zero error.
1239 calculatePercentageFromBottom(event2.getRawY());
1240
1241 mIsTracking = true;
1242 return true;
1243 }
1244
1245
1246 @Override
Brad Stenning3b0d7642019-03-28 11:04:30 -07001247 public boolean onFling(MotionEvent event1, MotionEvent event2,
1248 float velocityX, float velocityY) {
Priyank Singhb0241582019-04-19 18:46:26 -07001249 if (velocityY > SWIPE_THRESHOLD_VELOCITY) {
1250 mOpeningVelocity = velocityY;
Brad Stenning3b0d7642019-03-28 11:04:30 -07001251 openNotification();
1252 return true;
1253 }
Priyank Singhb0241582019-04-19 18:46:26 -07001254 animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
Brad Stenning3b0d7642019-03-28 11:04:30 -07001255
1256 return false;
1257 }
Priyank Singhb0241582019-04-19 18:46:26 -07001258
Brad Stenning3b0d7642019-03-28 11:04:30 -07001259 protected abstract void openNotification();
Brad Stenning19f236a2018-12-11 14:12:30 -08001260 }
1261
Priyank Singha47b6f72019-05-28 13:02:47 -07001262 /**
1263 * To be installed on the open panel notification panel
1264 */
Brad Stenning3b0d7642019-03-28 11:04:30 -07001265 private abstract class CloseNotificationGestureListener extends
1266 GestureDetector.SimpleOnGestureListener {
1267
1268 @Override
Priyank Singhbae09ae2019-04-25 18:17:02 -07001269 public boolean onSingleTapUp(MotionEvent motionEvent) {
Priyank Singhb9ab0522019-05-03 15:36:25 -07001270 if (mPanelExpanded) {
1271 animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
1272 }
1273 return true;
Priyank Singhbae09ae2019-04-25 18:17:02 -07001274 }
1275
1276 @Override
Priyank Singhb0241582019-04-19 18:46:26 -07001277 public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
1278 float distanceY) {
Priyank Singhbae09ae2019-04-25 18:17:02 -07001279 // should not clip while scroll to the bottom of the list.
Priyank Singhb0241582019-04-19 18:46:26 -07001280 if (!mNotificationListAtBottomAtTimeOfTouch) {
1281 return false;
1282 }
1283 float actualNotificationHeight =
1284 mNotificationView.getHeight() - (event1.getRawY() - event2.getRawY());
1285 if (actualNotificationHeight > mNotificationView.getHeight()) {
1286 actualNotificationHeight = mNotificationView.getHeight();
1287 }
1288 if (mNotificationView.getHeight() > 0) {
1289 mPercentageFromBottom = (int) Math.abs(
1290 actualNotificationHeight / mNotificationView.getHeight() * 100);
1291 boolean isUp = distanceY > 0;
1292
1293 // This check is to figure out if onScroll was called while swiping the card at
1294 // bottom of the list. At that time we should not allow notification shade to
1295 // close. We are also checking for the upwards swipe gesture here because it is
1296 // possible if a user is closing the notification shade and while swiping starts
1297 // to open again but does not fling. At that time we should allow the
1298 // notification shade to close fully or else it would stuck in between.
1299 if (Math.abs(mNotificationView.getHeight() - actualNotificationHeight)
1300 > SWIPE_DOWN_MIN_DISTANCE && isUp) {
1301 setNotificationViewClipBounds((int) actualNotificationHeight);
1302 mIsTracking = true;
1303 } else if (!isUp) {
1304 setNotificationViewClipBounds((int) actualNotificationHeight);
1305 }
1306 }
1307 // if we return true the the items in RV won't be scrollable.
1308 return false;
1309 }
1310
1311
1312 @Override
Brad Stenning3b0d7642019-03-28 11:04:30 -07001313 public boolean onFling(MotionEvent event1, MotionEvent event2,
1314 float velocityX, float velocityY) {
Priyank Singha01f2432019-05-17 16:33:57 -07001315 // should not fling if the touch does not start when view is at the bottom of the list.
1316 if (!mNotificationListAtBottomAtTimeOfTouch) {
Priyank Singhbae09ae2019-04-25 18:17:02 -07001317 return false;
1318 }
Brad Stenning3b0d7642019-03-28 11:04:30 -07001319 if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH
1320 || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) {
1321 // swipe was not vertical or was not fast enough
1322 return false;
1323 }
1324 boolean isUp = velocityY < 0;
Priyank Singhb0241582019-04-19 18:46:26 -07001325 if (isUp) {
Brad Stenning3b0d7642019-03-28 11:04:30 -07001326 close();
1327 return true;
Priyank Singhb0241582019-04-19 18:46:26 -07001328 } else {
1329 // we should close the shade
1330 animateNotificationPanel(velocityY, false);
Brad Stenning3b0d7642019-03-28 11:04:30 -07001331 }
1332 return false;
1333 }
Priyank Singhb0241582019-04-19 18:46:26 -07001334
Brad Stenning3b0d7642019-03-28 11:04:30 -07001335 protected abstract void close();
1336 }
1337
Priyank Singha47b6f72019-05-28 13:02:47 -07001338 /**
1339 * To be installed on the nav bars.
1340 */
Brad Stenning3b0d7642019-03-28 11:04:30 -07001341 private abstract class NavBarCloseNotificationGestureListener extends
1342 CloseNotificationGestureListener {
1343 @Override
1344 public boolean onSingleTapUp(MotionEvent e) {
Priyank Singhb0241582019-04-19 18:46:26 -07001345 mClosingVelocity = DEFAULT_FLING_VELOCITY;
Priyank Singhb9ab0522019-05-03 15:36:25 -07001346 if (mPanelExpanded) {
1347 close();
1348 }
Brad Stenning3b0d7642019-03-28 11:04:30 -07001349 return super.onSingleTapUp(e);
1350 }
1351
1352 @Override
Priyank Singhb0241582019-04-19 18:46:26 -07001353 public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
1354 float distanceY) {
1355 calculatePercentageFromBottom(event2.getRawY());
1356 setNotificationViewClipBounds((int) event2.getRawY());
1357 return true;
1358 }
1359
1360 @Override
Brad Stenning3b0d7642019-03-28 11:04:30 -07001361 public void onLongPress(MotionEvent e) {
Priyank Singhb0241582019-04-19 18:46:26 -07001362 mClosingVelocity = DEFAULT_FLING_VELOCITY;
Brad Stenning3b0d7642019-03-28 11:04:30 -07001363 close();
1364 super.onLongPress(e);
1365 }
Adora Zhang0237b9c2019-03-08 12:27:09 -08001366 }
Brad Stenning2ccc2a42019-04-15 10:17:01 -07001367
1368 /**
Priyank Singha47b6f72019-05-28 13:02:47 -07001369 * To be installed on the handle bar.
1370 */
1371 private class HandleBarCloseNotificationGestureListener extends
1372 GestureDetector.SimpleOnGestureListener {
1373
1374 @Override
1375 public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
1376 float distanceY) {
1377 calculatePercentageFromBottom(event2.getRawY());
1378 // To prevent the jump in the clip bounds while closing the notification shade using
1379 // the handle bar we should calculate the height using the diff of event1 and event2.
1380 // This will help the notification shade to clip smoothly as the event2 value changes
1381 // as event1 value will be fixed.
1382 int clipHeight =
1383 mNotificationView.getHeight() - (int) (event1.getRawY() - event2.getRawY());
1384 setNotificationViewClipBounds(clipHeight);
1385 return true;
1386 }
1387 }
1388
1389 /**
1390 * SystemUi version of the notification manager that overrides methods such that the
Brad Stenning2ccc2a42019-04-15 10:17:01 -07001391 * notifications end up in the status bar layouts instead of a standalone window.
1392 */
1393 private class CarSystemUIHeadsUpNotificationManager extends CarHeadsUpNotificationManager {
1394
1395 CarSystemUIHeadsUpNotificationManager(Context context,
1396 NotificationClickHandlerFactory clickHandlerFactory,
1397 NotificationDataManager notificationDataManager) {
1398 super(context, clickHandlerFactory, notificationDataManager);
1399 }
1400
1401 @Override
1402 protected View createHeadsUpPanel() {
1403 // In SystemUi the view is already in the window so just return a reference.
1404 return mStatusBarWindow.findViewById(R.id.notification_headsup);
1405 }
1406
1407 @Override
1408 protected void addHeadsUpPanelToDisplay() {
1409 // Set the panel initial state to invisible
1410 mHeadsUpPanel.setVisibility(View.INVISIBLE);
1411 }
1412
1413 @Override
Priyank Singh8bd920f2019-06-04 15:21:46 -07001414 protected void setInternalInsetsInfo(ViewTreeObserver.InternalInsetsInfo info,
1415 HeadsUpEntry currentNotification, boolean panelExpanded) {
1416 super.setInternalInsetsInfo(info, currentNotification, mPanelExpanded);
1417 }
1418
1419 @Override
Brad Stenning2ccc2a42019-04-15 10:17:01 -07001420 protected void setHeadsUpVisible() {
Priyank Singh7e9344f2019-05-07 14:30:13 -07001421 // if the Notifications panel is showing don't show the Heads up
Priyank Singh8bd920f2019-06-04 15:21:46 -07001422 if (!mEnableHeadsUpNotificationWhenNotificationShadeOpen && mPanelExpanded) {
Priyank Singh7e9344f2019-05-07 14:30:13 -07001423 return;
1424 }
Priyank Singh8bd920f2019-06-04 15:21:46 -07001425
Brad Stenning2ccc2a42019-04-15 10:17:01 -07001426 super.setHeadsUpVisible();
1427 if (mHeadsUpPanel.getVisibility() == View.VISIBLE) {
1428 mStatusBarWindowController.setHeadsUpShowing(true);
1429 mStatusBarWindowController.setForceStatusBarVisible(true);
1430 }
1431 }
1432
1433 @Override
1434 protected void removeNotificationFromPanel(HeadsUpEntry currentHeadsUpNotification) {
1435 super.removeNotificationFromPanel(currentHeadsUpNotification);
1436 // If the panel ended up empty and hidden we can remove it from SystemUi
1437 if (mHeadsUpPanel.getVisibility() != View.VISIBLE) {
1438 mStatusBarWindowController.setHeadsUpShowing(false);
1439 mStatusBarWindowController.setForceStatusBarVisible(false);
1440 }
1441 }
1442 }
Rakesh Iyer1186faa2015-12-07 16:48:46 -08001443}