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.view.InsetsState.TYPE_IME;
20 import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
21 import static android.view.InsetsState.TYPE_TOP_BAR;
22 import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
23 import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
24 import static android.view.ViewRootImpl.sNewInsetsMode;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.util.ArrayMap;
29 import android.util.ArraySet;
30 import android.util.SparseArray;
31 import android.view.InsetsSource;
32 import android.view.InsetsSourceControl;
33 import android.view.InsetsState;
34 import android.view.ViewRootImpl;
35 
36 import java.io.PrintWriter;
37 import java.util.ArrayList;
38 import java.util.function.Consumer;
39 
40 /**
41  * Manages global window inset state in the system represented by {@link InsetsState}.
42  */
43 class InsetsStateController {
44 
45     private final InsetsState mLastState = new InsetsState();
46     private final InsetsState mState = new InsetsState();
47     private final DisplayContent mDisplayContent;
48 
49     private final ArrayMap<Integer, InsetsSourceProvider> mControllers = new ArrayMap<>();
50     private final ArrayMap<WindowState, ArrayList<Integer>> mWinControlTypeMap = new ArrayMap<>();
51     private final SparseArray<WindowState> mTypeWinControlMap = new SparseArray<>();
52     private final ArraySet<WindowState> mPendingControlChanged = new ArraySet<>();
53 
54     private final Consumer<WindowState> mDispatchInsetsChanged = w -> {
55         if (w.isVisible()) {
56             w.notifyInsetsChanged();
57         }
58     };
59 
InsetsStateController(DisplayContent displayContent)60     InsetsStateController(DisplayContent displayContent) {
61         mDisplayContent = displayContent;
62     }
63 
64     /**
65      * When dispatching window state to the client, we'll need to exclude the source that represents
66      * the window that is being dispatched.
67      *
68      * @param target The client we dispatch the state to.
69      * @return The state stripped of the necessary information.
70      */
getInsetsForDispatch(WindowState target)71     InsetsState getInsetsForDispatch(WindowState target) {
72         final InsetsSourceProvider provider = target.getInsetProvider();
73         if (provider == null) {
74             return mState;
75         }
76 
77         final InsetsState state = new InsetsState();
78         state.set(mState);
79         final int type = provider.getSource().getType();
80         state.removeSource(type);
81 
82         // Navigation bar doesn't get influenced by anything else
83         if (type == TYPE_NAVIGATION_BAR) {
84             state.removeSource(TYPE_IME);
85             state.removeSource(TYPE_TOP_BAR);
86         }
87         return state;
88     }
89 
getControlsForDispatch(WindowState target)90     @Nullable InsetsSourceControl[] getControlsForDispatch(WindowState target) {
91         ArrayList<Integer> controlled = mWinControlTypeMap.get(target);
92         if (controlled == null) {
93             return null;
94         }
95         final int size = controlled.size();
96         final InsetsSourceControl[] result = new InsetsSourceControl[size];
97         for (int i = 0; i < size; i++) {
98             result[i] = mControllers.get(controlled.get(i)).getControl();
99         }
100         return result;
101     }
102 
103     /**
104      * @return The provider of a specific type.
105      */
getSourceProvider(int type)106     InsetsSourceProvider getSourceProvider(int type) {
107         return mControllers.computeIfAbsent(type,
108                 key -> new InsetsSourceProvider(mState.getSource(key), this, mDisplayContent));
109     }
110 
111     /**
112      * Called when a layout pass has occurred.
113      */
onPostLayout()114     void onPostLayout() {
115         mState.setDisplayFrame(mDisplayContent.getBounds());
116         for (int i = mControllers.size() - 1; i>= 0; i--) {
117             mControllers.valueAt(i).onPostLayout();
118         }
119         if (!mLastState.equals(mState)) {
120             mLastState.set(mState, true /* copySources */);
121             notifyInsetsChanged();
122         }
123     }
124 
onInsetsModified(WindowState windowState, InsetsState state)125     void onInsetsModified(WindowState windowState, InsetsState state) {
126         boolean changed = false;
127         for (int i = state.getSourcesCount() - 1; i >= 0; i--) {
128             final InsetsSource source = state.sourceAt(i);
129             final InsetsSourceProvider provider = mControllers.get(source.getType());
130             if (provider == null) {
131                 continue;
132             }
133             changed |= provider.onInsetsModified(windowState, source);
134         }
135         if (changed) {
136             notifyInsetsChanged();
137         }
138     }
139 
onImeTargetChanged(@ullable WindowState imeTarget)140     void onImeTargetChanged(@Nullable WindowState imeTarget) {
141         onControlChanged(TYPE_IME, imeTarget);
142         notifyPendingInsetsControlChanged();
143     }
144 
145     /**
146      * Called when the top opaque fullscreen window that is able to control the system bars changes.
147      *
148      * @param controllingWindow The window that is now able to control the system bars appearance
149      *                          and visibility.
150      */
onBarControllingWindowChanged(@ullable WindowState controllingWindow)151     void onBarControllingWindowChanged(@Nullable WindowState controllingWindow) {
152         // TODO: Apply policy that determines whether controllingWindow is able to control system
153         // bars
154 
155         // TODO: Depending on the form factor, mapping is different
156         onControlChanged(TYPE_TOP_BAR, controllingWindow);
157         onControlChanged(TYPE_NAVIGATION_BAR, controllingWindow);
158         notifyPendingInsetsControlChanged();
159     }
160 
notifyControlRevoked(@onNull WindowState previousControllingWin, InsetsSourceProvider provider)161     void notifyControlRevoked(@NonNull WindowState previousControllingWin,
162             InsetsSourceProvider provider) {
163         removeFromControlMaps(previousControllingWin, provider.getSource().getType());
164     }
165 
onControlChanged(int type, @Nullable WindowState win)166     private void onControlChanged(int type, @Nullable WindowState win) {
167         final WindowState previous = mTypeWinControlMap.get(type);
168         if (win == previous) {
169             return;
170         }
171         final InsetsSourceProvider controller = getSourceProvider(type);
172         if (controller == null) {
173             return;
174         }
175         if (!controller.isControllable()) {
176             return;
177         }
178         controller.updateControlForTarget(win, false /* force */);
179         if (previous != null) {
180             removeFromControlMaps(previous, type);
181             mPendingControlChanged.add(previous);
182         }
183         if (win != null) {
184             addToControlMaps(win, type);
185             mPendingControlChanged.add(win);
186         }
187     }
188 
removeFromControlMaps(@onNull WindowState win, int type)189     private void removeFromControlMaps(@NonNull WindowState win, int type) {
190         final ArrayList<Integer> array = mWinControlTypeMap.get(win);
191         if (array == null) {
192             return;
193         }
194         array.remove((Integer) type);
195         if (array.isEmpty()) {
196             mWinControlTypeMap.remove(win);
197         }
198         mTypeWinControlMap.remove(type);
199     }
200 
addToControlMaps(@onNull WindowState win, int type)201     private void addToControlMaps(@NonNull WindowState win, int type) {
202         final ArrayList<Integer> array = mWinControlTypeMap.computeIfAbsent(win,
203                 key -> new ArrayList<>());
204         array.add(type);
205         mTypeWinControlMap.put(type, win);
206     }
207 
notifyControlChanged(WindowState target)208     void notifyControlChanged(WindowState target) {
209         mPendingControlChanged.add(target);
210         notifyPendingInsetsControlChanged();
211     }
212 
notifyPendingInsetsControlChanged()213     private void notifyPendingInsetsControlChanged() {
214         if (mPendingControlChanged.isEmpty()) {
215             return;
216         }
217         mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
218             for (int i = mPendingControlChanged.size() - 1; i >= 0; i--) {
219                 final WindowState controllingWin = mPendingControlChanged.valueAt(i);
220                 controllingWin.notifyInsetsControlChanged();
221             }
222             mPendingControlChanged.clear();
223         });
224     }
225 
notifyInsetsChanged()226     private void notifyInsetsChanged() {
227         mDisplayContent.forAllWindows(mDispatchInsetsChanged, true /* traverseTopToBottom */);
228     }
229 
dump(String prefix, PrintWriter pw)230     void dump(String prefix, PrintWriter pw) {
231         pw.println(prefix + "WindowInsetsStateController");
232         mState.dump(prefix + "  ", pw);
233         pw.println(prefix + "  " + "Control map:");
234         for (int i = mTypeWinControlMap.size() - 1; i >= 0; i--) {
235             pw.print(prefix + "  ");
236             pw.println(InsetsState.typeToString(mTypeWinControlMap.keyAt(i)) + " -> "
237                     + mTypeWinControlMap.valueAt(i));
238         }
239     }
240 }
241