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.wm.shell;
18 
19 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
20 
21 import android.annotation.SuppressLint;
22 import android.annotation.UiContext;
23 import android.app.ResourcesManager;
24 import android.content.Context;
25 import android.content.ContextWrapper;
26 import android.content.res.Configuration;
27 import android.hardware.display.DisplayManager;
28 import android.os.Binder;
29 import android.os.IBinder;
30 import android.util.SparseArray;
31 import android.view.Display;
32 import android.view.SurfaceControl;
33 import android.window.DisplayAreaAppearedInfo;
34 import android.window.DisplayAreaInfo;
35 import android.window.DisplayAreaOrganizer;
36 import android.window.SystemPerformanceHinter;
37 
38 import androidx.annotation.NonNull;
39 import androidx.annotation.Nullable;
40 
41 import com.android.internal.protolog.common.ProtoLog;
42 import com.android.wm.shell.sysui.ShellInit;
43 
44 import java.io.PrintWriter;
45 import java.util.ArrayList;
46 import java.util.List;
47 import java.util.concurrent.Executor;
48 
49 /** Display area organizer for the root/default TaskDisplayAreas */
50 public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
51 
52     private static final String TAG = RootTaskDisplayAreaOrganizer.class.getSimpleName();
53 
54     /** {@link DisplayAreaInfo} list, which is mapped by display IDs. */
55     private final SparseArray<DisplayAreaInfo> mDisplayAreasInfo = new SparseArray<>();
56     /** Display area leashes, which is mapped by display IDs. */
57     private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>();
58 
59     private final SparseArray<ArrayList<RootTaskDisplayAreaListener>> mListeners =
60             new SparseArray<>();
61     /** {@link DisplayAreaContext} list, which is mapped by display IDs. */
62     private final SparseArray<DisplayAreaContext> mDisplayAreaContexts = new SparseArray<>();
63 
64     private final SystemPerformanceHinter.DisplayRootProvider mPerfRootProvider =
65             new SystemPerformanceHinter.DisplayRootProvider() {
66                 @Override
67                 public SurfaceControl getRootForDisplay(int displayId) {
68                     return mLeashes.get(displayId);
69                 }
70             };
71 
72     private final Context mContext;
73 
RootTaskDisplayAreaOrganizer(@onNull Executor executor, @NonNull Context context, @NonNull ShellInit shellInit)74     public RootTaskDisplayAreaOrganizer(@NonNull Executor executor, @NonNull Context context,
75             @NonNull ShellInit shellInit) {
76         super(executor);
77         mContext = context;
78         shellInit.addInitCallback(this::onInit, this);
79     }
80 
81     @SuppressLint("MissingPermission") // Only called by SysUI.
onInit()82     private void onInit() {
83         final List<DisplayAreaAppearedInfo> infos =
84                 registerOrganizer(FEATURE_DEFAULT_TASK_CONTAINER);
85         for (int i = infos.size() - 1; i >= 0; --i) {
86             onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash());
87         }
88     }
89 
registerListener(int displayId, RootTaskDisplayAreaListener listener)90     public void registerListener(int displayId, RootTaskDisplayAreaListener listener) {
91         ArrayList<RootTaskDisplayAreaListener> listeners = mListeners.get(displayId);
92         if (listeners == null) {
93             listeners = new ArrayList<>();
94             mListeners.put(displayId, listeners);
95         }
96 
97         listeners.add(listener);
98 
99         final DisplayAreaInfo info = mDisplayAreasInfo.get(displayId);
100         if (info != null) {
101             listener.onDisplayAreaAppeared(info);
102         }
103     }
104 
unregisterListener(RootTaskDisplayAreaListener listener)105     public void unregisterListener(RootTaskDisplayAreaListener listener) {
106         for (int i = mListeners.size() - 1; i >= 0; --i) {
107             final List<RootTaskDisplayAreaListener> listeners = mListeners.valueAt(i);
108             if (listeners == null) continue;
109             listeners.remove(listener);
110         }
111     }
112 
attachToDisplayArea(int displayId, SurfaceControl.Builder b)113     public void attachToDisplayArea(int displayId, SurfaceControl.Builder b) {
114         final SurfaceControl sc = mLeashes.get(displayId);
115         b.setParent(sc);
116     }
117 
118     /**
119      * Re-parents the provided surface to the leash of the provided display.
120      *
121      * @param displayId the display area to reparent to.
122      * @param sc the surface to be reparented.
123      * @param t a {@link SurfaceControl.Transaction} in which to reparent.
124      */
reparentToDisplayArea(int displayId, SurfaceControl sc, SurfaceControl.Transaction t)125     public void reparentToDisplayArea(int displayId, SurfaceControl sc,
126                                       SurfaceControl.Transaction t) {
127         final SurfaceControl displayAreaLeash = mLeashes.get(displayId);
128         t.reparent(sc, displayAreaLeash);
129     }
130 
setPosition(@onNull SurfaceControl.Transaction tx, int displayId, int x, int y)131     public void setPosition(@NonNull SurfaceControl.Transaction tx, int displayId, int x, int y) {
132         final SurfaceControl sc = mLeashes.get(displayId);
133         if (sc == null) {
134             throw new IllegalArgumentException("can't find display" + displayId);
135         }
136         tx.setPosition(sc, x, y);
137     }
138 
139     @Override
onDisplayAreaAppeared(@onNull DisplayAreaInfo displayAreaInfo, @NonNull SurfaceControl leash)140     public void onDisplayAreaAppeared(@NonNull DisplayAreaInfo displayAreaInfo,
141             @NonNull SurfaceControl leash) {
142         if (displayAreaInfo.featureId != FEATURE_DEFAULT_TASK_CONTAINER) {
143             throw new IllegalArgumentException(
144                     "Unknown feature: " + displayAreaInfo.featureId
145                             + "displayAreaInfo:" + displayAreaInfo);
146         }
147 
148         final int displayId = displayAreaInfo.displayId;
149         if (mDisplayAreasInfo.get(displayId) != null) {
150             throw new IllegalArgumentException(
151                     "Duplicate DA for displayId: " + displayId
152                             + " displayAreaInfo:" + displayAreaInfo
153                             + " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId));
154         }
155 
156         leash.setUnreleasedWarningCallSite(
157                 "RootTaskDisplayAreaOrganizer.onDisplayAreaAppeared");
158         mDisplayAreasInfo.put(displayId, displayAreaInfo);
159         mLeashes.put(displayId, leash);
160 
161         ArrayList<RootTaskDisplayAreaListener> listeners = mListeners.get(displayId);
162         if (listeners != null) {
163             for (int i = listeners.size() - 1; i >= 0; --i) {
164                 listeners.get(i).onDisplayAreaAppeared(displayAreaInfo);
165             }
166         }
167         applyConfigChangesToContext(displayAreaInfo);
168     }
169 
170     @Override
onDisplayAreaVanished(@onNull DisplayAreaInfo displayAreaInfo)171     public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) {
172         final int displayId = displayAreaInfo.displayId;
173         if (mDisplayAreasInfo.get(displayId) == null) {
174             throw new IllegalArgumentException(
175                     "onDisplayAreaVanished() Unknown DA displayId: " + displayId
176                             + " displayAreaInfo:" + displayAreaInfo
177                             + " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId));
178         }
179 
180         mDisplayAreasInfo.remove(displayId);
181         mLeashes.get(displayId).release();
182         mLeashes.remove(displayId);
183 
184         ArrayList<RootTaskDisplayAreaListener> listeners = mListeners.get(displayId);
185         if (listeners != null) {
186             for (int i = listeners.size() - 1; i >= 0; --i) {
187                 listeners.get(i).onDisplayAreaVanished(displayAreaInfo);
188             }
189         }
190         mDisplayAreaContexts.remove(displayId);
191     }
192 
193     @Override
onDisplayAreaInfoChanged(@onNull DisplayAreaInfo displayAreaInfo)194     public void onDisplayAreaInfoChanged(@NonNull DisplayAreaInfo displayAreaInfo) {
195         final int displayId = displayAreaInfo.displayId;
196         if (mDisplayAreasInfo.get(displayId) == null) {
197             throw new IllegalArgumentException(
198                     "onDisplayAreaInfoChanged() Unknown DA displayId: " + displayId
199                             + " displayAreaInfo:" + displayAreaInfo
200                             + " mDisplayAreasInfo.get():" + mDisplayAreasInfo.get(displayId));
201         }
202 
203         mDisplayAreasInfo.put(displayId, displayAreaInfo);
204 
205         ArrayList<RootTaskDisplayAreaListener> listeners = mListeners.get(displayId);
206         if (listeners != null) {
207             for (int i = listeners.size() - 1; i >= 0; --i) {
208                 listeners.get(i).onDisplayAreaInfoChanged(displayAreaInfo);
209             }
210         }
211         applyConfigChangesToContext(displayAreaInfo);
212     }
213 
214     /**
215      * Returns the list of display ids that are tracked by a {@link DisplayAreaInfo}
216      */
getDisplayIds()217     public int[] getDisplayIds() {
218         int[] displayIds = new int[mDisplayAreasInfo.size()];
219         for (int i = 0; i < mDisplayAreasInfo.size(); i++) {
220             displayIds[i] = mDisplayAreasInfo.keyAt(i);
221         }
222         return displayIds;
223     }
224 
225     /**
226      * Returns the {@link DisplayAreaInfo} of the {@link DisplayAreaInfo#displayId}.
227      */
228     @Nullable
getDisplayAreaInfo(int displayId)229     public DisplayAreaInfo getDisplayAreaInfo(int displayId) {
230         return mDisplayAreasInfo.get(displayId);
231     }
232 
233     /**
234      * Applies the {@link DisplayAreaInfo} to the {@link DisplayAreaContext} specified by
235      * {@link DisplayAreaInfo#displayId}.
236      */
applyConfigChangesToContext(@onNull DisplayAreaInfo displayAreaInfo)237     private void applyConfigChangesToContext(@NonNull DisplayAreaInfo displayAreaInfo) {
238         final int displayId = displayAreaInfo.displayId;
239         final Display display = mContext.getSystemService(DisplayManager.class)
240                 .getDisplay(displayId);
241         if (display == null) {
242             ProtoLog.w(WM_SHELL_TASK_ORG, "The display#%d has been removed."
243                     + " Skip following steps", displayId);
244             return;
245         }
246         DisplayAreaContext daContext = mDisplayAreaContexts.get(displayId);
247         if (daContext == null) {
248             daContext = new DisplayAreaContext(mContext, display);
249             mDisplayAreaContexts.put(displayId, daContext);
250         }
251         daContext.updateConfigurationChanges(displayAreaInfo.configuration);
252     }
253 
254     /**
255      * Returns the UI context associated with RootTaskDisplayArea specified by {@code displayId}.
256      */
257     @Nullable
258     @UiContext
getContext(int displayId)259     public Context getContext(int displayId) {
260         return mDisplayAreaContexts.get(displayId);
261     }
262 
263     @NonNull
getPerformanceRootProvider()264     public SystemPerformanceHinter.DisplayRootProvider getPerformanceRootProvider() {
265         return mPerfRootProvider;
266     }
267 
dump(@onNull PrintWriter pw, String prefix)268     public void dump(@NonNull PrintWriter pw, String prefix) {
269         final String innerPrefix = prefix + "  ";
270         final String childPrefix = innerPrefix + "  ";
271         pw.println(prefix + this);
272     }
273 
274     @Override
toString()275     public String toString() {
276         return TAG + "#" + mDisplayAreasInfo.size();
277     }
278 
279     /** Callbacks for when root task display areas change. */
280     public interface RootTaskDisplayAreaListener {
onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo)281         default void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) {
282         }
283 
onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo)284         default void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) {
285         }
286 
onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo)287         default void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) {
288         }
289 
dump(@onNull PrintWriter pw, String prefix)290         default void dump(@NonNull PrintWriter pw, String prefix) {
291         }
292     }
293 
294     /**
295      * A UI context to associate with a {@link com.android.server.wm.DisplayArea}.
296      *
297      * This context receives configuration changes through {@link DisplayAreaOrganizer} callbacks
298      * and the core implementation is {@link Context#createTokenContext(IBinder, Display)} to apply
299      * the configuration updates to the {@link android.content.res.Resources}.
300      */
301     @UiContext
302     public static class DisplayAreaContext extends ContextWrapper {
303         private final IBinder mToken = new Binder();
304         private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
305 
DisplayAreaContext(@onNull Context context, @NonNull Display display)306         public DisplayAreaContext(@NonNull Context context, @NonNull Display display) {
307             super(null);
308             attachBaseContext(context.createTokenContext(mToken, display));
309         }
310 
updateConfigurationChanges(@onNull Configuration newConfig)311         private void updateConfigurationChanges(@NonNull Configuration newConfig) {
312             final Configuration config = getResources().getConfiguration();
313             final boolean configChanged = config.diff(newConfig) != 0;
314             if (configChanged) {
315                 mResourcesManager.updateResourcesForActivity(mToken, newConfig, getDisplayId());
316             }
317         }
318     }
319 }