1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.statusbar.car; 18 19import android.app.ActivityManager; 20import android.graphics.PixelFormat; 21import android.graphics.drawable.Drawable; 22import android.os.SystemProperties; 23import android.util.Log; 24import android.view.Gravity; 25import android.view.View; 26import android.view.ViewGroup; 27import android.view.ViewGroup.LayoutParams; 28import android.view.WindowManager; 29 30import com.android.keyguard.KeyguardUpdateMonitor; 31import com.android.systemui.BatteryMeterView; 32import com.android.systemui.Dependency; 33import com.android.systemui.Prefs; 34import com.android.systemui.R; 35import com.android.systemui.classifier.FalsingLog; 36import com.android.systemui.classifier.FalsingManager; 37import com.android.systemui.fragments.FragmentHostManager; 38import com.android.systemui.recents.Recents; 39import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; 40import com.android.systemui.shared.system.ActivityManagerWrapper; 41import com.android.systemui.statusbar.StatusBarState; 42import com.android.systemui.statusbar.car.hvac.HvacController; 43import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; 44import com.android.systemui.statusbar.phone.StatusBar; 45import com.android.systemui.statusbar.policy.BatteryController; 46import com.android.systemui.statusbar.policy.DeviceProvisionedController; 47import com.android.systemui.statusbar.policy.UserSwitcherController; 48 49import java.io.FileDescriptor; 50import java.io.PrintWriter; 51import java.util.Map; 52/** 53 * A status bar (and navigation bar) tailored for the automotive use case. 54 */ 55public class CarStatusBar extends StatusBar implements 56 CarBatteryController.BatteryViewHandler { 57 private static final String TAG = "CarStatusBar"; 58 public static final boolean ENABLE_HVAC_CONNECTION 59 = !SystemProperties.getBoolean("android.car.hvac.demo", true); 60 61 private TaskStackListenerImpl mTaskStackListener; 62 63 private FullscreenUserSwitcher mFullscreenUserSwitcher; 64 65 private CarBatteryController mCarBatteryController; 66 private BatteryMeterView mBatteryMeterView; 67 private Drawable mNotificationPanelBackground; 68 69 private ConnectedDeviceSignalController mConnectedDeviceSignalController; 70 private ViewGroup mNavigationBarWindow; 71 private ViewGroup mLeftNavigationBarWindow; 72 private ViewGroup mRightNavigationBarWindow; 73 private CarNavigationBarView mNavigationBarView; 74 private CarNavigationBarView mLeftNavigationBarView; 75 private CarNavigationBarView mRightNavigationBarView; 76 77 private final Object mQueueLock = new Object(); 78 private boolean mShowLeft; 79 private boolean mShowRight; 80 private boolean mShowBottom; 81 private CarFacetButtonController mCarFacetButtonController; 82 private ActivityManagerWrapper mActivityManagerWrapper; 83 private DeviceProvisionedController mDeviceProvisionedController; 84 private boolean mDeviceIsProvisioned = true; 85 86 @Override 87 public void start() { 88 super.start(); 89 mTaskStackListener = new TaskStackListenerImpl(); 90 mActivityManagerWrapper = ActivityManagerWrapper.getInstance(); 91 mActivityManagerWrapper.registerTaskStackListener(mTaskStackListener); 92 93 mStackScroller.setScrollingEnabled(true); 94 95 createBatteryController(); 96 mCarBatteryController.startListening(); 97 98 if (ENABLE_HVAC_CONNECTION) { 99 Log.d(TAG, "Connecting to HVAC service"); 100 Dependency.get(HvacController.class).connectToCarService(); 101 } 102 mCarFacetButtonController = Dependency.get(CarFacetButtonController.class); 103 mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class); 104 mDeviceIsProvisioned = mDeviceProvisionedController.isDeviceProvisioned(); 105 if (!mDeviceIsProvisioned) { 106 mDeviceProvisionedController.addCallback( 107 new DeviceProvisionedController.DeviceProvisionedListener() { 108 @Override 109 public void onDeviceProvisionedChanged() { 110 mDeviceIsProvisioned = 111 mDeviceProvisionedController.isDeviceProvisioned(); 112 restartNavBars(); 113 } 114 }); 115 } 116 } 117 118 /** 119 * Remove all content from navbars and rebuild them. Used to allow for different nav bars 120 * before and after the device is provisioned 121 */ 122 private void restartNavBars() { 123 mCarFacetButtonController.removeAll(); 124 if (ENABLE_HVAC_CONNECTION) { 125 Dependency.get(HvacController.class).removeAllComponents(); 126 } 127 if (mNavigationBarWindow != null) { 128 mNavigationBarWindow.removeAllViews(); 129 mNavigationBarView = null; 130 } 131 132 if (mLeftNavigationBarWindow != null) { 133 mLeftNavigationBarWindow.removeAllViews(); 134 mLeftNavigationBarView = null; 135 } 136 137 if (mRightNavigationBarWindow != null) { 138 mRightNavigationBarWindow.removeAllViews(); 139 mRightNavigationBarView = null; 140 } 141 buildNavBarContent(); 142 } 143 144 /** 145 * Allows for showing or hiding just the navigation bars. This is indented to be used when 146 * the full screen user selector is shown. 147 */ 148 void setNavBarVisibility(@View.Visibility int visibility) { 149 if (mNavigationBarWindow != null) { 150 mNavigationBarWindow.setVisibility(visibility); 151 } 152 if (mLeftNavigationBarWindow != null) { 153 mLeftNavigationBarWindow.setVisibility(visibility); 154 } 155 if (mRightNavigationBarWindow != null) { 156 mRightNavigationBarWindow.setVisibility(visibility); 157 } 158 } 159 160 161 @Override 162 public boolean hideKeyguard() { 163 boolean result = super.hideKeyguard(); 164 if (mNavigationBarView != null) { 165 mNavigationBarView.hideKeyguardButtons(); 166 } 167 if (mLeftNavigationBarView != null) { 168 mLeftNavigationBarView.hideKeyguardButtons(); 169 } 170 if (mRightNavigationBarView != null) { 171 mRightNavigationBarView.hideKeyguardButtons(); 172 } 173 return result; 174 } 175 176 177 @Override 178 public void showKeyguard() { 179 super.showKeyguard(); 180 if (mNavigationBarView != null) { 181 mNavigationBarView.showKeyguardButtons(); 182 } 183 if (mLeftNavigationBarView != null) { 184 mLeftNavigationBarView.showKeyguardButtons(); 185 } 186 if (mRightNavigationBarView != null) { 187 mRightNavigationBarView.showKeyguardButtons(); 188 } 189 } 190 191 @Override 192 public void destroy() { 193 mCarBatteryController.stopListening(); 194 mConnectedDeviceSignalController.stopListening(); 195 mActivityManagerWrapper.unregisterTaskStackListener(mTaskStackListener); 196 197 if (mNavigationBarWindow != null) { 198 mWindowManager.removeViewImmediate(mNavigationBarWindow); 199 mNavigationBarView = null; 200 } 201 202 if (mLeftNavigationBarWindow != null) { 203 mWindowManager.removeViewImmediate(mLeftNavigationBarWindow); 204 mLeftNavigationBarView = null; 205 } 206 207 if (mRightNavigationBarWindow != null) { 208 mWindowManager.removeViewImmediate(mRightNavigationBarWindow); 209 mRightNavigationBarView = null; 210 } 211 super.destroy(); 212 } 213 214 215 @Override 216 protected void makeStatusBarView() { 217 super.makeStatusBarView(); 218 219 mNotificationPanelBackground = getDefaultWallpaper(); 220 mScrimController.setScrimBehindDrawable(mNotificationPanelBackground); 221 222 FragmentHostManager manager = FragmentHostManager.get(mStatusBarWindow); 223 manager.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> { 224 mBatteryMeterView = fragment.getView().findViewById(R.id.battery); 225 226 // By default, the BatteryMeterView should not be visible. It will be toggled 227 // when a device has connected by bluetooth. 228 mBatteryMeterView.setVisibility(View.GONE); 229 }); 230 } 231 232 private BatteryController createBatteryController() { 233 mCarBatteryController = new CarBatteryController(mContext); 234 mCarBatteryController.addBatteryViewHandler(this); 235 return mCarBatteryController; 236 } 237 238 @Override 239 protected void createNavigationBar() { 240 mShowBottom = mContext.getResources().getBoolean(R.bool.config_enableBottomNavigationBar); 241 mShowLeft = mContext.getResources().getBoolean(R.bool.config_enableLeftNavigationBar); 242 mShowRight = mContext.getResources().getBoolean(R.bool.config_enableRightNavigationBar); 243 244 buildNavBarWindows(); 245 buildNavBarContent(); 246 attachNavBarWindows(); 247 } 248 249 private void buildNavBarContent() { 250 if (mShowBottom) { 251 buildBottomBar((mDeviceIsProvisioned) ? R.layout.car_navigation_bar : 252 R.layout.car_navigation_bar_unprovisioned); 253 } 254 255 if (mShowLeft) { 256 buildLeft((mDeviceIsProvisioned) ? R.layout.car_left_navigation_bar : 257 R.layout.car_left_navigation_bar_unprovisioned); 258 } 259 260 if (mShowRight) { 261 buildRight((mDeviceIsProvisioned) ? R.layout.car_right_navigation_bar : 262 R.layout.car_right_navigation_bar_unprovisioned); 263 } 264 } 265 266 private void buildNavBarWindows() { 267 if (mShowBottom) { 268 269 mNavigationBarWindow = (ViewGroup) View.inflate(mContext, 270 R.layout.navigation_bar_window, null); 271 } 272 if (mShowLeft) { 273 mLeftNavigationBarWindow = (ViewGroup) View.inflate(mContext, 274 R.layout.navigation_bar_window, null); 275 } 276 if (mShowRight) { 277 mRightNavigationBarWindow = (ViewGroup) View.inflate(mContext, 278 R.layout.navigation_bar_window, null); 279 } 280 281 } 282 283 private void attachNavBarWindows() { 284 285 if (mShowBottom) { 286 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 287 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 288 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR, 289 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 290 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 291 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 292 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 293 PixelFormat.TRANSLUCENT); 294 lp.setTitle("CarNavigationBar"); 295 lp.windowAnimations = 0; 296 mWindowManager.addView(mNavigationBarWindow, lp); 297 } 298 if (mShowLeft) { 299 int width = mContext.getResources().getDimensionPixelSize( 300 R.dimen.car_left_navigation_bar_width); 301 WindowManager.LayoutParams leftlp = new WindowManager.LayoutParams( 302 width, LayoutParams.MATCH_PARENT, 303 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, 304 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 305 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 306 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 307 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 308 PixelFormat.TRANSLUCENT); 309 leftlp.setTitle("LeftCarNavigationBar"); 310 leftlp.windowAnimations = 0; 311 leftlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; 312 leftlp.gravity = Gravity.LEFT; 313 mWindowManager.addView(mLeftNavigationBarWindow, leftlp); 314 } 315 if (mShowRight) { 316 int width = mContext.getResources().getDimensionPixelSize( 317 R.dimen.car_right_navigation_bar_width); 318 WindowManager.LayoutParams rightlp = new WindowManager.LayoutParams( 319 width, LayoutParams.MATCH_PARENT, 320 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, 321 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 322 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 323 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH 324 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, 325 PixelFormat.TRANSLUCENT); 326 rightlp.setTitle("RightCarNavigationBar"); 327 rightlp.windowAnimations = 0; 328 rightlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR; 329 rightlp.gravity = Gravity.RIGHT; 330 mWindowManager.addView(mRightNavigationBarWindow, rightlp); 331 } 332 333 } 334 335 private void buildBottomBar(int layout) { 336 // SystemUI requires that the navigation bar view have a parent. Since the regular 337 // StatusBar inflates navigation_bar_window as this parent view, use the same view for the 338 // CarNavigationBarView. 339 View.inflate(mContext, layout, mNavigationBarWindow); 340 mNavigationBarView = (CarNavigationBarView) mNavigationBarWindow.getChildAt(0); 341 if (mNavigationBarView == null) { 342 Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar"); 343 throw new RuntimeException("Unable to build botom nav bar due to missing layout"); 344 } 345 mNavigationBarView.setStatusBar(this); 346 } 347 348 private void buildLeft(int layout) { 349 View.inflate(mContext, layout, mLeftNavigationBarWindow); 350 mLeftNavigationBarView = (CarNavigationBarView) mLeftNavigationBarWindow.getChildAt(0); 351 if (mLeftNavigationBarView == null) { 352 Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar"); 353 throw new RuntimeException("Unable to build left nav bar due to missing layout"); 354 } 355 mLeftNavigationBarView.setStatusBar(this); 356 } 357 358 359 private void buildRight(int layout) { 360 View.inflate(mContext, layout, mRightNavigationBarWindow); 361 mRightNavigationBarView = (CarNavigationBarView) mRightNavigationBarWindow.getChildAt(0); 362 if (mRightNavigationBarView == null) { 363 Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar"); 364 throw new RuntimeException("Unable to build right nav bar due to missing layout"); 365 } 366 } 367 368 @Override 369 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 370 //When executing dump() funciton simultaneously, we need to serialize them 371 //to get mStackScroller's position correctly. 372 synchronized (mQueueLock) { 373 pw.println(" mStackScroller: " + viewInfo(mStackScroller)); 374 pw.println(" mStackScroller: " + viewInfo(mStackScroller) 375 + " scroll " + mStackScroller.getScrollX() 376 + "," + mStackScroller.getScrollY()); 377 } 378 379 pw.print(" mTaskStackListener="); pw.println(mTaskStackListener); 380 pw.print(" mCarFacetButtonController="); 381 pw.println(mCarFacetButtonController); 382 pw.print(" mFullscreenUserSwitcher="); pw.println(mFullscreenUserSwitcher); 383 pw.print(" mCarBatteryController="); 384 pw.println(mCarBatteryController); 385 pw.print(" mBatteryMeterView="); 386 pw.println(mBatteryMeterView); 387 pw.print(" mConnectedDeviceSignalController="); 388 pw.println(mConnectedDeviceSignalController); 389 pw.print(" mNavigationBarView="); 390 pw.println(mNavigationBarView); 391 392 if (KeyguardUpdateMonitor.getInstance(mContext) != null) { 393 KeyguardUpdateMonitor.getInstance(mContext).dump(fd, pw, args); 394 } 395 396 FalsingManager.getInstance(mContext).dump(pw); 397 FalsingLog.dump(pw); 398 399 pw.println("SharedPreferences:"); 400 for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) { 401 pw.print(" "); pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue()); 402 } 403 } 404 405 406 @Override 407 public View getNavigationBarWindow() { 408 return mNavigationBarWindow; 409 } 410 411 @Override 412 protected View.OnTouchListener getStatusBarWindowTouchListener() { 413 // Usually, a touch on the background window will dismiss the notification shade. However, 414 // for the car use-case, the shade should remain unless the user switches to a different 415 // facet (e.g. phone). 416 return null; 417 } 418 419 @Override 420 public void showBatteryView() { 421 if (Log.isLoggable(TAG, Log.DEBUG)) { 422 Log.d(TAG, "showBatteryView(). mBatteryMeterView: " + mBatteryMeterView); 423 } 424 425 if (mBatteryMeterView != null) { 426 mBatteryMeterView.setVisibility(View.VISIBLE); 427 } 428 } 429 430 @Override 431 public void hideBatteryView() { 432 if (Log.isLoggable(TAG, Log.DEBUG)) { 433 Log.d(TAG, "hideBatteryView(). mBatteryMeterView: " + mBatteryMeterView); 434 } 435 436 if (mBatteryMeterView != null) { 437 mBatteryMeterView.setVisibility(View.GONE); 438 } 439 } 440 441 442 public boolean hasDockedTask() { 443 return Recents.getSystemServices().hasDockedTask(); 444 } 445 446 /** 447 * An implementation of SysUiTaskStackChangeListener, that listens for changes in the system task 448 * stack and notifies the navigation bar. 449 */ 450 private class TaskStackListenerImpl extends SysUiTaskStackChangeListener { 451 @Override 452 public void onTaskStackChanged() { 453 try { 454 mCarFacetButtonController.taskChanged( 455 ActivityManager.getService().getAllStackInfos()); 456 } catch (Exception e) { 457 Log.e(TAG, "Getting StackInfo from activity manager failed", e); 458 } 459 } 460 } 461 462 @Override 463 protected void createUserSwitcher() { 464 UserSwitcherController userSwitcherController = 465 Dependency.get(UserSwitcherController.class); 466 if (userSwitcherController.useFullscreenUserSwitcher()) { 467 mFullscreenUserSwitcher = new FullscreenUserSwitcher(this, 468 mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub), mContext); 469 } else { 470 super.createUserSwitcher(); 471 } 472 } 473 474 @Override 475 public void onUserSwitched(int newUserId) { 476 super.onUserSwitched(newUserId); 477 if (mFullscreenUserSwitcher != null) { 478 mFullscreenUserSwitcher.onUserSwitched(newUserId); 479 } 480 } 481 482 @Override 483 public void updateKeyguardState(boolean goingToFullShade, boolean fromShadeLocked) { 484 super.updateKeyguardState(goingToFullShade, fromShadeLocked); 485 if (mFullscreenUserSwitcher != null) { 486 if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER) { 487 mFullscreenUserSwitcher.show(); 488 } else { 489 mFullscreenUserSwitcher.hide(); 490 } 491 } 492 } 493 494 @Override 495 public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { 496 // Do nothing, we don't want to display media art in the lock screen for a car. 497 } 498 499 500 @Override 501 public void animateExpandNotificationsPanel() { 502 // Because space is usually constrained in the auto use-case, there should not be a 503 // pinned notification when the shade has been expanded. Ensure this by removing all heads- 504 // up notifications. 505 mHeadsUpManager.releaseAllImmediately(); 506 super.animateExpandNotificationsPanel(); 507 } 508 509 /** 510 * Ensures that relevant child views are appropriately recreated when the device's density 511 * changes. 512 */ 513 @Override 514 public void onDensityOrFontScaleChanged() { 515 super.onDensityOrFontScaleChanged(); 516 // Need to update the background on density changed in case the change was due to night 517 // mode. 518 mNotificationPanelBackground = getDefaultWallpaper(); 519 mScrimController.setScrimBehindDrawable(mNotificationPanelBackground); 520 } 521 522 /** 523 * Returns the {@link Drawable} that represents the wallpaper that the user has currently set. 524 */ 525 private Drawable getDefaultWallpaper() { 526 return mContext.getDrawable(com.android.internal.R.drawable.default_wallpaper); 527 } 528} 529