1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.car.systembar; 18 19 import static com.android.systemui.car.systembar.CarSystemBar.DEBUG; 20 21 import android.annotation.IntDef; 22 import android.annotation.Nullable; 23 import android.content.Context; 24 import android.util.AttributeSet; 25 import android.util.Log; 26 import android.view.MotionEvent; 27 import android.view.View; 28 import android.view.ViewGroup; 29 import android.widget.LinearLayout; 30 31 import com.android.car.dockutil.Flags; 32 import com.android.systemui.R; 33 import com.android.systemui.car.hvac.HvacPanelOverlayViewController; 34 import com.android.systemui.car.hvac.HvacView; 35 import com.android.systemui.car.hvac.TemperatureControlView; 36 import com.android.systemui.car.notification.NotificationPanelViewController; 37 import com.android.systemui.car.systembar.CarSystemBarController.HvacPanelController; 38 import com.android.systemui.car.systembar.CarSystemBarController.NotificationsShadeController; 39 import com.android.systemui.settings.UserTracker; 40 41 import java.lang.annotation.ElementType; 42 import java.lang.annotation.Target; 43 import java.util.Set; 44 45 /** 46 * A custom system bar for the automotive use case. 47 * <p> 48 * The system bar in the automotive use case is more like a list of shortcuts, rendered 49 * in a linear layout. 50 */ 51 public class CarSystemBarView extends LinearLayout { 52 53 @IntDef(value = {BUTTON_TYPE_NAVIGATION, BUTTON_TYPE_KEYGUARD, BUTTON_TYPE_OCCLUSION}) 54 @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) 55 private @interface ButtonsType { 56 } 57 58 private static final String TAG = CarSystemBarView.class.getSimpleName(); 59 60 public static final int BUTTON_TYPE_NAVIGATION = 0; 61 public static final int BUTTON_TYPE_KEYGUARD = 1; 62 public static final int BUTTON_TYPE_OCCLUSION = 2; 63 64 private final boolean mConsumeTouchWhenPanelOpen; 65 private final boolean mButtonsDraggable; 66 private CarSystemBarButton mHomeButton; 67 private CarSystemBarButton mPassengerHomeButton; 68 private View mNavButtons; 69 private CarSystemBarButton mNotificationsButton; 70 private CarSystemBarButton mHvacButton; 71 private HvacView mDriverHvacView; 72 private HvacView mPassengerHvacView; 73 private NotificationsShadeController mNotificationsShadeController; 74 private HvacPanelController mHvacPanelController; 75 private View mLockScreenButtons; 76 private View mOcclusionButtons; 77 // used to wire in open/close gestures for overlay panels 78 private Set<OnTouchListener> mStatusBarWindowTouchListeners; 79 private HvacPanelOverlayViewController mHvacPanelOverlayViewController; 80 private NotificationPanelViewController mNotificationPanelViewController; 81 private CarSystemBarButton mControlCenterButton; 82 CarSystemBarView(Context context, AttributeSet attrs)83 public CarSystemBarView(Context context, AttributeSet attrs) { 84 super(context, attrs); 85 mConsumeTouchWhenPanelOpen = getResources().getBoolean( 86 R.bool.config_consumeSystemBarTouchWhenNotificationPanelOpen); 87 mButtonsDraggable = getResources().getBoolean(R.bool.config_systemBarButtonsDraggable); 88 } 89 90 @Override onFinishInflate()91 public void onFinishInflate() { 92 mHomeButton = findViewById(R.id.home); 93 mPassengerHomeButton = findViewById(R.id.passenger_home); 94 mNavButtons = findViewById(R.id.nav_buttons); 95 mLockScreenButtons = findViewById(R.id.lock_screen_nav_buttons); 96 mOcclusionButtons = findViewById(R.id.occlusion_buttons); 97 mNotificationsButton = findViewById(R.id.notifications); 98 mHvacButton = findViewById(R.id.hvac); 99 mDriverHvacView = findViewById(R.id.driver_hvac); 100 mPassengerHvacView = findViewById(R.id.passenger_hvac); 101 mControlCenterButton = findViewById(R.id.control_center_nav); 102 if (mNotificationsButton != null) { 103 mNotificationsButton.setOnClickListener(this::onNotificationsClick); 104 } 105 setupHvacButton(); 106 // Needs to be clickable so that it will receive ACTION_MOVE events. 107 setClickable(true); 108 // Needs to not be focusable so rotary won't highlight the entire nav bar. 109 setFocusable(false); 110 } 111 updateHomeButtonVisibility(boolean isPassenger)112 void updateHomeButtonVisibility(boolean isPassenger) { 113 if (!isPassenger) { 114 return; 115 } 116 if (mPassengerHomeButton != null) { 117 if (mHomeButton != null) { 118 mHomeButton.setVisibility(GONE); 119 } 120 mPassengerHomeButton.setVisibility(VISIBLE); 121 } 122 } 123 setupHvacButton()124 void setupHvacButton() { 125 if (mHvacButton != null) { 126 mHvacButton.setOnClickListener(this::onHvacClick); 127 } 128 129 if (Flags.dockFeature()) { 130 if (mDriverHvacView instanceof TemperatureControlView) { 131 ((TemperatureControlView) mDriverHvacView).setTemperatureTextClickListener( 132 this::onHvacClick); 133 } 134 if (mPassengerHvacView instanceof TemperatureControlView) { 135 ((TemperatureControlView) mPassengerHvacView).setTemperatureTextClickListener( 136 this::onHvacClick); 137 } 138 } 139 } 140 setupSystemBarButtons(UserTracker userTracker)141 void setupSystemBarButtons(UserTracker userTracker) { 142 setupSystemBarButtons(this, userTracker); 143 } 144 setupSystemBarButtons(View v, UserTracker userTracker)145 private void setupSystemBarButtons(View v, UserTracker userTracker) { 146 if (v instanceof CarSystemBarButton) { 147 ((CarSystemBarButton) v).setUserTracker(userTracker); 148 } else if (v instanceof ViewGroup) { 149 ViewGroup viewGroup = (ViewGroup) v; 150 for (int i = 0; i < viewGroup.getChildCount(); i++) { 151 setupSystemBarButtons(viewGroup.getChildAt(i), userTracker); 152 } 153 } 154 } 155 updateControlCenterButtonVisibility(boolean isMumd)156 void updateControlCenterButtonVisibility(boolean isMumd) { 157 if (mControlCenterButton != null) { 158 mControlCenterButton.setVisibility(isMumd ? VISIBLE : GONE); 159 } 160 } 161 162 // Used to forward touch events even if the touch was initiated from a child component 163 @Override onInterceptTouchEvent(MotionEvent ev)164 public boolean onInterceptTouchEvent(MotionEvent ev) { 165 if (mStatusBarWindowTouchListeners != null && !mStatusBarWindowTouchListeners.isEmpty()) { 166 if (!mButtonsDraggable) { 167 return false; 168 } 169 boolean shouldConsumeEvent = mNotificationsShadeController == null ? false 170 : mNotificationsShadeController.isNotificationPanelOpen(); 171 172 // Forward touch events to the status bar window so it can drag 173 // windows if required (ex. Notification shade) 174 triggerAllTouchListeners(this, ev); 175 176 if (mConsumeTouchWhenPanelOpen && shouldConsumeEvent) { 177 return true; 178 } 179 } 180 return super.onInterceptTouchEvent(ev); 181 } 182 183 /** Sets the notifications panel controller. */ setNotificationsPanelController(NotificationsShadeController controller)184 public void setNotificationsPanelController(NotificationsShadeController controller) { 185 mNotificationsShadeController = controller; 186 } 187 188 /** Sets the HVAC panel controller. */ setHvacPanelController(HvacPanelController controller)189 public void setHvacPanelController(HvacPanelController controller) { 190 mHvacPanelController = controller; 191 } 192 193 /** Gets the notifications panel controller. */ getNotificationsPanelController()194 public NotificationsShadeController getNotificationsPanelController() { 195 return mNotificationsShadeController; 196 } 197 198 /** Gets the HVAC panel controller. */ getHvacPanelController()199 public HvacPanelController getHvacPanelController() { 200 return mHvacPanelController; 201 } 202 203 /** 204 * Sets the touch listeners that will be called from onInterceptTouchEvent and onTouchEvent 205 * 206 * @param statusBarWindowTouchListeners List of listeners to call from touch and intercept touch 207 */ setStatusBarWindowTouchListeners( Set<OnTouchListener> statusBarWindowTouchListeners)208 public void setStatusBarWindowTouchListeners( 209 Set<OnTouchListener> statusBarWindowTouchListeners) { 210 mStatusBarWindowTouchListeners = statusBarWindowTouchListeners; 211 } 212 213 /** Gets the touch listeners that will be called from onInterceptTouchEvent and onTouchEvent. */ getStatusBarWindowTouchListeners()214 public Set<OnTouchListener> getStatusBarWindowTouchListeners() { 215 return mStatusBarWindowTouchListeners; 216 } 217 218 @Override onTouchEvent(MotionEvent event)219 public boolean onTouchEvent(MotionEvent event) { 220 triggerAllTouchListeners(this, event); 221 return super.onTouchEvent(event); 222 } 223 onNotificationsClick(View v)224 protected void onNotificationsClick(View v) { 225 if (mNotificationsButton != null 226 && mNotificationsButton.getDisabled()) { 227 mNotificationsButton.runOnClickWhileDisabled(); 228 return; 229 } 230 if (mNotificationsShadeController != null) { 231 // If the notification shade is about to open, close the hvac panel 232 if (!mNotificationsShadeController.isNotificationPanelOpen() 233 && mHvacPanelController != null 234 && mHvacPanelController.isHvacPanelOpen()) { 235 mHvacPanelController.togglePanel(); 236 } 237 mNotificationsShadeController.togglePanel(); 238 } 239 } 240 onHvacClick(View v)241 protected void onHvacClick(View v) { 242 if (mHvacPanelController != null) { 243 // If the hvac panel is about to open, close the notification shade 244 if (!mHvacPanelController.isHvacPanelOpen() 245 && mNotificationsShadeController != null 246 && mNotificationsShadeController.isNotificationPanelOpen()) { 247 mNotificationsShadeController.togglePanel(); 248 } 249 mHvacPanelController.togglePanel(); 250 } 251 } 252 253 /** 254 * Shows buttons of the specified {@link ButtonsType}. 255 * 256 * NOTE: Only one type of buttons can be shown at a time, so showing buttons of one type will 257 * hide all buttons of other types. 258 * 259 * @param buttonsType 260 */ showButtonsOfType(@uttonsType int buttonsType)261 public void showButtonsOfType(@ButtonsType int buttonsType) { 262 switch(buttonsType) { 263 case BUTTON_TYPE_NAVIGATION: 264 setNavigationButtonsVisibility(View.VISIBLE); 265 setKeyguardButtonsVisibility(View.GONE); 266 setOcclusionButtonsVisibility(View.GONE); 267 break; 268 case BUTTON_TYPE_KEYGUARD: 269 setNavigationButtonsVisibility(View.GONE); 270 setKeyguardButtonsVisibility(View.VISIBLE); 271 setOcclusionButtonsVisibility(View.GONE); 272 break; 273 case BUTTON_TYPE_OCCLUSION: 274 setNavigationButtonsVisibility(View.GONE); 275 setKeyguardButtonsVisibility(View.GONE); 276 setOcclusionButtonsVisibility(View.VISIBLE); 277 break; 278 } 279 } 280 281 /** 282 * Sets the system bar view's disabled state and runnable when disabled. 283 */ setDisabledSystemBarButton(int viewId, boolean disabled, Runnable runnable, @Nullable String buttonName)284 public void setDisabledSystemBarButton(int viewId, boolean disabled, Runnable runnable, 285 @Nullable String buttonName) { 286 CarSystemBarButton button = findViewById(viewId); 287 if (button != null) { 288 if (DEBUG) { 289 Log.d(TAG, "setDisabledSystemBarButton for: " + buttonName + " to: " + disabled); 290 } 291 button.setDisabled(disabled, runnable); 292 } 293 } 294 295 /** 296 * Sets the system bar specific View container's visibility. ViewName is used just for 297 * debugging. 298 */ setVisibilityByViewId(int viewId, @Nullable String viewName, @View.Visibility int visibility)299 public void setVisibilityByViewId(int viewId, @Nullable String viewName, 300 @View.Visibility int visibility) { 301 View v = findViewById(viewId); 302 if (v != null) { 303 if (DEBUG) Log.d(TAG, "setVisibilityByViewId for: " + viewName + " to: " + visibility); 304 v.setVisibility(visibility); 305 } 306 } 307 308 /** 309 * Sets the HvacPanelOverlayViewController and adds HVAC button listeners 310 */ registerHvacPanelOverlayViewController(HvacPanelOverlayViewController controller)311 public void registerHvacPanelOverlayViewController(HvacPanelOverlayViewController controller) { 312 mHvacPanelOverlayViewController = controller; 313 if (mHvacPanelOverlayViewController != null && mHvacButton != null) { 314 mHvacPanelOverlayViewController.registerViewStateListener(mHvacButton); 315 } 316 } 317 318 /** 319 * Sets the NotificationPanelViewController and adds button listeners 320 */ registerNotificationPanelViewController( NotificationPanelViewController controller)321 public void registerNotificationPanelViewController( 322 NotificationPanelViewController controller) { 323 mNotificationPanelViewController = controller; 324 if (mNotificationPanelViewController != null && mNotificationsButton != null) { 325 mNotificationPanelViewController.registerViewStateListener(mNotificationsButton); 326 } 327 } 328 setNavigationButtonsVisibility(@iew.Visibility int visibility)329 private void setNavigationButtonsVisibility(@View.Visibility int visibility) { 330 if (mNavButtons != null) { 331 mNavButtons.setVisibility(visibility); 332 } 333 } 334 setKeyguardButtonsVisibility(@iew.Visibility int visibility)335 private void setKeyguardButtonsVisibility(@View.Visibility int visibility) { 336 if (mLockScreenButtons != null) { 337 mLockScreenButtons.setVisibility(visibility); 338 } 339 } 340 setOcclusionButtonsVisibility(@iew.Visibility int visibility)341 private void setOcclusionButtonsVisibility(@View.Visibility int visibility) { 342 if (mOcclusionButtons != null) { 343 mOcclusionButtons.setVisibility(visibility); 344 } 345 } 346 triggerAllTouchListeners(View view, MotionEvent event)347 private void triggerAllTouchListeners(View view, MotionEvent event) { 348 if (mStatusBarWindowTouchListeners == null) { 349 return; 350 } 351 for (OnTouchListener listener : mStatusBarWindowTouchListeners) { 352 listener.onTouch(view, event); 353 } 354 } 355 356 /** 357 * Toggles the notification unseen indicator on/off. 358 * 359 * @param hasUnseen true if the unseen notification count is great than 0. 360 */ toggleNotificationUnseenIndicator(Boolean hasUnseen)361 public void toggleNotificationUnseenIndicator(Boolean hasUnseen) { 362 if (mNotificationsButton == null) return; 363 364 mNotificationsButton.setUnseen(hasUnseen); 365 } 366 } 367