1 /*
2  * Copyright (C) 2018 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.server.wm;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
21 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
22 import static android.view.InsetsState.ITYPE_IME;
23 import static android.view.InsetsState.ITYPE_INVALID;
24 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
25 import static android.view.InsetsState.ITYPE_STATUS_BAR;
26 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
27 import static android.view.ViewRootImpl.sNewInsetsMode;
28 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
29 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
30 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
31 
32 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
33 
34 import android.annotation.NonNull;
35 import android.annotation.Nullable;
36 import android.app.WindowConfiguration;
37 import android.app.WindowConfiguration.WindowingMode;
38 import android.util.ArrayMap;
39 import android.util.ArraySet;
40 import android.util.SparseArray;
41 import android.view.InsetsSource;
42 import android.view.InsetsSourceControl;
43 import android.view.InsetsState;
44 import android.view.InsetsState.InternalInsetsType;
45 import android.view.WindowManager;
46 
47 import com.android.server.inputmethod.InputMethodManagerInternal;
48 import com.android.server.protolog.common.ProtoLog;
49 
50 import java.io.PrintWriter;
51 import java.util.ArrayList;
52 import java.util.function.Consumer;
53 
54 /**
55  * Manages global window inset state in the system represented by {@link InsetsState}.
56  */
57 class InsetsStateController {
58 
59     private final InsetsState mLastState = new InsetsState();
60     private final InsetsState mState = new InsetsState();
61     private final DisplayContent mDisplayContent;
62 
63     private final ArrayMap<Integer, InsetsSourceProvider> mProviders = new ArrayMap<>();
64     private final ArrayMap<InsetsControlTarget, ArrayList<Integer>> mControlTargetTypeMap =
65             new ArrayMap<>();
66     private final SparseArray<InsetsControlTarget> mTypeControlTargetMap = new SparseArray<>();
67 
68     /** @see #onControlFakeTargetChanged */
69     private final SparseArray<InsetsControlTarget> mTypeFakeControlTargetMap = new SparseArray<>();
70 
71     private final ArraySet<InsetsControlTarget> mPendingControlChanged = new ArraySet<>();
72 
73     private final Consumer<WindowState> mDispatchInsetsChanged = w -> {
74         if (w.isVisible()) {
75             w.notifyInsetsChanged();
76         }
77     };
78     private final InsetsControlTarget mEmptyImeControlTarget = new InsetsControlTarget() {
79         @Override
80         public void notifyInsetsControlChanged() {
81             InsetsSourceControl[] controls = getControlsForDispatch(this);
82             if (controls == null) {
83                 return;
84             }
85             for (InsetsSourceControl control : controls) {
86                 if (control.getType() == ITYPE_IME) {
87                     mDisplayContent.mWmService.mH.post(() ->
88                             InputMethodManagerInternal.get().removeImeSurface());
89                 }
90             }
91         }
92     };
93 
InsetsStateController(DisplayContent displayContent)94     InsetsStateController(DisplayContent displayContent) {
95         mDisplayContent = displayContent;
96     }
97 
98     /**
99      * When dispatching window state to the client, we'll need to exclude the source that represents
100      * the window that is being dispatched. We also need to exclude certain types of insets source
101      * for client within specific windowing modes.
102      *
103      * @param target The client we dispatch the state to.
104      * @return The state stripped of the necessary information.
105      */
getInsetsForDispatch(@onNull WindowState target)106     InsetsState getInsetsForDispatch(@NonNull WindowState target) {
107         final InsetsSourceProvider provider = target.getControllableInsetProvider();
108         final @InternalInsetsType int type = provider != null
109                 ? provider.getSource().getType() : ITYPE_INVALID;
110         return getInsetsForDispatchInner(type, target.getWindowingMode(), target.isAlwaysOnTop(),
111                 isAboveIme(target));
112     }
113 
getInsetsForWindowMetrics(@onNull WindowManager.LayoutParams attrs)114     InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
115         final @InternalInsetsType int type = getInsetsTypeForWindowType(attrs.type);
116         final WindowToken token = mDisplayContent.getWindowToken(attrs.token);
117         final @WindowingMode int windowingMode = token != null
118                 ? token.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
119         final boolean alwaysOnTop = token != null && token.isAlwaysOnTop();
120         return getInsetsForDispatchInner(type, windowingMode, alwaysOnTop, isAboveIme(token));
121     }
122 
isAboveIme(WindowContainer target)123     private boolean isAboveIme(WindowContainer target) {
124         final WindowState imeWindow = mDisplayContent.mInputMethodWindow;
125         if (target == null || imeWindow == null) {
126             return false;
127         }
128         if (target instanceof WindowState) {
129             final WindowState win = (WindowState) target;
130             return win.needsRelativeLayeringToIme() || !win.mBehindIme;
131         }
132         return false;
133     }
134 
getInsetsTypeForWindowType(int type)135     private static @InternalInsetsType int getInsetsTypeForWindowType(int type) {
136         switch (type) {
137             case TYPE_STATUS_BAR:
138                 return ITYPE_STATUS_BAR;
139             case TYPE_NAVIGATION_BAR:
140                 return ITYPE_NAVIGATION_BAR;
141             case TYPE_INPUT_METHOD:
142                 return ITYPE_IME;
143             default:
144                 return ITYPE_INVALID;
145         }
146     }
147 
148     /** @see #getInsetsForDispatch */
getInsetsForDispatchInner(@nternalInsetsType int type, @WindowingMode int windowingMode, boolean isAlwaysOnTop, boolean aboveIme)149     private InsetsState getInsetsForDispatchInner(@InternalInsetsType int type,
150             @WindowingMode int windowingMode, boolean isAlwaysOnTop, boolean aboveIme) {
151         InsetsState state = mState;
152 
153         if (type != ITYPE_INVALID) {
154             state = new InsetsState(state);
155             state.removeSource(type);
156 
157             // Navigation bar doesn't get influenced by anything else
158             if (type == ITYPE_NAVIGATION_BAR) {
159                 state.removeSource(ITYPE_IME);
160                 state.removeSource(ITYPE_STATUS_BAR);
161                 state.removeSource(ITYPE_CAPTION_BAR);
162             }
163 
164             // Status bar doesn't get influenced by caption bar
165             if (type == ITYPE_STATUS_BAR) {
166                 state.removeSource(ITYPE_CAPTION_BAR);
167             }
168 
169             // IME needs different frames for certain cases (e.g. navigation bar in gesture nav).
170             if (type == ITYPE_IME) {
171                 for (int i = mProviders.size() - 1; i >= 0; i--) {
172                     InsetsSourceProvider otherProvider = mProviders.valueAt(i);
173                     if (otherProvider.overridesImeFrame()) {
174                         InsetsSource override =
175                                 new InsetsSource(
176                                         state.getSource(otherProvider.getSource().getType()));
177                         override.setFrame(otherProvider.getImeOverrideFrame());
178                         state.addSource(override);
179                     }
180                 }
181             }
182         }
183 
184         if (WindowConfiguration.isFloating(windowingMode)
185                 || (windowingMode == WINDOWING_MODE_MULTI_WINDOW && isAlwaysOnTop)) {
186             state = new InsetsState(state);
187             state.removeSource(ITYPE_STATUS_BAR);
188             state.removeSource(ITYPE_NAVIGATION_BAR);
189         }
190 
191         if (aboveIme) {
192             InsetsSource imeSource = state.peekSource(ITYPE_IME);
193             if (imeSource != null && imeSource.isVisible()) {
194                 imeSource = new InsetsSource(imeSource);
195                 imeSource.setVisible(false);
196                 imeSource.setFrame(0, 0, 0, 0);
197                 state = new InsetsState(state);
198                 state.addSource(imeSource);
199             }
200         }
201 
202         return state;
203     }
204 
getRawInsetsState()205     InsetsState getRawInsetsState() {
206         return mState;
207     }
208 
getControlsForDispatch(InsetsControlTarget target)209     @Nullable InsetsSourceControl[] getControlsForDispatch(InsetsControlTarget target) {
210         ArrayList<Integer> controlled = mControlTargetTypeMap.get(target);
211         if (controlled == null) {
212             return null;
213         }
214         final int size = controlled.size();
215         final InsetsSourceControl[] result = new InsetsSourceControl[size];
216         for (int i = 0; i < size; i++) {
217             result[i] = mProviders.get(controlled.get(i)).getControl(target);
218         }
219         return result;
220     }
221 
222     /**
223      * @return The provider of a specific type.
224      */
getSourceProvider(@nternalInsetsType int type)225     InsetsSourceProvider getSourceProvider(@InternalInsetsType int type) {
226         if (type == ITYPE_IME) {
227             return mProviders.computeIfAbsent(type,
228                     key -> new ImeInsetsSourceProvider(
229                             mState.getSource(key), this, mDisplayContent));
230         } else {
231             return mProviders.computeIfAbsent(type,
232                     key -> new InsetsSourceProvider(mState.getSource(key), this, mDisplayContent));
233         }
234     }
235 
getImeSourceProvider()236     ImeInsetsSourceProvider getImeSourceProvider() {
237         return (ImeInsetsSourceProvider) getSourceProvider(ITYPE_IME);
238     }
239 
240     /**
241      * @return The provider of a specific type or null if we don't have it.
242      */
peekSourceProvider(@nternalInsetsType int type)243     @Nullable InsetsSourceProvider peekSourceProvider(@InternalInsetsType int type) {
244         return mProviders.get(type);
245     }
246 
247     /**
248      * Called when a layout pass has occurred.
249      */
onPostLayout()250     void onPostLayout() {
251         mState.setDisplayFrame(mDisplayContent.getBounds());
252         for (int i = mProviders.size() - 1; i >= 0; i--) {
253             mProviders.valueAt(i).onPostLayout();
254         }
255         final ArrayList<WindowState> winInsetsChanged = mDisplayContent.mWinInsetsChanged;
256         if (!mLastState.equals(mState)) {
257             mLastState.set(mState, true /* copySources */);
258             notifyInsetsChanged();
259         } else {
260             // The global insets state has not changed but there might be windows whose conditions
261             // (e.g., z-order) have changed. They can affect the insets states that we dispatch to
262             // the clients.
263             for (int i = winInsetsChanged.size() - 1; i >= 0; i--) {
264                 mDispatchInsetsChanged.accept(winInsetsChanged.get(i));
265             }
266         }
267         winInsetsChanged.clear();
268     }
269 
onInsetsModified(InsetsControlTarget windowState, InsetsState state)270     void onInsetsModified(InsetsControlTarget windowState, InsetsState state) {
271         boolean changed = false;
272         for (int i = 0; i < InsetsState.SIZE; i++) {
273             final InsetsSource source = state.peekSource(i);
274             if (source == null) continue;
275             final InsetsSourceProvider provider = mProviders.get(source.getType());
276             if (provider == null) {
277                 continue;
278             }
279             changed |= provider.onInsetsModified(windowState, source);
280         }
281         if (changed) {
282             notifyInsetsChanged();
283             mDisplayContent.getDisplayPolicy().updateSystemUiVisibilityLw();
284         }
285     }
286 
287     /**
288      * Computes insets state of the insets provider window in the display frames.
289      *
290      * @param state The output state.
291      * @param win The owner window of insets provider.
292      * @param displayFrames The display frames to create insets source.
293      * @param windowFrames The specified frames to represent the owner window.
294      */
computeSimulatedState(InsetsState state, WindowState win, DisplayFrames displayFrames, WindowFrames windowFrames)295     void computeSimulatedState(InsetsState state, WindowState win, DisplayFrames displayFrames,
296             WindowFrames windowFrames) {
297         for (int i = mProviders.size() - 1; i >= 0; i--) {
298             final InsetsSourceProvider provider = mProviders.valueAt(i);
299             if (provider.mWin == win) {
300                 state.addSource(provider.createSimulatedSource(displayFrames, windowFrames));
301             }
302         }
303     }
304 
isFakeTarget(@nternalInsetsType int type, InsetsControlTarget target)305     boolean isFakeTarget(@InternalInsetsType int type, InsetsControlTarget target) {
306         return mTypeFakeControlTargetMap.get(type) == target;
307     }
308 
onImeControlTargetChanged(@ullable InsetsControlTarget imeTarget)309     void onImeControlTargetChanged(@Nullable InsetsControlTarget imeTarget) {
310 
311         // Make sure that we always have a control target for the IME, even if the IME target is
312         // null. Otherwise there is no leash that will hide it and IME becomes "randomly" visible.
313         InsetsControlTarget target = imeTarget != null ? imeTarget : mEmptyImeControlTarget;
314         onControlChanged(ITYPE_IME, target);
315         ProtoLog.d(WM_DEBUG_IME, "onImeControlTargetChanged %s",
316                 target != null ? target.getWindow() : "null");
317         notifyPendingInsetsControlChanged();
318     }
319 
320     /**
321      * Called when the focused window that is able to control the system bars changes.
322      *
323      * @param statusControlling The target that is now able to control the status bar appearance
324      *                          and visibility.
325      * @param navControlling The target that is now able to control the nav bar appearance
326      *                       and visibility.
327      */
onBarControlTargetChanged(@ullable InsetsControlTarget statusControlling, @Nullable InsetsControlTarget fakeStatusControlling, @Nullable InsetsControlTarget navControlling, @Nullable InsetsControlTarget fakeNavControlling)328     void onBarControlTargetChanged(@Nullable InsetsControlTarget statusControlling,
329             @Nullable InsetsControlTarget fakeStatusControlling,
330             @Nullable InsetsControlTarget navControlling,
331             @Nullable InsetsControlTarget fakeNavControlling) {
332         onControlChanged(ITYPE_STATUS_BAR, statusControlling);
333         onControlChanged(ITYPE_NAVIGATION_BAR, navControlling);
334         onControlFakeTargetChanged(ITYPE_STATUS_BAR, fakeStatusControlling);
335         onControlFakeTargetChanged(ITYPE_NAVIGATION_BAR, fakeNavControlling);
336         notifyPendingInsetsControlChanged();
337     }
338 
notifyControlRevoked(@onNull InsetsControlTarget previousControlTarget, InsetsSourceProvider provider)339     void notifyControlRevoked(@NonNull InsetsControlTarget previousControlTarget,
340             InsetsSourceProvider provider) {
341         removeFromControlMaps(previousControlTarget, provider.getSource().getType(),
342                 false /* fake */);
343     }
344 
onControlChanged(@nternalInsetsType int type, @Nullable InsetsControlTarget target)345     private void onControlChanged(@InternalInsetsType int type,
346             @Nullable InsetsControlTarget target) {
347         final InsetsControlTarget previous = mTypeControlTargetMap.get(type);
348         if (target == previous) {
349             return;
350         }
351         final InsetsSourceProvider provider = mProviders.get(type);
352         if (provider == null) {
353             return;
354         }
355         if (!provider.isControllable()) {
356             return;
357         }
358         provider.updateControlForTarget(target, false /* force */);
359         target = provider.getControlTarget();
360         if (previous != null) {
361             removeFromControlMaps(previous, type, false /* fake */);
362             mPendingControlChanged.add(previous);
363         }
364         if (target != null) {
365             addToControlMaps(target, type, false /* fake */);
366             mPendingControlChanged.add(target);
367         }
368     }
369 
370     /**
371      * The fake target saved here will be used to pretend to the app that it's still under control
372      * of the bars while it's not really, but we still need to find out the apps intentions around
373      * showing/hiding. For example, when the transient bars are showing, and the fake target
374      * requests to show system bars, the transient state will be aborted.
375      */
onControlFakeTargetChanged(@nternalInsetsType int type, @Nullable InsetsControlTarget fakeTarget)376     void onControlFakeTargetChanged(@InternalInsetsType int type,
377             @Nullable InsetsControlTarget fakeTarget) {
378         if (sNewInsetsMode != NEW_INSETS_MODE_FULL) {
379             return;
380         }
381         final InsetsControlTarget previous = mTypeFakeControlTargetMap.get(type);
382         if (fakeTarget == previous) {
383             return;
384         }
385         final InsetsSourceProvider provider = mProviders.get(type);
386         if (provider == null) {
387             return;
388         }
389         provider.updateControlForFakeTarget(fakeTarget);
390         if (previous != null) {
391             removeFromControlMaps(previous, type, true /* fake */);
392             mPendingControlChanged.add(previous);
393         }
394         if (fakeTarget != null) {
395             addToControlMaps(fakeTarget, type, true /* fake */);
396             mPendingControlChanged.add(fakeTarget);
397         }
398     }
399 
removeFromControlMaps(@onNull InsetsControlTarget target, @InternalInsetsType int type, boolean fake)400     private void removeFromControlMaps(@NonNull InsetsControlTarget target,
401             @InternalInsetsType int type, boolean fake) {
402         final ArrayList<Integer> array = mControlTargetTypeMap.get(target);
403         if (array == null) {
404             return;
405         }
406         array.remove((Integer) type);
407         if (array.isEmpty()) {
408             mControlTargetTypeMap.remove(target);
409         }
410         if (fake) {
411             mTypeFakeControlTargetMap.remove(type);
412         } else {
413             mTypeControlTargetMap.remove(type);
414         }
415     }
416 
addToControlMaps(@onNull InsetsControlTarget target, @InternalInsetsType int type, boolean fake)417     private void addToControlMaps(@NonNull InsetsControlTarget target,
418             @InternalInsetsType int type, boolean fake) {
419         final ArrayList<Integer> array = mControlTargetTypeMap.computeIfAbsent(target,
420                 key -> new ArrayList<>());
421         array.add(type);
422         if (fake) {
423             mTypeFakeControlTargetMap.put(type, target);
424         } else {
425             mTypeControlTargetMap.put(type, target);
426         }
427     }
428 
notifyControlChanged(InsetsControlTarget target)429     void notifyControlChanged(InsetsControlTarget target) {
430         mPendingControlChanged.add(target);
431         notifyPendingInsetsControlChanged();
432     }
433 
notifyPendingInsetsControlChanged()434     private void notifyPendingInsetsControlChanged() {
435         if (mPendingControlChanged.isEmpty()) {
436             return;
437         }
438         mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
439             for (int i = mProviders.size() - 1; i >= 0; i--) {
440                 final InsetsSourceProvider provider = mProviders.valueAt(i);
441                 provider.onSurfaceTransactionApplied();
442             }
443             for (int i = mPendingControlChanged.size() - 1; i >= 0; i--) {
444                 final InsetsControlTarget controlTarget = mPendingControlChanged.valueAt(i);
445                 controlTarget.notifyInsetsControlChanged();
446             }
447             mPendingControlChanged.clear();
448         });
449     }
450 
notifyInsetsChanged()451     void notifyInsetsChanged() {
452         mDisplayContent.forAllWindows(mDispatchInsetsChanged, true /* traverseTopToBottom */);
453         if (mDisplayContent.mRemoteInsetsControlTarget != null) {
454             mDisplayContent.mRemoteInsetsControlTarget.notifyInsetsChanged();
455         }
456     }
457 
dump(String prefix, PrintWriter pw)458     void dump(String prefix, PrintWriter pw) {
459         pw.println(prefix + "WindowInsetsStateController");
460         mState.dump(prefix + "  ", pw);
461         pw.println(prefix + "  " + "Control map:");
462         for (int i = mTypeControlTargetMap.size() - 1; i >= 0; i--) {
463             pw.print(prefix + "  ");
464             pw.println(InsetsState.typeToString(mTypeControlTargetMap.keyAt(i)) + " -> "
465                     + mTypeControlTargetMap.valueAt(i));
466         }
467         pw.println(prefix + "  " + "InsetsSourceProviders map:");
468         for (int i = mProviders.size() - 1; i >= 0; i--) {
469             pw.print(prefix + "  ");
470             pw.println(InsetsState.typeToString(mProviders.keyAt(i)) + " -> ");
471             mProviders.valueAt(i).dump(pw, prefix);
472         }
473     }
474 }
475