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.os.Trace.TRACE_TAG_WINDOW_MANAGER;
20 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
21 import static android.view.InsetsSource.ID_IME;
22 import static android.view.WindowInsets.Type.displayCutout;
23 import static android.view.WindowInsets.Type.ime;
24 import static android.view.WindowInsets.Type.mandatorySystemGestures;
25 import static android.view.WindowInsets.Type.systemGestures;
26 
27 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
28 import static com.android.server.wm.DisplayContentProto.IME_INSETS_SOURCE_PROVIDER;
29 import static com.android.server.wm.DisplayContentProto.INSETS_SOURCE_PROVIDERS;
30 
31 import android.annotation.NonNull;
32 import android.annotation.Nullable;
33 import android.os.Trace;
34 import android.util.ArrayMap;
35 import android.util.ArraySet;
36 import android.util.SparseArray;
37 import android.util.proto.ProtoOutputStream;
38 import android.view.InsetsSource;
39 import android.view.InsetsSourceControl;
40 import android.view.InsetsState;
41 import android.view.WindowInsets;
42 import android.view.WindowInsets.Type.InsetsType;
43 
44 import com.android.internal.protolog.common.ProtoLog;
45 import com.android.server.inputmethod.InputMethodManagerInternal;
46 
47 import java.io.PrintWriter;
48 import java.util.ArrayList;
49 import java.util.function.Consumer;
50 
51 /**
52  * Manages global window inset state in the system represented by {@link InsetsState}.
53  */
54 class InsetsStateController {
55 
56     private final InsetsState mLastState = new InsetsState();
57     private final InsetsState mState = new InsetsState();
58     private final DisplayContent mDisplayContent;
59 
60     private final SparseArray<InsetsSourceProvider> mProviders = new SparseArray<>();
61     private final ArrayMap<InsetsControlTarget, ArrayList<InsetsSourceProvider>>
62             mControlTargetProvidersMap = new ArrayMap<>();
63     private final SparseArray<InsetsControlTarget> mIdControlTargetMap = new SparseArray<>();
64     private final SparseArray<InsetsControlTarget> mIdFakeControlTargetMap = new SparseArray<>();
65 
66     private final ArraySet<InsetsControlTarget> mPendingControlChanged = new ArraySet<>();
67 
68     private final Consumer<WindowState> mDispatchInsetsChanged = w -> {
69         if (w.isReadyToDispatchInsetsState()) {
70             w.notifyInsetsChanged();
71         }
72     };
73     private final InsetsControlTarget mEmptyImeControlTarget = new InsetsControlTarget() {
74         @Override
75         public void notifyInsetsControlChanged() {
76             InsetsSourceControl[] controls = getControlsForDispatch(this);
77             if (controls == null) {
78                 return;
79             }
80             for (InsetsSourceControl control : controls) {
81                 if (control.getType() == WindowInsets.Type.ime()) {
82                     mDisplayContent.mWmService.mH.post(() ->
83                             InputMethodManagerInternal.get().removeImeSurface());
84                 }
85             }
86         }
87     };
88 
89     private @InsetsType int mForcedConsumingTypes;
90 
InsetsStateController(DisplayContent displayContent)91     InsetsStateController(DisplayContent displayContent) {
92         mDisplayContent = displayContent;
93     }
94 
getRawInsetsState()95     InsetsState getRawInsetsState() {
96         return mState;
97     }
98 
getControlsForDispatch(InsetsControlTarget target)99     @Nullable InsetsSourceControl[] getControlsForDispatch(InsetsControlTarget target) {
100         final ArrayList<InsetsSourceProvider> controlled = mControlTargetProvidersMap.get(target);
101         if (controlled == null) {
102             return null;
103         }
104         final int size = controlled.size();
105         final InsetsSourceControl[] result = new InsetsSourceControl[size];
106         for (int i = 0; i < size; i++) {
107             result[i] = controlled.get(i).getControl(target);
108         }
109         return result;
110     }
111 
getSourceProviders()112     SparseArray<InsetsSourceProvider> getSourceProviders() {
113         return mProviders;
114     }
115 
116     /**
117      * @return The provider of a specific source ID.
118      */
getOrCreateSourceProvider(int id, @InsetsType int type)119     InsetsSourceProvider getOrCreateSourceProvider(int id, @InsetsType int type) {
120         InsetsSourceProvider provider = mProviders.get(id);
121         if (provider != null) {
122             return provider;
123         }
124         final InsetsSource source = mState.getOrCreateSource(id, type);
125         provider = id == ID_IME
126                 ? new ImeInsetsSourceProvider(source, this, mDisplayContent)
127                 : new InsetsSourceProvider(source, this, mDisplayContent);
128         provider.setFlags(
129                 (mForcedConsumingTypes & type) != 0
130                         ? FLAG_FORCE_CONSUMING
131                         : 0,
132                 FLAG_FORCE_CONSUMING);
133         mProviders.put(id, provider);
134         return provider;
135     }
136 
getImeSourceProvider()137     ImeInsetsSourceProvider getImeSourceProvider() {
138         return (ImeInsetsSourceProvider) getOrCreateSourceProvider(ID_IME, ime());
139     }
140 
removeSourceProvider(int id)141     void removeSourceProvider(int id) {
142         if (id != ID_IME) {
143             mState.removeSource(id);
144             mProviders.remove(id);
145         }
146     }
147 
setForcedConsumingTypes(@nsetsType int types)148     void setForcedConsumingTypes(@InsetsType int types) {
149         if (mForcedConsumingTypes != types) {
150             mForcedConsumingTypes = types;
151             boolean changed = false;
152             for (int i = mProviders.size() - 1; i >= 0; i--) {
153                 final InsetsSourceProvider provider = mProviders.valueAt(i);
154                 changed |= provider.setFlags(
155                         (types & provider.getSource().getType()) != 0
156                                 ? FLAG_FORCE_CONSUMING
157                                 : 0,
158                         FLAG_FORCE_CONSUMING);
159             }
160             if (changed) {
161                 notifyInsetsChanged();
162             }
163         }
164     }
165 
166     /**
167      * Called when a layout pass has occurred.
168      */
onPostLayout()169     void onPostLayout() {
170         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ISC.onPostLayout");
171         for (int i = mProviders.size() - 1; i >= 0; i--) {
172             mProviders.valueAt(i).onPostLayout();
173         }
174         if (!mLastState.equals(mState)) {
175             mLastState.set(mState, true /* copySources */);
176             notifyInsetsChanged();
177         }
178         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
179     }
180 
181     /**
182      * Updates {@link WindowState#mAboveInsetsState} for all windows in the display.
183      *
184      * @param notifyInsetsChange {@code true} if the clients should be notified about the change.
185      */
updateAboveInsetsState(boolean notifyInsetsChange)186     void updateAboveInsetsState(boolean notifyInsetsChange) {
187         final InsetsState aboveInsetsState = new InsetsState();
188         aboveInsetsState.set(mState,
189                 displayCutout() | systemGestures() | mandatorySystemGestures());
190         final SparseArray<InsetsSource> localInsetsSourcesFromParent = new SparseArray<>();
191         final ArraySet<WindowState> insetsChangedWindows = new ArraySet<>();
192 
193         // This method will iterate on the entire hierarchy in top to bottom z-order manner. The
194         // aboveInsetsState will be modified as per the insets provided by the WindowState being
195         // visited.
196         mDisplayContent.updateAboveInsetsState(aboveInsetsState, localInsetsSourcesFromParent,
197                 insetsChangedWindows);
198 
199         if (notifyInsetsChange) {
200             for (int i = insetsChangedWindows.size() - 1; i >= 0; i--) {
201                 mDispatchInsetsChanged.accept(insetsChangedWindows.valueAt(i));
202             }
203         }
204     }
205 
onDisplayFramesUpdated(boolean notifyInsetsChange)206     void onDisplayFramesUpdated(boolean notifyInsetsChange) {
207         final ArrayList<WindowState> insetsChangedWindows = new ArrayList<>();
208         mDisplayContent.forAllWindows(w -> {
209             w.mAboveInsetsState.set(mState, displayCutout());
210             insetsChangedWindows.add(w);
211         }, true /* traverseTopToBottom */);
212         if (notifyInsetsChange) {
213             for (int i = insetsChangedWindows.size() - 1; i >= 0; i--) {
214                 mDispatchInsetsChanged.accept(insetsChangedWindows.get(i));
215             }
216         }
217     }
218 
onRequestedVisibleTypesChanged(InsetsControlTarget caller)219     void onRequestedVisibleTypesChanged(InsetsControlTarget caller) {
220         boolean changed = false;
221         for (int i = mProviders.size() - 1; i >= 0; i--) {
222             changed |= mProviders.valueAt(i).updateClientVisibility(caller);
223         }
224         if (changed) {
225             notifyInsetsChanged();
226             mDisplayContent.updateSystemGestureExclusion();
227             mDisplayContent.updateKeepClearAreas();
228             mDisplayContent.getDisplayPolicy().updateSystemBarAttributes();
229         }
230     }
231 
getFakeControllingTypes(InsetsControlTarget target)232     @InsetsType int getFakeControllingTypes(InsetsControlTarget target) {
233         @InsetsType int types = 0;
234         for (int i = mProviders.size() - 1; i >= 0; i--) {
235             final InsetsSourceProvider provider = mProviders.valueAt(i);
236             final InsetsControlTarget fakeControlTarget = provider.getFakeControlTarget();
237             if (target == fakeControlTarget) {
238                 types |= provider.getSource().getType();
239             }
240         }
241         return types;
242     }
243 
onImeControlTargetChanged(@ullable InsetsControlTarget imeTarget)244     void onImeControlTargetChanged(@Nullable InsetsControlTarget imeTarget) {
245 
246         // Make sure that we always have a control target for the IME, even if the IME target is
247         // null. Otherwise there is no leash that will hide it and IME becomes "randomly" visible.
248         InsetsControlTarget target = imeTarget != null ? imeTarget : mEmptyImeControlTarget;
249         onControlTargetChanged(getImeSourceProvider(), target, false /* fake */);
250         ProtoLog.d(WM_DEBUG_IME, "onImeControlTargetChanged %s",
251                 target != null ? target.getWindow() : "null");
252         notifyPendingInsetsControlChanged();
253     }
254 
255     /**
256      * Called when the focused window that is able to control the system bars changes.
257      *
258      * @param statusControlling The target that is now able to control the status bar appearance
259      *                          and visibility.
260      * @param navControlling The target that is now able to control the nav bar appearance
261      *                       and visibility.
262      */
onBarControlTargetChanged(@ullable InsetsControlTarget statusControlling, @Nullable InsetsControlTarget fakeStatusControlling, @Nullable InsetsControlTarget navControlling, @Nullable InsetsControlTarget fakeNavControlling)263     void onBarControlTargetChanged(@Nullable InsetsControlTarget statusControlling,
264             @Nullable InsetsControlTarget fakeStatusControlling,
265             @Nullable InsetsControlTarget navControlling,
266             @Nullable InsetsControlTarget fakeNavControlling) {
267         for (int i = mProviders.size() - 1; i >= 0; i--) {
268             final InsetsSourceProvider provider = mProviders.valueAt(i);
269             final @InsetsType int type = provider.getSource().getType();
270             if (type == WindowInsets.Type.statusBars()) {
271                 onControlTargetChanged(provider, statusControlling, false /* fake */);
272                 onControlTargetChanged(provider, fakeStatusControlling, true /* fake */);
273             } else if (type == WindowInsets.Type.navigationBars()) {
274                 onControlTargetChanged(provider, navControlling, false /* fake */);
275                 onControlTargetChanged(provider, fakeNavControlling, true /* fake */);
276             }
277         }
278         notifyPendingInsetsControlChanged();
279     }
280 
notifyControlRevoked(@onNull InsetsControlTarget previousControlTarget, InsetsSourceProvider provider)281     void notifyControlRevoked(@NonNull InsetsControlTarget previousControlTarget,
282             InsetsSourceProvider provider) {
283         removeFromControlMaps(previousControlTarget, provider, false /* fake */);
284     }
285 
onControlTargetChanged(InsetsSourceProvider provider, @Nullable InsetsControlTarget target, boolean fake)286     private void onControlTargetChanged(InsetsSourceProvider provider,
287             @Nullable InsetsControlTarget target, boolean fake) {
288         final InsetsControlTarget lastTarget = fake
289                 ? mIdFakeControlTargetMap.get(provider.getSource().getId())
290                 : mIdControlTargetMap.get(provider.getSource().getId());
291         if (target == lastTarget) {
292             return;
293         }
294         if (!provider.isControllable()) {
295             return;
296         }
297         if (fake) {
298             // The fake target updated here will be used to pretend to the app that it's still under
299             // control of the bars while it's not really, but we still need to find out the apps
300             // intentions around showing/hiding. For example, when the transient bars are showing,
301             // and the fake target requests to show system bars, the transient state will be
302             // aborted.
303             provider.updateFakeControlTarget(target);
304         } else {
305             provider.updateControlForTarget(target, false /* force */);
306 
307             // Get control target again in case the provider didn't accept the one we passed to it.
308             target = provider.getControlTarget();
309             if (target == lastTarget) {
310                 return;
311             }
312         }
313         if (lastTarget != null) {
314             removeFromControlMaps(lastTarget, provider, fake);
315             mPendingControlChanged.add(lastTarget);
316         }
317         if (target != null) {
318             addToControlMaps(target, provider, fake);
319             mPendingControlChanged.add(target);
320         }
321     }
322 
removeFromControlMaps(@onNull InsetsControlTarget target, InsetsSourceProvider provider, boolean fake)323     private void removeFromControlMaps(@NonNull InsetsControlTarget target,
324             InsetsSourceProvider provider, boolean fake) {
325         final ArrayList<InsetsSourceProvider> array = mControlTargetProvidersMap.get(target);
326         if (array == null) {
327             return;
328         }
329         array.remove(provider);
330         if (array.isEmpty()) {
331             mControlTargetProvidersMap.remove(target);
332         }
333         if (fake) {
334             mIdFakeControlTargetMap.remove(provider.getSource().getId());
335         } else {
336             mIdControlTargetMap.remove(provider.getSource().getId());
337         }
338     }
339 
addToControlMaps(@onNull InsetsControlTarget target, InsetsSourceProvider provider, boolean fake)340     private void addToControlMaps(@NonNull InsetsControlTarget target,
341             InsetsSourceProvider provider, boolean fake) {
342         final ArrayList<InsetsSourceProvider> array = mControlTargetProvidersMap.computeIfAbsent(
343                 target, key -> new ArrayList<>());
344         array.add(provider);
345         if (fake) {
346             mIdFakeControlTargetMap.put(provider.getSource().getId(), target);
347         } else {
348             mIdControlTargetMap.put(provider.getSource().getId(), target);
349         }
350     }
351 
notifyControlChanged(InsetsControlTarget target)352     void notifyControlChanged(InsetsControlTarget target) {
353         mPendingControlChanged.add(target);
354         notifyPendingInsetsControlChanged();
355     }
356 
notifyPendingInsetsControlChanged()357     private void notifyPendingInsetsControlChanged() {
358         if (mPendingControlChanged.isEmpty()) {
359             return;
360         }
361         mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
362             for (int i = mProviders.size() - 1; i >= 0; i--) {
363                 final InsetsSourceProvider provider = mProviders.valueAt(i);
364                 provider.onSurfaceTransactionApplied();
365             }
366             final ArraySet<InsetsControlTarget> newControlTargets = new ArraySet<>();
367             for (int i = mPendingControlChanged.size() - 1; i >= 0; i--) {
368                 final InsetsControlTarget controlTarget = mPendingControlChanged.valueAt(i);
369                 controlTarget.notifyInsetsControlChanged();
370                 if (mControlTargetProvidersMap.containsKey(controlTarget)) {
371                     // We only collect targets who get controls, not lose controls.
372                     newControlTargets.add(controlTarget);
373                 }
374             }
375             mPendingControlChanged.clear();
376 
377             // This updates the insets visibilities AFTER sending current insets state and controls
378             // to the clients, so that the clients can change the current visibilities to the
379             // requested visibilities with animations.
380             for (int i = newControlTargets.size() - 1; i >= 0; i--) {
381                 onRequestedVisibleTypesChanged(newControlTargets.valueAt(i));
382             }
383             newControlTargets.clear();
384         });
385     }
386 
notifyInsetsChanged()387     void notifyInsetsChanged() {
388         mDisplayContent.notifyInsetsChanged(mDispatchInsetsChanged);
389     }
390 
dump(String prefix, PrintWriter pw)391     void dump(String prefix, PrintWriter pw) {
392         pw.println(prefix + "WindowInsetsStateController");
393         prefix = prefix + "  ";
394         mState.dump(prefix, pw);
395         pw.println(prefix + "Control map:");
396         for (int i = mControlTargetProvidersMap.size() - 1; i >= 0; i--) {
397             final InsetsControlTarget controlTarget = mControlTargetProvidersMap.keyAt(i);
398             pw.print(prefix + "  ");
399             pw.print(controlTarget);
400             pw.println(":");
401             final ArrayList<InsetsSourceProvider> providers = mControlTargetProvidersMap.valueAt(i);
402             for (int j = providers.size() - 1; j >= 0; j--) {
403                 final InsetsSourceProvider provider = providers.get(j);
404                 if (provider != null) {
405                     pw.print(prefix + "    ");
406                     if (controlTarget == provider.getFakeControlTarget()) {
407                         pw.print("(fake) ");
408                     }
409                     pw.println(provider.getControl(controlTarget));
410                 }
411             }
412         }
413         if (mControlTargetProvidersMap.isEmpty()) {
414             pw.print(prefix + "  none");
415         }
416         pw.println(prefix + "InsetsSourceProviders:");
417         for (int i = mProviders.size() - 1; i >= 0; i--) {
418             mProviders.valueAt(i).dump(pw, prefix + "  ");
419         }
420         if (mForcedConsumingTypes != 0) {
421             pw.println(prefix + "mForcedConsumingTypes="
422                     + WindowInsets.Type.toString(mForcedConsumingTypes));
423         }
424     }
425 
dumpDebug(ProtoOutputStream proto, @WindowTraceLogLevel int logLevel)426     void dumpDebug(ProtoOutputStream proto, @WindowTraceLogLevel int logLevel) {
427         for (int i = mProviders.size() - 1; i >= 0; i--) {
428             final InsetsSourceProvider provider = mProviders.valueAt(i);
429             provider.dumpDebug(proto,
430                     provider.getSource().getType() == ime()
431                             ? IME_INSETS_SOURCE_PROVIDER
432                             : INSETS_SOURCE_PROVIDERS,
433                     logLevel);
434         }
435     }
436 }
437