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.os.Bundle; 29 import android.os.IBinder; 30 import android.os.RemoteException; 31 import android.util.ArrayMap; 32 import android.util.Log; 33 import android.view.IWindowManager; 34 import android.view.WindowManagerGlobal; 35 36 import com.android.internal.annotations.GuardedBy; 37 import com.android.internal.annotations.VisibleForTesting; 38 39 /** 40 * Singleton controller to manage the attached {@link WindowTokenClient}s, and to dispatch 41 * corresponding window configuration change from server side. 42 * @hide 43 */ 44 public class WindowTokenClientController { 45 46 private static final String TAG = WindowTokenClientController.class.getSimpleName(); 47 private static WindowTokenClientController sController; 48 49 private final Object mLock = new Object(); 50 private final IApplicationThread mAppThread = ActivityThread.currentActivityThread() 51 .getApplicationThread(); 52 53 /** Mapping from a client defined token to the {@link WindowTokenClient} it represents. */ 54 @GuardedBy("mLock") 55 private final ArrayMap<IBinder, WindowTokenClient> mWindowTokenClientMap = new ArrayMap<>(); 56 57 /** Gets the singleton controller. */ 58 @NonNull getInstance()59 public static WindowTokenClientController getInstance() { 60 synchronized (WindowTokenClientController.class) { 61 if (sController == null) { 62 sController = new WindowTokenClientController(); 63 } 64 return sController; 65 } 66 } 67 68 /** Overrides the {@link #getInstance()} for test only. */ 69 @VisibleForTesting overrideForTesting(@onNull WindowTokenClientController controller)70 public static void overrideForTesting(@NonNull WindowTokenClientController controller) { 71 synchronized (WindowTokenClientController.class) { 72 sController = controller; 73 } 74 } 75 76 /** Creates a new instance for test only. */ 77 @VisibleForTesting 78 @NonNull createInstanceForTesting()79 public static WindowTokenClientController createInstanceForTesting() { 80 return new WindowTokenClientController(); 81 } 82 WindowTokenClientController()83 private WindowTokenClientController() {} 84 85 /** Gets the {@link WindowContext} instance for the token. */ 86 @Nullable getWindowContext(@onNull IBinder clientToken)87 public Context getWindowContext(@NonNull IBinder clientToken) { 88 final WindowTokenClient windowTokenClient; 89 synchronized (mLock) { 90 windowTokenClient = mWindowTokenClientMap.get(clientToken); 91 } 92 return windowTokenClient != null ? windowTokenClient.getContext() : null; 93 } 94 95 /** 96 * Attaches a {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}. 97 * 98 * @param client The {@link WindowTokenClient} to attach. 99 * @param type The window type of the {@link WindowContext} 100 * @param displayId The {@link Context#getDisplayId() ID of display} to associate with 101 * @param options The window context launched option 102 * @return {@code true} if attaching successfully. 103 */ attachToDisplayArea(@onNull WindowTokenClient client, @WindowType int type, int displayId, @Nullable Bundle options)104 public boolean attachToDisplayArea(@NonNull WindowTokenClient client, 105 @WindowType int type, int displayId, @Nullable Bundle options) { 106 final WindowContextInfo info; 107 try { 108 info = getWindowManagerService().attachWindowContextToDisplayArea( 109 mAppThread, client, type, displayId, options); 110 } catch (RemoteException e) { 111 throw e.rethrowFromSystemServer(); 112 } 113 if (info == null) { 114 return false; 115 } 116 onWindowContextTokenAttached(client, info, false /* shouldReportConfigChange */); 117 return true; 118 } 119 120 /** 121 * Attaches a {@link WindowTokenClient} to a {@code DisplayContent}. 122 * 123 * @param client The {@link WindowTokenClient} to attach. 124 * @param displayId The {@link Context#getDisplayId() ID of display} to associate with 125 * @return {@code true} if attaching successfully. 126 */ attachToDisplayContent(@onNull WindowTokenClient client, int displayId)127 public boolean attachToDisplayContent(@NonNull WindowTokenClient client, int displayId) { 128 final IWindowManager wms = getWindowManagerService(); 129 // #createSystemUiContext may call this method before WindowManagerService is initialized. 130 if (wms == null) { 131 return false; 132 } 133 final WindowContextInfo info; 134 try { 135 info = wms.attachWindowContextToDisplayContent(mAppThread, client, displayId); 136 } catch (RemoteException e) { 137 throw e.rethrowFromSystemServer(); 138 } 139 if (info == null) { 140 return false; 141 } 142 onWindowContextTokenAttached(client, info, false /* shouldReportConfigChange */); 143 return true; 144 } 145 146 /** 147 * Attaches this {@link WindowTokenClient} to a {@code windowToken}. 148 * 149 * @param client The {@link WindowTokenClient} to attach. 150 * @param windowToken the window token to associated with 151 * @return {@code true} if attaching successfully. 152 */ attachToWindowToken(@onNull WindowTokenClient client, @NonNull IBinder windowToken)153 public boolean attachToWindowToken(@NonNull WindowTokenClient client, 154 @NonNull IBinder windowToken) { 155 final WindowContextInfo info; 156 try { 157 info = getWindowManagerService().attachWindowContextToWindowToken( 158 mAppThread, client, windowToken); 159 } catch (RemoteException e) { 160 throw e.rethrowFromSystemServer(); 161 } 162 if (info == null) { 163 return false; 164 } 165 // We currently report configuration for WindowToken after attached. 166 onWindowContextTokenAttached(client, info, true /* shouldReportConfigChange */); 167 return true; 168 } 169 170 /** Detaches a {@link WindowTokenClient} from associated WindowContainer if there's one. */ detachIfNeeded(@onNull WindowTokenClient client)171 public void detachIfNeeded(@NonNull WindowTokenClient client) { 172 synchronized (mLock) { 173 if (mWindowTokenClientMap.remove(client) == null) { 174 return; 175 } 176 } 177 try { 178 getWindowManagerService().detachWindowContext(client); 179 } catch (RemoteException e) { 180 throw e.rethrowFromSystemServer(); 181 } 182 } 183 onWindowContextTokenAttached(@onNull WindowTokenClient client, @NonNull WindowContextInfo info, boolean shouldReportConfigChange)184 private void onWindowContextTokenAttached(@NonNull WindowTokenClient client, 185 @NonNull WindowContextInfo info, boolean shouldReportConfigChange) { 186 synchronized (mLock) { 187 mWindowTokenClientMap.put(client, client); 188 } 189 if (shouldReportConfigChange) { 190 // Should trigger an #onConfigurationChanged callback to the WindowContext. Post the 191 // dispatch in the next loop to prevent the callback from being dispatched before 192 // #onCreate or WindowContext creation.. 193 client.postOnConfigurationChanged(info.getConfiguration(), info.getDisplayId()); 194 } else { 195 // Apply the config change directly in case users get stale values after WindowContext 196 // creation. 197 client.onConfigurationChanged(info.getConfiguration(), info.getDisplayId(), 198 false /* shouldReportConfigChange */); 199 } 200 } 201 202 /** Called when receives {@link WindowContextInfoChangeItem}. */ onWindowContextInfoChanged(@onNull IBinder clientToken, @NonNull WindowContextInfo info)203 public void onWindowContextInfoChanged(@NonNull IBinder clientToken, 204 @NonNull WindowContextInfo info) { 205 final WindowTokenClient windowTokenClient = getWindowTokenClient(clientToken); 206 if (windowTokenClient != null) { 207 windowTokenClient.onConfigurationChanged(info.getConfiguration(), info.getDisplayId()); 208 } 209 } 210 211 /** Called when receives {@link WindowContextWindowRemovalItem}. */ onWindowContextWindowRemoved(@onNull IBinder clientToken)212 public void onWindowContextWindowRemoved(@NonNull IBinder clientToken) { 213 final WindowTokenClient windowTokenClient = getWindowTokenClient(clientToken); 214 if (windowTokenClient != null) { 215 windowTokenClient.onWindowTokenRemoved(); 216 } 217 } 218 219 @Nullable getWindowTokenClient(@onNull IBinder clientToken)220 private WindowTokenClient getWindowTokenClient(@NonNull IBinder clientToken) { 221 final WindowTokenClient windowTokenClient; 222 synchronized (mLock) { 223 windowTokenClient = mWindowTokenClientMap.get(clientToken); 224 } 225 if (windowTokenClient == null) { 226 Log.w(TAG, "Can't find attached WindowTokenClient for " + clientToken); 227 } 228 return windowTokenClient; 229 } 230 231 /** Gets the {@link IWindowManager}. */ 232 @VisibleForTesting 233 @Nullable getWindowManagerService()234 public IWindowManager getWindowManagerService() { 235 return WindowManagerGlobal.getWindowManagerService(); 236 } 237 } 238