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.content.Context; 21 import android.util.AttributeSet; 22 import android.util.Log; 23 import android.view.MotionEvent; 24 import android.view.View; 25 import android.view.ViewGroup; 26 import android.widget.LinearLayout; 27 28 import com.android.systemui.R; 29 import com.android.systemui.car.hvac.HvacPanelOverlayViewController; 30 import com.android.systemui.car.statusicon.ui.QuickControlsEntryPointsController; 31 import com.android.systemui.car.statusicon.ui.ReadOnlyIconsController; 32 import com.android.systemui.car.systembar.CarSystemBarController.HvacPanelController; 33 import com.android.systemui.car.systembar.CarSystemBarController.NotificationsShadeController; 34 import com.android.systemui.flags.FeatureFlags; 35 import com.android.systemui.statusbar.phone.StatusBarIconController; 36 37 import java.lang.annotation.ElementType; 38 import java.lang.annotation.Target; 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 55 public static final int BUTTON_TYPE_NAVIGATION = 0; 56 public static final int BUTTON_TYPE_KEYGUARD = 1; 57 public static final int BUTTON_TYPE_OCCLUSION = 2; 58 59 private final boolean mConsumeTouchWhenPanelOpen; 60 private final boolean mButtonsDraggable; 61 62 private View mNavButtons; 63 private CarSystemBarButton mNotificationsButton; 64 private HvacButton mHvacButton; 65 private NotificationsShadeController mNotificationsShadeController; 66 private HvacPanelController mHvacPanelController; 67 private View mLockScreenButtons; 68 private View mOcclusionButtons; 69 private ViewGroup mQcEntryPointsContainer; 70 private ViewGroup mReadOnlyIconsContainer; 71 // used to wire in open/close gestures for notifications 72 private OnTouchListener mStatusBarWindowTouchListener; 73 private HvacPanelOverlayViewController mHvacPanelOverlayViewController; 74 CarSystemBarView(Context context, AttributeSet attrs)75 public CarSystemBarView(Context context, AttributeSet attrs) { 76 super(context, attrs); 77 mConsumeTouchWhenPanelOpen = getResources().getBoolean( 78 R.bool.config_consumeSystemBarTouchWhenNotificationPanelOpen); 79 mButtonsDraggable = getResources().getBoolean(R.bool.config_systemBarButtonsDraggable); 80 } 81 82 @Override onFinishInflate()83 public void onFinishInflate() { 84 mNavButtons = findViewById(R.id.nav_buttons); 85 mLockScreenButtons = findViewById(R.id.lock_screen_nav_buttons); 86 mOcclusionButtons = findViewById(R.id.occlusion_buttons); 87 mNotificationsButton = findViewById(R.id.notifications); 88 mHvacButton = findViewById(R.id.hvac); 89 mQcEntryPointsContainer = findViewById(R.id.qc_entry_points_container); 90 mReadOnlyIconsContainer = findViewById(R.id.read_only_icons_container); 91 if (mNotificationsButton != null) { 92 mNotificationsButton.setOnClickListener(this::onNotificationsClick); 93 } 94 // Needs to be clickable so that it will receive ACTION_MOVE events. 95 setClickable(true); 96 // Needs to not be focusable so rotary won't highlight the entire nav bar. 97 setFocusable(false); 98 } 99 setupHvacButton()100 void setupHvacButton() { 101 if (mHvacButton != null) { 102 mHvacButton.setOnClickListener(this::onHvacClick); 103 } 104 } 105 setupQuickControlsEntryPoints( QuickControlsEntryPointsController quickControlsEntryPointsController, boolean isSetUp)106 void setupQuickControlsEntryPoints( 107 QuickControlsEntryPointsController quickControlsEntryPointsController, 108 boolean isSetUp) { 109 if (mQcEntryPointsContainer != null) { 110 quickControlsEntryPointsController.addIconViews(mQcEntryPointsContainer, isSetUp); 111 } 112 } 113 setupReadOnlyIcons(ReadOnlyIconsController readOnlyIconsController)114 void setupReadOnlyIcons(ReadOnlyIconsController readOnlyIconsController) { 115 if (mReadOnlyIconsContainer != null) { 116 readOnlyIconsController.addIconViews(mReadOnlyIconsContainer); 117 } 118 } 119 setupIconController(FeatureFlags featureFlags, StatusBarIconController iconController)120 void setupIconController(FeatureFlags featureFlags, StatusBarIconController iconController) { 121 View mStatusIcons = findViewById(R.id.statusIcons); 122 if (mStatusIcons != null) { 123 // Attach the controllers for Status icons such as wifi and bluetooth if the standard 124 // container is in the view. 125 StatusBarIconController.DarkIconManager mDarkIconManager = 126 new StatusBarIconController.DarkIconManager( 127 mStatusIcons.findViewById(R.id.statusIcons), featureFlags); 128 mDarkIconManager.setShouldLog(true); 129 iconController.addIconGroup(mDarkIconManager); 130 } 131 } 132 133 // Used to forward touch events even if the touch was initiated from a child component 134 @Override onInterceptTouchEvent(MotionEvent ev)135 public boolean onInterceptTouchEvent(MotionEvent ev) { 136 if (mStatusBarWindowTouchListener != null) { 137 if (!mButtonsDraggable) { 138 return false; 139 } 140 boolean shouldConsumeEvent = mNotificationsShadeController == null ? false 141 : mNotificationsShadeController.isNotificationPanelOpen(); 142 143 // Forward touch events to the status bar window so it can drag 144 // windows if required (Notification shade) 145 mStatusBarWindowTouchListener.onTouch(this, ev); 146 147 if (mConsumeTouchWhenPanelOpen && shouldConsumeEvent) { 148 return true; 149 } 150 } 151 return super.onInterceptTouchEvent(ev); 152 } 153 154 /** Sets the notifications panel controller. */ setNotificationsPanelController(NotificationsShadeController controller)155 public void setNotificationsPanelController(NotificationsShadeController controller) { 156 mNotificationsShadeController = controller; 157 } 158 159 /** Sets the HVAC panel controller. */ setHvacPanelController(HvacPanelController controller)160 public void setHvacPanelController(HvacPanelController controller) { 161 mHvacPanelController = controller; 162 } 163 164 /** Gets the notifications panel controller. */ getNotificationsPanelController()165 public NotificationsShadeController getNotificationsPanelController() { 166 return mNotificationsShadeController; 167 } 168 169 /** Gets the HVAC panel controller. */ getHvacPanelController()170 public HvacPanelController getHvacPanelController() { 171 return mHvacPanelController; 172 } 173 174 /** 175 * Sets a touch listener that will be called from onInterceptTouchEvent and onTouchEvent 176 * 177 * @param statusBarWindowTouchListener The listener to call from touch and intercept touch 178 */ setStatusBarWindowTouchListener(OnTouchListener statusBarWindowTouchListener)179 public void setStatusBarWindowTouchListener(OnTouchListener statusBarWindowTouchListener) { 180 mStatusBarWindowTouchListener = statusBarWindowTouchListener; 181 } 182 183 /** Gets the touch listener that will be called from onInterceptTouchEvent and onTouchEvent. */ getStatusBarWindowTouchListener()184 public OnTouchListener getStatusBarWindowTouchListener() { 185 return mStatusBarWindowTouchListener; 186 } 187 188 @Override onTouchEvent(MotionEvent event)189 public boolean onTouchEvent(MotionEvent event) { 190 if (mStatusBarWindowTouchListener != null) { 191 mStatusBarWindowTouchListener.onTouch(this, event); 192 } 193 return super.onTouchEvent(event); 194 } 195 onNotificationsClick(View v)196 protected void onNotificationsClick(View v) { 197 if (mNotificationsButton != null 198 && mNotificationsButton.getDisabled()) { 199 mNotificationsButton.runOnClickWhileDisabled(); 200 return; 201 } 202 if (mNotificationsShadeController != null) { 203 mNotificationsShadeController.togglePanel(); 204 } 205 } 206 onHvacClick(View v)207 protected void onHvacClick(View v) { 208 if (mHvacPanelController != null) { 209 mHvacPanelController.togglePanel(); 210 } 211 } 212 213 /** 214 * Shows buttons of the specified {@link ButtonsType}. 215 * 216 * NOTE: Only one type of buttons can be shown at a time, so showing buttons of one type will 217 * hide all buttons of other types. 218 * 219 * @param buttonsType 220 */ showButtonsOfType(@uttonsType int buttonsType)221 public void showButtonsOfType(@ButtonsType int buttonsType) { 222 switch(buttonsType) { 223 case BUTTON_TYPE_NAVIGATION: 224 setNavigationButtonsVisibility(View.VISIBLE); 225 setKeyguardButtonsVisibility(View.GONE); 226 setOcclusionButtonsVisibility(View.GONE); 227 break; 228 case BUTTON_TYPE_KEYGUARD: 229 setNavigationButtonsVisibility(View.GONE); 230 setKeyguardButtonsVisibility(View.VISIBLE); 231 setOcclusionButtonsVisibility(View.GONE); 232 break; 233 case BUTTON_TYPE_OCCLUSION: 234 setNavigationButtonsVisibility(View.GONE); 235 setKeyguardButtonsVisibility(View.GONE); 236 setOcclusionButtonsVisibility(View.VISIBLE); 237 break; 238 } 239 } 240 241 /** 242 * Sets the system bar view's disabled state and runnable when disabled. 243 */ setLockTaskDisabledButton(int viewId, boolean disabled, Runnable runnable)244 public void setLockTaskDisabledButton(int viewId, boolean disabled, Runnable runnable) { 245 CarSystemBarButton button = findViewById(viewId); 246 if (button != null) { 247 button.setDisabled(disabled, runnable); 248 } 249 } 250 251 /** 252 * Sets the system bar ViewGroup container's visibility 253 */ setLockTaskDisabledContainer(int viewId, @View.Visibility int visibility)254 public void setLockTaskDisabledContainer(int viewId, @View.Visibility int visibility) { 255 View v = findViewById(viewId); 256 if (v == null) { 257 Log.e(TAG, "setLockTaskViewVisibility for: " + viewId + " not found"); 258 return; 259 } 260 if (v instanceof ViewGroup) { 261 ViewGroup group = (ViewGroup) v; 262 for (int i = 0; i < group.getChildCount(); i++) { 263 group.getChildAt(i).setVisibility(visibility); 264 } 265 return; 266 } 267 v.setVisibility(visibility); 268 } 269 270 /** 271 * Sets the HvacPanelOverlayViewController and adds HVAC button listeners 272 */ registerHvacPanelOverlayViewController(HvacPanelOverlayViewController controller)273 public void registerHvacPanelOverlayViewController(HvacPanelOverlayViewController controller) { 274 mHvacPanelOverlayViewController = controller; 275 if (mHvacPanelOverlayViewController != null && mHvacButton != null) { 276 mHvacPanelOverlayViewController.registerViewStateListener(mHvacButton); 277 } 278 } 279 setNavigationButtonsVisibility(@iew.Visibility int visibility)280 private void setNavigationButtonsVisibility(@View.Visibility int visibility) { 281 if (mNavButtons != null) { 282 mNavButtons.setVisibility(visibility); 283 } 284 } 285 setKeyguardButtonsVisibility(@iew.Visibility int visibility)286 private void setKeyguardButtonsVisibility(@View.Visibility int visibility) { 287 if (mLockScreenButtons != null) { 288 mLockScreenButtons.setVisibility(visibility); 289 } 290 } 291 setOcclusionButtonsVisibility(@iew.Visibility int visibility)292 private void setOcclusionButtonsVisibility(@View.Visibility int visibility) { 293 if (mOcclusionButtons != null) { 294 mOcclusionButtons.setVisibility(visibility); 295 } 296 } 297 298 /** 299 * Toggles the notification unseen indicator on/off. 300 * 301 * @param hasUnseen true if the unseen notification count is great than 0. 302 */ toggleNotificationUnseenIndicator(Boolean hasUnseen)303 public void toggleNotificationUnseenIndicator(Boolean hasUnseen) { 304 if (mNotificationsButton == null) return; 305 306 mNotificationsButton.setUnseen(hasUnseen); 307 } 308 } 309