• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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