1 /* 2 * Copyright (C) 2024 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 package com.android.systemui.car.systembar; 17 18 import android.annotation.Nullable; 19 import android.content.Context; 20 import android.os.Bundle; 21 import android.view.MotionEvent; 22 import android.view.View; 23 import android.view.ViewGroup; 24 import android.widget.FrameLayout; 25 26 import androidx.annotation.IdRes; 27 import androidx.annotation.VisibleForTesting; 28 29 import com.android.car.ui.FocusParkingView; 30 import com.android.car.ui.utils.ViewUtils; 31 import com.android.systemui.Gefingerpoken; 32 import com.android.systemui.R; 33 import com.android.systemui.car.systembar.CarSystemBarController.SystemBarSide; 34 import com.android.systemui.car.systembar.element.CarSystemBarElementInitializer; 35 import com.android.systemui.car.window.OverlayPanelViewController; 36 import com.android.systemui.car.window.OverlayViewController; 37 import com.android.systemui.car.window.OverlayVisibilityMediator; 38 import com.android.systemui.settings.UserTracker; 39 import com.android.systemui.util.ViewController; 40 41 import dagger.Lazy; 42 import dagger.assisted.Assisted; 43 import dagger.assisted.AssistedFactory; 44 import dagger.assisted.AssistedInject; 45 46 import java.util.Set; 47 48 /** 49 * A controller for initializing the system bar views. 50 */ 51 public class CarSystemBarViewControllerImpl 52 extends ViewController<CarSystemBarViewControllerImpl.TouchInterceptingFrameLayout> 53 implements CarSystemBarViewController, Gefingerpoken { 54 55 private static final String LAST_FOCUSED_VIEW_ID = "last_focused_view_id"; 56 57 protected final Context mContext; 58 59 private final UserTracker mUserTracker; 60 private final CarSystemBarElementInitializer mCarSystemBarElementInitializer; 61 private final SystemBarConfigs mSystemBarConfigs; 62 private final ButtonRoleHolderController mButtonRoleHolderController; 63 private final Lazy<MicPrivacyChipViewController> mMicPrivacyChipViewControllerLazy; 64 private final Lazy<CameraPrivacyChipViewController> mCameraPrivacyChipViewControllerLazy; 65 private final @SystemBarSide int mSide; 66 private final OverlayVisibilityMediator mOverlayVisibilityMediator; 67 68 private final boolean mConsumeTouchWhenPanelOpen; 69 private final boolean mButtonsDraggable; 70 private View mNavButtons; 71 private View mLockScreenButtons; 72 private View mOcclusionButtons; 73 // used to wire in open/close gestures for overlay panels 74 private Set<View.OnTouchListener> mSystemBarTouchListeners; 75 76 @AssistedInject CarSystemBarViewControllerImpl(Context context, UserTracker userTracker, CarSystemBarElementInitializer elementInitializer, SystemBarConfigs systemBarConfigs, ButtonRoleHolderController buttonRoleHolderController, Lazy<CameraPrivacyChipViewController> cameraPrivacyChipViewControllerLazy, Lazy<MicPrivacyChipViewController> micPrivacyChipViewControllerLazy, OverlayVisibilityMediator overlayVisibilityMediator, @Assisted @SystemBarSide int side, @Assisted ViewGroup systemBarView)77 public CarSystemBarViewControllerImpl(Context context, 78 UserTracker userTracker, 79 CarSystemBarElementInitializer elementInitializer, 80 SystemBarConfigs systemBarConfigs, 81 ButtonRoleHolderController buttonRoleHolderController, 82 Lazy<CameraPrivacyChipViewController> cameraPrivacyChipViewControllerLazy, 83 Lazy<MicPrivacyChipViewController> micPrivacyChipViewControllerLazy, 84 OverlayVisibilityMediator overlayVisibilityMediator, 85 @Assisted @SystemBarSide int side, 86 @Assisted ViewGroup systemBarView) { 87 super(new TouchInterceptingFrameLayout(context, systemBarView)); 88 89 mContext = context; 90 mUserTracker = userTracker; 91 mCarSystemBarElementInitializer = elementInitializer; 92 mSystemBarConfigs = systemBarConfigs; 93 mButtonRoleHolderController = buttonRoleHolderController; 94 mCameraPrivacyChipViewControllerLazy = cameraPrivacyChipViewControllerLazy; 95 mMicPrivacyChipViewControllerLazy = micPrivacyChipViewControllerLazy; 96 mSide = side; 97 mOverlayVisibilityMediator = overlayVisibilityMediator; 98 99 mConsumeTouchWhenPanelOpen = getResources().getBoolean( 100 R.bool.config_consumeSystemBarTouchWhenNotificationPanelOpen); 101 mButtonsDraggable = getResources().getBoolean(R.bool.config_systemBarButtonsDraggable); 102 } 103 104 @Override onInit()105 protected void onInit() { 106 // Include a FocusParkingView at the beginning. The rotary controller "parks" the focus here 107 // when the user navigates to another window. This is also used to prevent wrap-around. 108 mView.addView(new FocusParkingView(mContext), 0); 109 mView.setTouchListener(this); 110 111 setupSystemBarButtons(mView, mUserTracker); 112 mCarSystemBarElementInitializer.initializeCarSystemBarElements(mView); 113 114 mNavButtons = mView.findViewById(R.id.nav_buttons); 115 mLockScreenButtons = mView.findViewById(R.id.lock_screen_nav_buttons); 116 mOcclusionButtons = mView.findViewById(R.id.occlusion_buttons); 117 // Needs to be clickable so that it will receive ACTION_MOVE events. 118 mView.setClickable(true); 119 // Needs to not be focusable so rotary won't highlight the entire nav bar. 120 mView.setFocusable(false); 121 } 122 123 @Override onSaveInstanceState(Bundle outState)124 public void onSaveInstanceState(Bundle outState) { 125 // The focused view will be destroyed during re-layout, causing the framework to adjust 126 // the focus unexpectedly. To avoid that, move focus to a view that won't be 127 // destroyed during re-layout and has no focus highlight (the FocusParkingView), then 128 // move focus back to the previously focused view after re-layout. 129 outState.putInt(LAST_FOCUSED_VIEW_ID, cacheAndHideFocus(mView)); 130 } 131 132 @Override onRestoreInstanceState(Bundle savedInstanceState)133 public void onRestoreInstanceState(Bundle savedInstanceState) { 134 restoreFocus(mView, savedInstanceState.getInt(LAST_FOCUSED_VIEW_ID, View.NO_ID)); 135 } 136 137 @Override getView()138 public ViewGroup getView() { 139 return mView; 140 } 141 142 @Override setSystemBarTouchListeners( Set<View.OnTouchListener> systemBarTouchListeners)143 public void setSystemBarTouchListeners( 144 Set<View.OnTouchListener> systemBarTouchListeners) { 145 mSystemBarTouchListeners = systemBarTouchListeners; 146 } 147 148 @Override showButtonsOfType(@uttonsType int buttonsType)149 public void showButtonsOfType(@ButtonsType int buttonsType) { 150 switch(buttonsType) { 151 case BUTTON_TYPE_NAVIGATION: 152 setNavigationButtonsVisibility(View.VISIBLE); 153 setKeyguardButtonsVisibility(View.GONE); 154 setOcclusionButtonsVisibility(View.GONE); 155 break; 156 case BUTTON_TYPE_KEYGUARD: 157 setNavigationButtonsVisibility(View.GONE); 158 setKeyguardButtonsVisibility(View.VISIBLE); 159 setOcclusionButtonsVisibility(View.GONE); 160 break; 161 case BUTTON_TYPE_OCCLUSION: 162 setNavigationButtonsVisibility(View.GONE); 163 setKeyguardButtonsVisibility(View.GONE); 164 setOcclusionButtonsVisibility(View.VISIBLE); 165 break; 166 } 167 } 168 169 /** 170 * Used to forward touch events even if the touch was initiated from a child component 171 */ 172 @Override onInterceptTouchEvent(MotionEvent ev)173 public boolean onInterceptTouchEvent(MotionEvent ev) { 174 if (mSystemBarTouchListeners != null && !mSystemBarTouchListeners.isEmpty()) { 175 if (!mButtonsDraggable) { 176 return false; 177 } 178 179 OverlayViewController topOverlay = 180 mOverlayVisibilityMediator.getHighestZOrderOverlayViewController(); 181 boolean shouldConsumeEvent = topOverlay instanceof OverlayPanelViewController 182 ? ((OverlayPanelViewController) topOverlay).shouldPanelConsumeSystemBarTouch() 183 : false; 184 185 // Forward touch events to the status bar window so it can drag 186 // windows if required (ex. Notification shade) 187 triggerAllTouchListeners(mView, ev); 188 189 if (mConsumeTouchWhenPanelOpen && shouldConsumeEvent) { 190 return true; 191 } 192 } 193 return false; 194 } 195 196 /** 197 * Used for forwarding onTouch events on the systembar. 198 */ 199 @Override onTouchEvent(MotionEvent event)200 public boolean onTouchEvent(MotionEvent event) { 201 triggerAllTouchListeners(mView, event); 202 return false; 203 } 204 205 @Override onViewAttached()206 protected void onViewAttached() { 207 mSystemBarConfigs.insetSystemBar(mSide, mView); 208 209 mButtonRoleHolderController.addAllButtonsWithRoleName(mView); 210 mMicPrivacyChipViewControllerLazy.get().addPrivacyChipView(mView); 211 mCameraPrivacyChipViewControllerLazy.get().addPrivacyChipView(mView); 212 } 213 214 @Override onViewDetached()215 protected void onViewDetached() { 216 mButtonRoleHolderController.removeAll(); 217 mMicPrivacyChipViewControllerLazy.get().removeAll(); 218 mCameraPrivacyChipViewControllerLazy.get().removeAll(); 219 } 220 221 @AssistedFactory 222 public interface Factory 223 extends CarSystemBarViewControllerFactory<CarSystemBarViewControllerImpl> { 224 } 225 setupSystemBarButtons(View v, UserTracker userTracker)226 private void setupSystemBarButtons(View v, UserTracker userTracker) { 227 if (v instanceof CarSystemBarButton) { 228 ((CarSystemBarButton) v).setUserTracker(userTracker); 229 } else if (v instanceof ViewGroup) { 230 ViewGroup viewGroup = (ViewGroup) v; 231 for (int i = 0; i < viewGroup.getChildCount(); i++) { 232 setupSystemBarButtons(viewGroup.getChildAt(i), userTracker); 233 } 234 } 235 } 236 setNavigationButtonsVisibility(@iew.Visibility int visibility)237 private void setNavigationButtonsVisibility(@View.Visibility int visibility) { 238 if (mNavButtons != null) { 239 mNavButtons.setVisibility(visibility); 240 } 241 } 242 setKeyguardButtonsVisibility(@iew.Visibility int visibility)243 private void setKeyguardButtonsVisibility(@View.Visibility int visibility) { 244 if (mLockScreenButtons != null) { 245 mLockScreenButtons.setVisibility(visibility); 246 } 247 } 248 setOcclusionButtonsVisibility(@iew.Visibility int visibility)249 private void setOcclusionButtonsVisibility(@View.Visibility int visibility) { 250 if (mOcclusionButtons != null) { 251 mOcclusionButtons.setVisibility(visibility); 252 } 253 } 254 triggerAllTouchListeners(View view, MotionEvent event)255 private void triggerAllTouchListeners(View view, MotionEvent event) { 256 if (mSystemBarTouchListeners == null) { 257 return; 258 } 259 for (View.OnTouchListener listener : mSystemBarTouchListeners) { 260 listener.onTouch(view, event); 261 } 262 } 263 264 @VisibleForTesting cacheAndHideFocus(@ullable View rootView)265 static int cacheAndHideFocus(@Nullable View rootView) { 266 if (rootView == null) return View.NO_ID; 267 View focusedView = rootView.findFocus(); 268 if (focusedView == null || focusedView instanceof FocusParkingView) return View.NO_ID; 269 int focusedViewId = focusedView.getId(); 270 ViewUtils.hideFocus(rootView); 271 return focusedViewId; 272 } 273 restoreFocus(@ullable View rootView, @IdRes int viewToFocusId)274 private static boolean restoreFocus(@Nullable View rootView, @IdRes int viewToFocusId) { 275 if (rootView == null || viewToFocusId == View.NO_ID) return false; 276 View focusedView = rootView.findViewById(viewToFocusId); 277 if (focusedView == null) return false; 278 focusedView.requestFocus(); 279 return true; 280 } 281 282 static class TouchInterceptingFrameLayout extends FrameLayout { 283 @Nullable 284 private Gefingerpoken mTouchListener; 285 TouchInterceptingFrameLayout(Context context, ViewGroup content)286 TouchInterceptingFrameLayout(Context context, ViewGroup content) { 287 super(context); 288 addView(content); 289 } 290 setTouchListener(@ullable Gefingerpoken listener)291 void setTouchListener(@Nullable Gefingerpoken listener) { 292 mTouchListener = listener; 293 } 294 295 /** Called when a touch is being intercepted in a ViewGroup. */ 296 @Override onInterceptTouchEvent(MotionEvent ev)297 public boolean onInterceptTouchEvent(MotionEvent ev) { 298 return (mTouchListener != null && mTouchListener 299 .onInterceptTouchEvent(ev)) ? true : super.onInterceptTouchEvent(ev); 300 } 301 302 /** Called when a touch is being handled by a view. */ 303 @Override onTouchEvent(MotionEvent ev)304 public boolean onTouchEvent(MotionEvent ev) { 305 return (mTouchListener != null && mTouchListener 306 .onTouchEvent(ev)) ? true : super.onTouchEvent(ev); 307 } 308 } 309 } 310