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.wm;
18 
19 import static android.content.Intent.ACTION_OVERLAY_CHANGED;
20 import static android.view.WindowInsets.Type.navigationBars;
21 import static android.view.WindowInsets.Type.statusBars;
22 import static android.view.WindowInsets.Type.systemBars;
23 
24 import static com.android.systemui.car.users.CarSystemUIUserUtil.isSecondaryMUMDSystemUI;
25 
26 import android.annotation.Nullable;
27 import android.content.BroadcastReceiver;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.os.Handler;
33 import android.os.PatternMatcher;
34 import android.os.RemoteException;
35 import android.os.UserHandle;
36 import android.util.Log;
37 import android.util.Slog;
38 import android.util.SparseArray;
39 import android.view.IDisplayWindowInsetsController;
40 import android.view.IWindowManager;
41 import android.view.InsetsController;
42 import android.view.InsetsSourceControl;
43 import android.view.InsetsState;
44 import android.view.WindowInsets;
45 import android.view.WindowInsets.Type.InsetsType;
46 import android.view.inputmethod.ImeTracker;
47 import android.view.inputmethod.InputMethodManager;
48 
49 import androidx.annotation.VisibleForTesting;
50 
51 import com.android.systemui.R;
52 import com.android.systemui.dagger.qualifiers.Main;
53 import com.android.wm.shell.common.DisplayController;
54 import com.android.wm.shell.common.DisplayInsetsController;
55 
56 import java.util.Arrays;
57 
58 /**
59  * Controller that maps between displays and {@link IDisplayWindowInsetsController} in order to
60  * give system bar control to SystemUI.
61  * {@link R.bool#config_remoteInsetsControllerControlsSystemBars} determines whether this controller
62  * takes control or not.
63  */
64 public class DisplaySystemBarsController implements DisplayController.OnDisplaysChangedListener {
65 
66     private static final String TAG = DisplaySystemBarsController.class.getSimpleName();
67     private static final int STATE_NON_IMMERSIVE = systemBars();
68     private static final int STATE_IMMERSIVE_WITH_NAV_BAR = navigationBars();
69     private static final int STATE_IMMERSIVE_WITH_STATUS_BAR = statusBars();
70     private static final int STATE_IMMERSIVE = 0;
71     private static final boolean DEBUG = Log.isLoggable(DisplayController.class.getSimpleName(),
72             Log.DEBUG);
73 
74     protected final Context mContext;
75     protected final IWindowManager mWmService;
76     protected final DisplayInsetsController mDisplayInsetsController;
77     protected final Handler mHandler;
78 
79     private final int[] mDefaultVisibilities =
80             new int[]{WindowInsets.Type.systemBars(), 0};
81     private final int[] mImmersiveWithNavBarVisibilities = new int[]{
82             WindowInsets.Type.navigationBars() | WindowInsets.Type.captionBar()
83                     | WindowInsets.Type.systemOverlays(),
84             WindowInsets.Type.statusBars()
85     };
86     private final int[] mImmersiveWithStatusBarVisibilities = new int[]{
87             WindowInsets.Type.statusBars() | WindowInsets.Type.captionBar()
88                     | WindowInsets.Type.systemOverlays(),
89             WindowInsets.Type.navigationBars()
90     };
91     private final int[] mImmersiveVisibilities =
92             new int[]{0, WindowInsets.Type.systemBars()};
93 
94     @VisibleForTesting
95     SparseArray<PerDisplay> mPerDisplaySparseArray;
96     @InsetsType
97     private int mWindowRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
98     @InsetsType
99     private int mAppRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
100     @InsetsType
101     private int mImmersiveState = systemBars();
102 
DisplaySystemBarsController( Context context, IWindowManager wmService, DisplayController displayController, DisplayInsetsController displayInsetsController, @Main Handler mainHandler)103     public DisplaySystemBarsController(
104             Context context,
105             IWindowManager wmService,
106             DisplayController displayController,
107             DisplayInsetsController displayInsetsController,
108             @Main Handler mainHandler) {
109         mContext = context;
110         mWmService = wmService;
111         mDisplayInsetsController = displayInsetsController;
112         mHandler = mainHandler;
113         if (!isSecondaryMUMDSystemUI()) {
114             // This WM controller should only be initialized once for the primary SystemUI, as it
115             // will affect insets on all displays.
116             // TODO(b/262773276): support per-user remote inset controllers
117             displayController.addDisplayWindowListener(this);
118         }
119     }
120 
121     @Override
onDisplayAdded(int displayId)122     public void onDisplayAdded(int displayId) {
123         PerDisplay pd = new PerDisplay(displayId);
124         pd.register();
125         // Lazy loading policy control filters instead of during boot.
126         if (mPerDisplaySparseArray == null) {
127             mPerDisplaySparseArray = new SparseArray<>();
128             BarControlPolicy.reloadFromSetting(mContext);
129             BarControlPolicy.registerContentObserver(mContext, mHandler, () -> {
130                 int size = mPerDisplaySparseArray.size();
131                 for (int i = 0; i < size; i++) {
132                     mPerDisplaySparseArray.valueAt(i).updateDisplayWindowRequestedVisibleTypes();
133                 }
134             });
135         }
136         mPerDisplaySparseArray.put(displayId, pd);
137     }
138 
139     @Override
onDisplayRemoved(int displayId)140     public void onDisplayRemoved(int displayId) {
141         PerDisplay pd = mPerDisplaySparseArray.get(displayId);
142         pd.unregister();
143         mPerDisplaySparseArray.remove(displayId);
144     }
145 
146     class PerDisplay implements DisplayInsetsController.OnInsetsChangedListener {
147         private static final String OVERLAY_FILTER_DATA_SCHEME = "package";
148 
149         int mDisplayId;
150         InsetsController mInsetsController;
151         @InsetsType
152         int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
153         String mPackageName;
154         int mBehavior = 0;
155 
PerDisplay(int displayId)156         PerDisplay(int displayId) {
157             mDisplayId = displayId;
158             InputMethodManager inputMethodManager =
159                     mContext.getSystemService(InputMethodManager.class);
160             mInsetsController = new InsetsController(
161                     new DisplaySystemBarsInsetsControllerHost(mHandler, requestedVisibleTypes -> {
162                         mRequestedVisibleTypes = requestedVisibleTypes;
163                         updateDisplayWindowRequestedVisibleTypes();
164                     }, inputMethodManager)
165             );
166             mBehavior = mContext.getResources().getInteger(
167                     R.integer.config_systemBarPersistency);
168             registerOverlayChangeBroadcastReceiver();
169         }
170 
register()171         public void register() {
172             mDisplayInsetsController.addInsetsChangedListener(mDisplayId, this);
173         }
174 
unregister()175         public void unregister() {
176             mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, this);
177         }
178 
179         @Override
insetsChanged(InsetsState insetsState)180         public void insetsChanged(InsetsState insetsState) {
181             mInsetsController.onStateChanged(insetsState);
182             updateDisplayWindowRequestedVisibleTypes();
183         }
184 
185         @Override
hideInsets(@nsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)186         public void hideInsets(@InsetsType int types, boolean fromIme,
187                 @Nullable ImeTracker.Token statsToken) {
188             if ((types & WindowInsets.Type.ime()) == 0) {
189                 mInsetsController.hide(types, /* fromIme = */ false, statsToken);
190             }
191         }
192 
193         @Override
showInsets(@nsetsType int types, boolean fromIme, @Nullable ImeTracker.Token statsToken)194         public void showInsets(@InsetsType int types, boolean fromIme,
195                 @Nullable ImeTracker.Token statsToken) {
196             if ((types & WindowInsets.Type.ime()) == 0) {
197                 mInsetsController.show(types, /* fromIme= */ false, statsToken);
198             }
199         }
200 
201         @Override
insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls)202         public void insetsControlChanged(InsetsState insetsState,
203                 InsetsSourceControl[] activeControls) {
204             InsetsSourceControl[] nonImeControls = null;
205             // Need to filter out IME control to prevent control after leash is released
206             if (activeControls != null) {
207                 nonImeControls = Arrays.stream(activeControls).filter(
208                         c -> c.getType() != WindowInsets.Type.ime()).toArray(
209                         InsetsSourceControl[]::new);
210             }
211             mInsetsController.onControlsChanged(nonImeControls);
212         }
213 
214         @Override
topFocusedWindowChanged(ComponentName component, @InsetsType int requestedVisibleTypes)215         public void topFocusedWindowChanged(ComponentName component,
216                 @InsetsType int requestedVisibleTypes) {
217             String packageName = component != null ? component.getPackageName() : null;
218             boolean showNavRequest =
219                     (requestedVisibleTypes & navigationBars()) == navigationBars();
220             boolean showStatusRequest =
221                     (requestedVisibleTypes & statusBars()) == statusBars();
222 
223 
224             boolean maybeUpdate = (mWindowRequestedVisibleTypes != requestedVisibleTypes || (
225                     mPackageName != null && !mPackageName.equals(packageName)));
226 
227             if (DEBUG) {
228                 Slog.d(TAG, "topFocusedWindowChanged behavior = " + mBehavior
229                         + ", component = " + component
230                         + ", requestedVisibleTypes = " + requestedVisibleTypes
231                         + ", showNavRequest = " + showNavRequest
232                         + ", showStatusRequest = " + showStatusRequest
233                         + ", mWindowRequestedVisibleTypes = " + mWindowRequestedVisibleTypes
234                         + ", mPackageName = " + mPackageName
235                         + ", maybeUpdate = " + maybeUpdate);
236             }
237 
238             if (maybeUpdate) {
239                 if (mBehavior == 1) {
240                     mImmersiveState = 0;
241                     if (showNavRequest) {
242                         mImmersiveState |= navigationBars();
243                     }
244                     if (showStatusRequest) {
245                         mImmersiveState |= statusBars();
246                     }
247                 } else if (mBehavior == 2) {
248                     mImmersiveState = navigationBars();
249                     if (showStatusRequest) {
250                         mImmersiveState |= statusBars();
251                     }
252                 }
253             } else {
254                 mImmersiveState = STATE_NON_IMMERSIVE;
255             }
256 
257             mPackageName = packageName;
258             updateDisplayWindowRequestedVisibleTypes();
259         }
260 
261         @Override
setImeInputTargetRequestedVisibility(boolean visible)262         public void setImeInputTargetRequestedVisibility(boolean visible) {
263             // TODO
264         }
265 
registerOverlayChangeBroadcastReceiver()266         private void registerOverlayChangeBroadcastReceiver() {
267             IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
268             overlayFilter.addDataScheme(OVERLAY_FILTER_DATA_SCHEME);
269             overlayFilter.addDataSchemeSpecificPart(mContext.getPackageName(),
270                     PatternMatcher.PATTERN_LITERAL);
271             BroadcastReceiver receiver = new BroadcastReceiver() {
272                 @Override
273                 public void onReceive(Context context, Intent intent) {
274                     Slog.d(TAG, "topFocusedWindowChanged behavior = ");
275                     mBehavior = mContext.getResources().getInteger(
276                             R.integer.config_systemBarPersistency);
277                     Slog.d(TAG, "Refresh system bar persistency behavior on overlay change"
278                             + mBehavior);
279                 }
280             };
281             mContext.registerReceiverAsUser(receiver, UserHandle.ALL,
282                     overlayFilter, /* broadcastPermission= */null, /* handler= */ null);
283         }
284 
updateDisplayWindowRequestedVisibleTypes()285         protected void updateDisplayWindowRequestedVisibleTypes() {
286             if (mPackageName == null) {
287                 return;
288             }
289 
290             int[] barVisibilities;
291             if (mImmersiveState == STATE_IMMERSIVE_WITH_NAV_BAR) {
292                 barVisibilities = mImmersiveWithNavBarVisibilities;
293             } else if (mImmersiveState == STATE_IMMERSIVE_WITH_STATUS_BAR) {
294                 barVisibilities = mImmersiveWithStatusBarVisibilities;
295             } else if (mImmersiveState == STATE_IMMERSIVE) {
296                 barVisibilities = mImmersiveVisibilities;
297             } else if (mImmersiveState == STATE_NON_IMMERSIVE) {
298                 barVisibilities = mDefaultVisibilities;
299             } else {
300                 barVisibilities = mDefaultVisibilities;
301             }
302             if (DEBUG) {
303                 Slog.d(TAG, "mImmersiveState = " + mImmersiveState + "barVisibilities to "
304                         + Arrays.toString(barVisibilities));
305             }
306 
307             updateRequestedVisibleTypes(barVisibilities[0], /* visible= */ true);
308             updateRequestedVisibleTypes(barVisibilities[1], /* visible= */ false);
309 
310             if (mAppRequestedVisibleTypes == mRequestedVisibleTypes) {
311                 return;
312             }
313             mAppRequestedVisibleTypes = mRequestedVisibleTypes;
314 
315             showInsets(barVisibilities[0], /* fromIme= */ false, /* statsToken= */ null);
316             hideInsets(barVisibilities[1], /* fromIme= */ false, /* statsToken = */ null);
317             try {
318                 mWmService.updateDisplayWindowRequestedVisibleTypes(mDisplayId,
319                         mRequestedVisibleTypes);
320             } catch (RemoteException e) {
321                 Slog.w(TAG, "Unable to update window manager service.");
322             }
323         }
324 
updateRequestedVisibleTypes(@nsetsType int types, boolean visible)325         protected void updateRequestedVisibleTypes(@InsetsType int types, boolean visible) {
326             mRequestedVisibleTypes = visible
327                     ? (mRequestedVisibleTypes | types)
328                     : (mRequestedVisibleTypes & ~types);
329         }
330     }
331 }
332