1 /* 2 * Copyright (C) 2022 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.wm; 18 19 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_MOVING; 20 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_UNKNOWN; 21 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; 22 import static android.view.InsetsState.ITYPE_STATUS_BAR; 23 24 import android.car.Car; 25 import android.car.drivingstate.CarDrivingStateEvent; 26 import android.car.drivingstate.CarDrivingStateManager; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.os.Handler; 30 import android.os.RemoteException; 31 import android.util.Slog; 32 import android.util.SparseArray; 33 import android.view.IWindowManager; 34 import android.view.InsetsVisibilities; 35 import android.view.WindowInsets; 36 import android.widget.Toast; 37 38 import com.android.car.ui.R; 39 import com.android.wm.shell.common.DisplayController; 40 import com.android.wm.shell.common.DisplayInsetsController; 41 import com.android.wm.shell.dagger.WMSingleton; 42 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.Objects; 46 47 /** 48 * Controller that expands upon {@link DisplaySystemBarsController} but allows for immersive 49 * mode overrides and notification in other SystemUI classes via the provided methods and callbacks. 50 */ 51 @WMSingleton 52 public class CarUiPortraitDisplaySystemBarsController extends DisplaySystemBarsController { 53 private static final String TAG = "CarUiPortraitDisplaySystemBarsController"; 54 private SparseArray<CarUiPortraitPerDisplay> mCarUiPerDisplaySparseArray; 55 56 private int mCurrentDrivingState = DRIVING_STATE_UNKNOWN; 57 58 private final CarDrivingStateManager.CarDrivingStateEventListener mDrivingStateEventListener = 59 this::handleDrivingStateChange; 60 CarUiPortraitDisplaySystemBarsController(Context context, IWindowManager wmService, DisplayController displayController, DisplayInsetsController displayInsetsController, Handler mainHandler)61 public CarUiPortraitDisplaySystemBarsController(Context context, 62 IWindowManager wmService, 63 DisplayController displayController, 64 DisplayInsetsController displayInsetsController, 65 Handler mainHandler) { 66 super(context, wmService, displayController, displayInsetsController, mainHandler); 67 68 Car car = Car.createCar(context); 69 if (car != null) { 70 CarDrivingStateManager mDrivingStateManager = 71 (CarDrivingStateManager) car.getCarManager(Car.CAR_DRIVING_STATE_SERVICE); 72 mDrivingStateManager.registerListener(mDrivingStateEventListener); 73 mDrivingStateEventListener.onDrivingStateChanged( 74 mDrivingStateManager.getCurrentCarDrivingState()); 75 } else { 76 Slog.e(TAG, "Failed to initialize car"); 77 } 78 } 79 80 @Override onDisplayAdded(int displayId)81 public void onDisplayAdded(int displayId) { 82 CarUiPortraitPerDisplay pd = new CarUiPortraitPerDisplay(displayId); 83 pd.register(); 84 if (mCarUiPerDisplaySparseArray == null) { 85 mCarUiPerDisplaySparseArray = new SparseArray<>(); 86 BarControlPolicy.reloadFromSetting(mContext); 87 BarControlPolicy.registerContentObserver(mContext, mHandler, () -> { 88 int size = mCarUiPerDisplaySparseArray.size(); 89 for (int i = 0; i < size; i++) { 90 mCarUiPerDisplaySparseArray.valueAt(i) 91 .updateDisplayWindowRequestedVisibilities(); 92 } 93 }); 94 } 95 mCarUiPerDisplaySparseArray.put(displayId, pd); 96 } 97 98 @Override onDisplayRemoved(int displayId)99 public void onDisplayRemoved(int displayId) { 100 CarUiPortraitPerDisplay pd = mCarUiPerDisplaySparseArray.get(displayId); 101 pd.unregister(); 102 mCarUiPerDisplaySparseArray.remove(displayId); 103 } 104 105 /** 106 * Request an immersive mode override for a particular display id. This request will override 107 * the usual BarControlPolicy until the package or requested visibilites change. 108 */ requestImmersiveMode(int displayId, boolean immersive)109 public void requestImmersiveMode(int displayId, boolean immersive) { 110 CarUiPortraitPerDisplay display = mCarUiPerDisplaySparseArray.get(displayId); 111 if (display == null) { 112 return; 113 } 114 display.setImmersiveMode(immersive); 115 } 116 117 /** 118 * Request an immersive mode override for a particular display id specifically for setup wizard. 119 * This request will override the usual BarControlPolicy and will persist until explicitly 120 * revoked. 121 */ requestImmersiveModeForSUW(int displayId, boolean immersive)122 public void requestImmersiveModeForSUW(int displayId, boolean immersive) { 123 CarUiPortraitPerDisplay display = mCarUiPerDisplaySparseArray.get(displayId); 124 if (display == null) { 125 return; 126 } 127 display.setImmersiveModeForSUW(immersive); 128 } 129 130 /** 131 * Register an immersive mode callback for a particular display. 132 */ registerCallback(int displayId, Callback callback)133 public void registerCallback(int displayId, Callback callback) { 134 CarUiPortraitPerDisplay display = mCarUiPerDisplaySparseArray.get(displayId); 135 if (display == null) { 136 return; 137 } 138 display.addCallbackForDisplay(callback); 139 } 140 141 /** 142 * Unregister an immersive mode callback for a particular display. 143 */ unregisterCallback(int displayId, Callback callback)144 public void unregisterCallback(int displayId, Callback callback) { 145 CarUiPortraitPerDisplay display = mCarUiPerDisplaySparseArray.get(displayId); 146 if (display == null) { 147 return; 148 } 149 display.removeCallbackForDisplay(callback); 150 } 151 handleDrivingStateChange(CarDrivingStateEvent event)152 private void handleDrivingStateChange(CarDrivingStateEvent event) { 153 mCurrentDrivingState = event.eventValue; 154 if (mCarUiPerDisplaySparseArray != null) { 155 for (int i = 0; i < mCarUiPerDisplaySparseArray.size(); i++) { 156 mCarUiPerDisplaySparseArray.valueAt(i).onDrivingStateChanged(); 157 } 158 } 159 } 160 161 class CarUiPortraitPerDisplay extends DisplaySystemBarsController.PerDisplay { 162 private final int[] mImmersiveVisibilities = new int[] {0, WindowInsets.Type.systemBars()}; 163 private final List<Callback> mCallbacks = new ArrayList<>(); 164 private InsetsVisibilities mWindowRequestedVisibilities; 165 private InsetsVisibilities mAppliedVisibilities = new InsetsVisibilities(); 166 private boolean mImmersiveOverride = false; 167 private boolean mImmersiveForSUW = false; 168 CarUiPortraitPerDisplay(int displayId)169 CarUiPortraitPerDisplay(int displayId) { 170 super(displayId); 171 } 172 173 @Override topFocusedWindowChanged(ComponentName component, InsetsVisibilities requestedVisibilities)174 public void topFocusedWindowChanged(ComponentName component, 175 InsetsVisibilities requestedVisibilities) { 176 boolean requestedVisibilitiesChanged = false; 177 if (requestedVisibilities != null) { 178 if (!requestedVisibilities.equals(mWindowRequestedVisibilities)) { 179 mWindowRequestedVisibilities = requestedVisibilities; 180 boolean immersive = !mWindowRequestedVisibilities.getVisibility( 181 ITYPE_STATUS_BAR) && !mWindowRequestedVisibilities.getVisibility( 182 ITYPE_NAVIGATION_BAR); 183 notifyOnImmersiveRequestedChanged(component, immersive); 184 if (!immersive) { 185 mImmersiveOverride = false; 186 requestedVisibilitiesChanged = true; 187 } 188 } 189 } else if (mWindowRequestedVisibilities != null) { 190 mWindowRequestedVisibilities = null; 191 notifyOnImmersiveRequestedChanged(component, false); 192 requestedVisibilitiesChanged = true; 193 } 194 String packageName = component != null ? component.getPackageName() : null; 195 if (Objects.equals(mPackageName, packageName) && !requestedVisibilitiesChanged) { 196 return; 197 } 198 mPackageName = packageName; 199 mImmersiveOverride = false; // reset override when changing application 200 updateDisplayWindowRequestedVisibilities(); 201 } 202 203 @Override updateDisplayWindowRequestedVisibilities()204 protected void updateDisplayWindowRequestedVisibilities() { 205 if (mPackageName == null && !mImmersiveOverride && !mImmersiveForSUW) { 206 return; 207 } 208 int[] barVisibilities = mImmersiveOverride || mImmersiveForSUW 209 ? mImmersiveVisibilities 210 : BarControlPolicy.getBarVisibilities(mPackageName); 211 updateRequestedVisibilities(barVisibilities[0], /* visible= */ true); 212 updateRequestedVisibilities(barVisibilities[1], /* visible= */ false); 213 214 // Return if the requested visibility is already applied. 215 if (mAppliedVisibilities.equals(mRequestedVisibilities)) { 216 return; 217 } 218 mAppliedVisibilities.set(mRequestedVisibilities); 219 220 showInsets(barVisibilities[0], /* fromIme= */ false); 221 hideInsets(barVisibilities[1], /* fromIme= */ false); 222 223 boolean immersiveState = mImmersiveOverride || mImmersiveForSUW || ( 224 (barVisibilities[1] & (WindowInsets.Type.statusBars() 225 | WindowInsets.Type.navigationBars())) == ( 226 WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars())); 227 notifyOnImmersiveStateChanged(immersiveState); 228 229 try { 230 mWmService.updateDisplayWindowRequestedVisibilities(mDisplayId, 231 mRequestedVisibilities); 232 } catch (RemoteException e) { 233 Slog.w(TAG, "Unable to update window manager service."); 234 } 235 } 236 setImmersiveMode(boolean immersive)237 void setImmersiveMode(boolean immersive) { 238 if (mImmersiveOverride == immersive) { 239 return; 240 } 241 if (immersive && mCurrentDrivingState == DRIVING_STATE_MOVING) { 242 Toast.makeText(mContext, 243 R.string.car_ui_restricted_while_driving, Toast.LENGTH_LONG).show(); 244 return; 245 } 246 mImmersiveOverride = immersive; 247 updateDisplayWindowRequestedVisibilities(); 248 } 249 setImmersiveModeForSUW(boolean immersive)250 void setImmersiveModeForSUW(boolean immersive) { 251 if (mImmersiveForSUW == immersive) { 252 return; 253 } 254 mImmersiveForSUW = immersive; 255 updateDisplayWindowRequestedVisibilities(); 256 } 257 addCallbackForDisplay(Callback callback)258 void addCallbackForDisplay(Callback callback) { 259 if (mCallbacks.contains(callback)) return; 260 mCallbacks.add(callback); 261 } 262 removeCallbackForDisplay(Callback callback)263 void removeCallbackForDisplay(Callback callback) { 264 mCallbacks.remove(callback); 265 } 266 notifyOnImmersiveStateChanged(boolean immersive)267 void notifyOnImmersiveStateChanged(boolean immersive) { 268 for (Callback callback : mCallbacks) { 269 callback.onImmersiveStateChanged(immersive); 270 } 271 } 272 notifyOnImmersiveRequestedChanged(ComponentName component, boolean requested)273 void notifyOnImmersiveRequestedChanged(ComponentName component, boolean requested) { 274 for (Callback callback : mCallbacks) { 275 callback.onImmersiveRequestedChanged(component, requested); 276 } 277 } 278 onDrivingStateChanged()279 void onDrivingStateChanged() { 280 if (mImmersiveOverride && mCurrentDrivingState == DRIVING_STATE_MOVING) { 281 mImmersiveOverride = false; 282 updateDisplayWindowRequestedVisibilities(); 283 } 284 } 285 } 286 287 /** 288 * Callback for notifying changes to the immersive and immersive request states. 289 */ 290 public interface Callback { 291 /** 292 * Callback triggered when the current package's requested visibilities change has caused 293 * an immersive request change. 294 */ onImmersiveRequestedChanged(ComponentName component, boolean requested)295 void onImmersiveRequestedChanged(ComponentName component, boolean requested); 296 297 /** 298 * Callback triggered when the immersive override state changes. 299 */ onImmersiveStateChanged(boolean immersive)300 void onImmersiveStateChanged(boolean immersive); 301 } 302 } 303