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