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