1 /* 2 * Copyright (C) 2023 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 android.window; 18 19 import static android.view.WindowManager.LayoutParams.WindowType; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.app.ActivityThread; 24 import android.app.IApplicationThread; 25 import android.app.servertransaction.WindowContextInfoChangeItem; 26 import android.app.servertransaction.WindowContextWindowRemovalItem; 27 import android.content.Context; 28 import android.content.res.Configuration; 29 import android.os.Bundle; 30 import android.os.Handler; 31 import android.os.IBinder; 32 import android.os.RemoteException; 33 import android.util.ArraySet; 34 import android.util.Log; 35 import android.view.IWindowManager; 36 import android.view.WindowManagerGlobal; 37 38 import com.android.internal.annotations.GuardedBy; 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.window.flags.Flags; 41 42 /** 43 * Singleton controller to manage the attached {@link WindowTokenClient}s, and to dispatch 44 * corresponding window configuration change from server side. 45 * @hide 46 */ 47 public class WindowTokenClientController { 48 49 private static final String TAG = WindowTokenClientController.class.getSimpleName(); 50 private static WindowTokenClientController sController; 51 52 private final Object mLock = new Object(); 53 private final IApplicationThread mAppThread = ActivityThread.currentActivityThread() 54 .getApplicationThread(); 55 private final Handler mHandler = ActivityThread.currentActivityThread().getHandler(); 56 57 /** Attached {@link WindowTokenClient}. */ 58 @GuardedBy("mLock") 59 private final ArraySet<WindowTokenClient> mWindowTokenClients = new ArraySet<>(); 60 61 /** Gets the singleton controller. */ 62 @NonNull getInstance()63 public static WindowTokenClientController getInstance() { 64 synchronized (WindowTokenClientController.class) { 65 if (sController == null) { 66 sController = new WindowTokenClientController(); 67 } 68 return sController; 69 } 70 } 71 72 /** Overrides the {@link #getInstance()} for test only. */ 73 @VisibleForTesting overrideForTesting(@onNull WindowTokenClientController controller)74 public static void overrideForTesting(@NonNull WindowTokenClientController controller) { 75 synchronized (WindowTokenClientController.class) { 76 sController = controller; 77 } 78 } 79 80 /** Creates a new instance for test only. */ 81 @VisibleForTesting 82 @NonNull createInstanceForTesting()83 public static WindowTokenClientController createInstanceForTesting() { 84 return new WindowTokenClientController(); 85 } 86 WindowTokenClientController()87 private WindowTokenClientController() {} 88 89 /** Gets the {@link WindowContext} instance for the token. */ 90 @Nullable getWindowContext(@onNull IBinder clientToken)91 public Context getWindowContext(@NonNull IBinder clientToken) { 92 if (!(clientToken instanceof WindowTokenClient windowTokenClient)) { 93 return null; 94 } 95 synchronized (mLock) { 96 if (!mWindowTokenClients.contains(windowTokenClient)) { 97 return null; 98 } 99 } 100 return windowTokenClient.getContext(); 101 } 102 103 /** 104 * Attaches a {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}. 105 * 106 * @param client The {@link WindowTokenClient} to attach. 107 * @param type The window type of the {@link WindowContext} 108 * @param displayId The {@link Context#getDisplayId() ID of display} to associate with 109 * @param options The window context launched option 110 * @return {@code true} if attaching successfully. 111 */ attachToDisplayArea(@onNull WindowTokenClient client, @WindowType int type, int displayId, @Nullable Bundle options)112 public boolean attachToDisplayArea(@NonNull WindowTokenClient client, 113 @WindowType int type, int displayId, @Nullable Bundle options) { 114 final WindowContextInfo info; 115 try { 116 info = getWindowManagerService().attachWindowContextToDisplayArea( 117 mAppThread, client, type, displayId, options); 118 } catch (RemoteException e) { 119 throw e.rethrowFromSystemServer(); 120 } 121 if (info == null) { 122 return false; 123 } 124 onWindowContextTokenAttached(client, info, false /* shouldReportConfigChange */); 125 return true; 126 } 127 128 /** 129 * Attaches a {@link WindowTokenClient} to a {@code DisplayContent}. 130 * 131 * @param client The {@link WindowTokenClient} to attach. 132 * @param displayId The {@link Context#getDisplayId() ID of display} to associate with 133 * @return {@code true} if attaching successfully. 134 */ attachToDisplayContent(@onNull WindowTokenClient client, int displayId)135 public boolean attachToDisplayContent(@NonNull WindowTokenClient client, int displayId) { 136 final IWindowManager wms = getWindowManagerService(); 137 if (wms == null) { 138 // #createSystemUiContext may call this method before WindowManagerService is 139 // initialized. 140 // Regardless of whether or not it is ready, keep track of the token so that when WMS 141 // is initialized later, the SystemUiContext will start reporting from 142 // DisplayContent#registerSystemUiContext, and WindowTokenClientController can report 143 // the Configuration to the correct client. 144 if (Flags.trackSystemUiContextBeforeWms()) { 145 recordWindowContextToken(client); 146 } 147 return false; 148 } 149 final WindowContextInfo info; 150 try { 151 info = wms.attachWindowContextToDisplayContent(mAppThread, client, displayId); 152 } catch (RemoteException e) { 153 throw e.rethrowFromSystemServer(); 154 } catch (Exception e) { 155 Log.e(TAG, "Failed attachToDisplayContent", e); 156 return false; 157 } 158 if (info == null) { 159 return false; 160 } 161 onWindowContextTokenAttached(client, info, false /* shouldReportConfigChange */); 162 return true; 163 } 164 165 /** 166 * Attaches this {@link WindowTokenClient} to a {@code windowToken}. 167 * 168 * @param client The {@link WindowTokenClient} to attach. 169 * @param windowToken the window token to associated with 170 * @return {@code true} if attaching successfully. 171 */ attachToWindowToken(@onNull WindowTokenClient client, @NonNull IBinder windowToken)172 public boolean attachToWindowToken(@NonNull WindowTokenClient client, 173 @NonNull IBinder windowToken) { 174 final WindowContextInfo info; 175 try { 176 info = getWindowManagerService().attachWindowContextToWindowToken( 177 mAppThread, client, windowToken); 178 } catch (RemoteException e) { 179 throw e.rethrowFromSystemServer(); 180 } 181 if (info == null) { 182 return false; 183 } 184 // We currently report configuration for WindowToken after attached. 185 onWindowContextTokenAttached(client, info, true /* shouldReportConfigChange */); 186 return true; 187 } 188 189 /** Detaches a {@link WindowTokenClient} from associated WindowContainer if there's one. */ detachIfNeeded(@onNull WindowTokenClient client)190 public void detachIfNeeded(@NonNull WindowTokenClient client) { 191 synchronized (mLock) { 192 if (!mWindowTokenClients.remove(client)) { 193 return; 194 } 195 } 196 final IWindowManager wms = getWindowManagerService(); 197 if (wms == null) { 198 // #createSystemUiContext may call this method before WindowManagerService is 199 // initialized. If it is GC'ed before WMS is initialized, skip calling into WMS. 200 return; 201 } 202 try { 203 wms.detachWindowContext(client); 204 } catch (RemoteException e) { 205 throw e.rethrowFromSystemServer(); 206 } 207 } 208 209 /** 210 * Reparents a {@link WindowTokenClient} and its associated WindowContainer if there's one. 211 */ reparentToDisplayArea(@onNull WindowTokenClient client, int displayId)212 public void reparentToDisplayArea(@NonNull WindowTokenClient client, int displayId) { 213 try { 214 if (!getWindowManagerService().reparentWindowContextToDisplayArea(mAppThread, client, 215 displayId)) { 216 Log.e(TAG, 217 "Didn't succeed reparenting of " + client + " to displayId=" + displayId); 218 } 219 } catch (RemoteException e) { 220 throw e.rethrowFromSystemServer(); 221 } 222 } 223 onWindowContextTokenAttached(@onNull WindowTokenClient client, @NonNull WindowContextInfo info, boolean shouldReportConfigChange)224 private void onWindowContextTokenAttached(@NonNull WindowTokenClient client, 225 @NonNull WindowContextInfo info, boolean shouldReportConfigChange) { 226 recordWindowContextToken(client); 227 if (shouldReportConfigChange) { 228 // Should trigger an #onConfigurationChanged callback to the WindowContext. Post the 229 // dispatch in the next loop to prevent the callback from being dispatched before 230 // #onCreate or WindowContext creation.. 231 client.postOnConfigurationChanged(info.getConfiguration(), info.getDisplayId()); 232 } else { 233 // Apply the config change directly in case users get stale values after WindowContext 234 // creation. 235 client.onConfigurationChanged(info.getConfiguration(), info.getDisplayId(), 236 false /* shouldReportConfigChange */); 237 } 238 } 239 recordWindowContextToken(@onNull WindowTokenClient client)240 private void recordWindowContextToken(@NonNull WindowTokenClient client) { 241 synchronized (mLock) { 242 mWindowTokenClients.add(client); 243 } 244 } 245 246 /** Called when receives {@link WindowContextInfoChangeItem}. */ onWindowContextInfoChanged(@onNull IBinder clientToken, @NonNull WindowContextInfo info)247 public void onWindowContextInfoChanged(@NonNull IBinder clientToken, 248 @NonNull WindowContextInfo info) { 249 final WindowTokenClient windowTokenClient = getWindowTokenClientIfAttached(clientToken); 250 if (windowTokenClient != null) { 251 windowTokenClient.onConfigurationChanged(info.getConfiguration(), info.getDisplayId()); 252 } 253 } 254 255 /** Called when receives {@link WindowContextWindowRemovalItem}. */ onWindowContextWindowRemoved(@onNull IBinder clientToken)256 public void onWindowContextWindowRemoved(@NonNull IBinder clientToken) { 257 final WindowTokenClient windowTokenClient = getWindowTokenClientIfAttached(clientToken); 258 if (windowTokenClient != null) { 259 windowTokenClient.onWindowTokenRemoved(); 260 } 261 } 262 263 /** Propagates the configuration change to the client token. */ onWindowConfigurationChanged(@onNull IBinder clientToken, @NonNull Configuration config, int displayId)264 public void onWindowConfigurationChanged(@NonNull IBinder clientToken, 265 @NonNull Configuration config, int displayId) { 266 final WindowTokenClient windowTokenClient = getWindowTokenClientIfAttached(clientToken); 267 if (windowTokenClient != null) { 268 // Let's make sure it's called on the main thread! 269 if (mHandler.getLooper().isCurrentThread()) { 270 windowTokenClient.onConfigurationChanged(config, displayId); 271 } else { 272 windowTokenClient.postOnConfigurationChanged(config, displayId); 273 } 274 } 275 } 276 277 @Nullable getWindowTokenClientIfAttached(@onNull IBinder clientToken)278 private WindowTokenClient getWindowTokenClientIfAttached(@NonNull IBinder clientToken) { 279 if (!(clientToken instanceof WindowTokenClient windowTokenClient)) { 280 Log.e(TAG, "getWindowTokenClient failed for non-window token " + clientToken); 281 return null; 282 } 283 synchronized (mLock) { 284 if (!mWindowTokenClients.contains(windowTokenClient)) { 285 Log.w(TAG, "Can't find attached WindowTokenClient for " + clientToken); 286 return null; 287 } 288 } 289 return windowTokenClient; 290 } 291 292 /** Gets the {@link IWindowManager}. */ 293 @VisibleForTesting 294 @Nullable getWindowManagerService()295 public IWindowManager getWindowManagerService() { 296 return WindowManagerGlobal.getWindowManagerService(); 297 } 298 } 299