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