/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.window; import static android.view.WindowManager.LayoutParams.WindowType; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; import android.app.IApplicationThread; import android.app.servertransaction.WindowContextInfoChangeItem; import android.app.servertransaction.WindowContextWindowRemovalItem; import android.content.Context; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; import android.view.IWindowManager; import android.view.WindowManagerGlobal; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; /** * Singleton controller to manage the attached {@link WindowTokenClient}s, and to dispatch * corresponding window configuration change from server side. * @hide */ public class WindowTokenClientController { private static final String TAG = WindowTokenClientController.class.getSimpleName(); private static WindowTokenClientController sController; private final Object mLock = new Object(); private final IApplicationThread mAppThread = ActivityThread.currentActivityThread() .getApplicationThread(); /** Mapping from a client defined token to the {@link WindowTokenClient} it represents. */ @GuardedBy("mLock") private final ArrayMap mWindowTokenClientMap = new ArrayMap<>(); /** Gets the singleton controller. */ @NonNull public static WindowTokenClientController getInstance() { synchronized (WindowTokenClientController.class) { if (sController == null) { sController = new WindowTokenClientController(); } return sController; } } /** Overrides the {@link #getInstance()} for test only. */ @VisibleForTesting public static void overrideForTesting(@NonNull WindowTokenClientController controller) { synchronized (WindowTokenClientController.class) { sController = controller; } } /** Creates a new instance for test only. */ @VisibleForTesting @NonNull public static WindowTokenClientController createInstanceForTesting() { return new WindowTokenClientController(); } private WindowTokenClientController() {} /** Gets the {@link WindowContext} instance for the token. */ @Nullable public Context getWindowContext(@NonNull IBinder clientToken) { final WindowTokenClient windowTokenClient; synchronized (mLock) { windowTokenClient = mWindowTokenClientMap.get(clientToken); } return windowTokenClient != null ? windowTokenClient.getContext() : null; } /** * Attaches a {@link WindowTokenClient} to a {@link com.android.server.wm.DisplayArea}. * * @param client The {@link WindowTokenClient} to attach. * @param type The window type of the {@link WindowContext} * @param displayId The {@link Context#getDisplayId() ID of display} to associate with * @param options The window context launched option * @return {@code true} if attaching successfully. */ public boolean attachToDisplayArea(@NonNull WindowTokenClient client, @WindowType int type, int displayId, @Nullable Bundle options) { final WindowContextInfo info; try { info = getWindowManagerService().attachWindowContextToDisplayArea( mAppThread, client, type, displayId, options); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } if (info == null) { return false; } onWindowContextTokenAttached(client, info, false /* shouldReportConfigChange */); return true; } /** * Attaches a {@link WindowTokenClient} to a {@code DisplayContent}. * * @param client The {@link WindowTokenClient} to attach. * @param displayId The {@link Context#getDisplayId() ID of display} to associate with * @return {@code true} if attaching successfully. */ public boolean attachToDisplayContent(@NonNull WindowTokenClient client, int displayId) { final IWindowManager wms = getWindowManagerService(); // #createSystemUiContext may call this method before WindowManagerService is initialized. if (wms == null) { return false; } final WindowContextInfo info; try { info = wms.attachWindowContextToDisplayContent(mAppThread, client, displayId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } if (info == null) { return false; } onWindowContextTokenAttached(client, info, false /* shouldReportConfigChange */); return true; } /** * Attaches this {@link WindowTokenClient} to a {@code windowToken}. * * @param client The {@link WindowTokenClient} to attach. * @param windowToken the window token to associated with * @return {@code true} if attaching successfully. */ public boolean attachToWindowToken(@NonNull WindowTokenClient client, @NonNull IBinder windowToken) { final WindowContextInfo info; try { info = getWindowManagerService().attachWindowContextToWindowToken( mAppThread, client, windowToken); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } if (info == null) { return false; } // We currently report configuration for WindowToken after attached. onWindowContextTokenAttached(client, info, true /* shouldReportConfigChange */); return true; } /** Detaches a {@link WindowTokenClient} from associated WindowContainer if there's one. */ public void detachIfNeeded(@NonNull WindowTokenClient client) { synchronized (mLock) { if (mWindowTokenClientMap.remove(client) == null) { return; } } try { getWindowManagerService().detachWindowContext(client); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } private void onWindowContextTokenAttached(@NonNull WindowTokenClient client, @NonNull WindowContextInfo info, boolean shouldReportConfigChange) { synchronized (mLock) { mWindowTokenClientMap.put(client, client); } if (shouldReportConfigChange) { // Should trigger an #onConfigurationChanged callback to the WindowContext. Post the // dispatch in the next loop to prevent the callback from being dispatched before // #onCreate or WindowContext creation.. client.postOnConfigurationChanged(info.getConfiguration(), info.getDisplayId()); } else { // Apply the config change directly in case users get stale values after WindowContext // creation. client.onConfigurationChanged(info.getConfiguration(), info.getDisplayId(), false /* shouldReportConfigChange */); } } /** Called when receives {@link WindowContextInfoChangeItem}. */ public void onWindowContextInfoChanged(@NonNull IBinder clientToken, @NonNull WindowContextInfo info) { final WindowTokenClient windowTokenClient = getWindowTokenClient(clientToken); if (windowTokenClient != null) { windowTokenClient.onConfigurationChanged(info.getConfiguration(), info.getDisplayId()); } } /** Called when receives {@link WindowContextWindowRemovalItem}. */ public void onWindowContextWindowRemoved(@NonNull IBinder clientToken) { final WindowTokenClient windowTokenClient = getWindowTokenClient(clientToken); if (windowTokenClient != null) { windowTokenClient.onWindowTokenRemoved(); } } @Nullable private WindowTokenClient getWindowTokenClient(@NonNull IBinder clientToken) { final WindowTokenClient windowTokenClient; synchronized (mLock) { windowTokenClient = mWindowTokenClientMap.get(clientToken); } if (windowTokenClient == null) { Log.w(TAG, "Can't find attached WindowTokenClient for " + clientToken); } return windowTokenClient; } /** Gets the {@link IWindowManager}. */ @VisibleForTesting @Nullable public IWindowManager getWindowManagerService() { return WindowManagerGlobal.getWindowManagerService(); } }