| /* |
| * Copyright (C) 2006 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.view; |
| |
| import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; |
| import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; |
| import static android.window.WindowProviderService.isWindowProviderService; |
| |
| import static com.android.window.flags.Flags.screenRecordingCallbacks; |
| |
| import android.annotation.CallbackExecutor; |
| import android.annotation.IntRange; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.UiContext; |
| import android.compat.annotation.UnsupportedAppUsage; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.graphics.Bitmap; |
| import android.graphics.Region; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.RemoteException; |
| import android.os.StrictMode; |
| import android.util.Log; |
| import android.window.ITaskFpsCallback; |
| import android.window.InputTransferToken; |
| import android.window.TaskFpsCallback; |
| import android.window.TrustedPresentationThresholds; |
| import android.window.WindowContext; |
| import android.window.WindowMetricsController; |
| import android.window.WindowProvider; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.os.IResultReceiver; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.concurrent.Executor; |
| import java.util.function.Consumer; |
| import java.util.function.IntConsumer; |
| |
| /** |
| * Provides low-level communication with the system window manager for |
| * operations that are bound to a particular context, display or parent window. |
| * Instances of this object are sensitive to the compatibility info associated |
| * with the running application. |
| * |
| * This object implements the {@link ViewManager} interface, |
| * allowing you to add any View subclass as a top-level window on the screen. |
| * Additional window manager specific layout parameters are defined for |
| * control over how windows are displayed. It also implements the {@link WindowManager} |
| * interface, allowing you to control the displays attached to the device. |
| * |
| * <p>Applications will not normally use WindowManager directly, instead relying |
| * on the higher-level facilities in {@link android.app.Activity} and |
| * {@link android.app.Dialog}. |
| * |
| * <p>Even for low-level window manager access, it is almost never correct to use |
| * this class. For example, {@link android.app.Activity#getWindowManager} |
| * provides a window manager for adding windows that are associated with that |
| * activity -- the window manager will not normally allow you to add arbitrary |
| * windows that are not associated with an activity. |
| * |
| * @see WindowManager |
| * @see WindowManagerGlobal |
| * @hide |
| */ |
| public final class WindowManagerImpl implements WindowManager { |
| private static final String TAG = "WindowManager"; |
| |
| @UnsupportedAppUsage |
| private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); |
| @UiContext |
| @VisibleForTesting |
| public final Context mContext; |
| private final Window mParentWindow; |
| |
| /** |
| * If {@link LayoutParams#token} is {@code null} and no parent window is specified, the value |
| * of {@link LayoutParams#token} will be overridden to {@code mDefaultToken}. |
| */ |
| private IBinder mDefaultToken; |
| |
| /** |
| * This token will be set to {@link LayoutParams#mWindowContextToken} and used to receive |
| * configuration changes from the server side. |
| */ |
| @Nullable |
| private final IBinder mWindowContextToken; |
| |
| @GuardedBy("mOnFpsCallbackListenerProxies") |
| private final ArrayList<OnFpsCallbackListenerProxy> mOnFpsCallbackListenerProxies = |
| new ArrayList<>(); |
| |
| /** A controller to handle {@link WindowMetrics} related APIs */ |
| @NonNull |
| private final WindowMetricsController mWindowMetricsController; |
| |
| public WindowManagerImpl(Context context) { |
| this(context, null /* parentWindow */, null /* clientToken */); |
| } |
| |
| private WindowManagerImpl(Context context, Window parentWindow, |
| @Nullable IBinder windowContextToken) { |
| mContext = context; |
| mParentWindow = parentWindow; |
| mWindowContextToken = windowContextToken; |
| mWindowMetricsController = new WindowMetricsController(mContext); |
| } |
| |
| public WindowManagerImpl createLocalWindowManager(Window parentWindow) { |
| return new WindowManagerImpl(mContext, parentWindow, mWindowContextToken); |
| } |
| |
| public WindowManagerImpl createPresentationWindowManager(Context displayContext) { |
| return new WindowManagerImpl(displayContext, mParentWindow, mWindowContextToken); |
| } |
| |
| /** Creates a {@link WindowManager} for a {@link WindowContext}. */ |
| public static WindowManager createWindowContextWindowManager(Context context) { |
| final IBinder clientToken = context.getWindowContextToken(); |
| return new WindowManagerImpl(context, null /* parentWindow */, clientToken); |
| } |
| |
| /** |
| * Sets the window token to assign when none is specified by the client or |
| * available from the parent window. |
| * |
| * @param token The default token to assign. |
| */ |
| public void setDefaultToken(IBinder token) { |
| mDefaultToken = token; |
| } |
| |
| @Override |
| public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { |
| applyTokens(params); |
| mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow, |
| mContext.getUserId()); |
| } |
| |
| @Override |
| public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { |
| applyTokens(params); |
| mGlobal.updateViewLayout(view, params); |
| } |
| |
| private void applyTokens(@NonNull ViewGroup.LayoutParams params) { |
| if (!(params instanceof WindowManager.LayoutParams)) { |
| throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); |
| } |
| final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; |
| assertWindowContextTypeMatches(wparams.type); |
| // Only use the default token if we don't have a parent window and a token. |
| if (mDefaultToken != null && mParentWindow == null && wparams.token == null) { |
| wparams.token = mDefaultToken; |
| } |
| wparams.mWindowContextToken = mWindowContextToken; |
| } |
| |
| private void assertWindowContextTypeMatches(@LayoutParams.WindowType int windowType) { |
| if (!(mContext instanceof WindowProvider)) { |
| return; |
| } |
| // Don't need to check sub-window type because sub window should be allowed to be attached |
| // to the parent window. |
| if (windowType >= FIRST_SUB_WINDOW && windowType <= LAST_SUB_WINDOW) { |
| return; |
| } |
| final WindowProvider windowProvider = (WindowProvider) mContext; |
| if (windowProvider.getWindowType() == windowType) { |
| return; |
| } |
| IllegalArgumentException exception = new IllegalArgumentException("Window type mismatch." |
| + " Window Context's window type is " + windowProvider.getWindowType() |
| + ", while LayoutParams' type is set to " + windowType + "." |
| + " Please create another Window Context via" |
| + " createWindowContext(getDisplay(), " + windowType + ", null)" |
| + " to add window with type:" + windowType); |
| if (!isWindowProviderService(windowProvider.getWindowContextOptions())) { |
| throw exception; |
| } |
| // Throw IncorrectCorrectViolation if the Window Context is allowed to provide multiple |
| // window types. Usually it's because the Window Context is a WindowProviderService. |
| StrictMode.onIncorrectContextUsed("WindowContext's window type must" |
| + " match type in WindowManager.LayoutParams", exception); |
| } |
| |
| @Override |
| public void removeView(View view) { |
| mGlobal.removeView(view, false); |
| } |
| |
| @Override |
| public void removeViewImmediate(View view) { |
| mGlobal.removeView(view, true); |
| } |
| |
| @Override |
| public void requestAppKeyboardShortcuts( |
| final KeyboardShortcutsReceiver receiver, int deviceId) { |
| IResultReceiver resultReceiver = new IResultReceiver.Stub() { |
| @Override |
| public void send(int resultCode, Bundle resultData) throws RemoteException { |
| List<KeyboardShortcutGroup> result = |
| resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY, |
| android.view.KeyboardShortcutGroup.class); |
| receiver.onKeyboardShortcutsReceived(result); |
| } |
| }; |
| try { |
| WindowManagerGlobal.getWindowManagerService() |
| .requestAppKeyboardShortcuts(resultReceiver, deviceId); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| @Override |
| public void requestImeKeyboardShortcuts( |
| final KeyboardShortcutsReceiver receiver, int deviceId) { |
| IResultReceiver resultReceiver = new IResultReceiver.Stub() { |
| @Override |
| public void send(int resultCode, Bundle resultData) throws RemoteException { |
| List<KeyboardShortcutGroup> result = |
| resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY, |
| android.view.KeyboardShortcutGroup.class); |
| receiver.onKeyboardShortcutsReceived(result); |
| } |
| }; |
| try { |
| WindowManagerGlobal.getWindowManagerService() |
| .requestImeKeyboardShortcuts(resultReceiver, deviceId); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| @Override |
| public Display getDefaultDisplay() { |
| return mContext.getDisplayNoVerify(); |
| } |
| |
| @Override |
| public Region getCurrentImeTouchRegion() { |
| try { |
| return WindowManagerGlobal.getWindowManagerService().getCurrentImeTouchRegion(); |
| } catch (RemoteException e) { |
| } |
| return null; |
| } |
| |
| @Override |
| public void setShouldShowWithInsecureKeyguard(int displayId, boolean shouldShow) { |
| try { |
| WindowManagerGlobal.getWindowManagerService() |
| .setShouldShowWithInsecureKeyguard(displayId, shouldShow); |
| } catch (RemoteException e) { |
| } |
| } |
| |
| @Override |
| public void setShouldShowSystemDecors(int displayId, boolean shouldShow) { |
| try { |
| WindowManagerGlobal.getWindowManagerService() |
| .setShouldShowSystemDecors(displayId, shouldShow); |
| } catch (RemoteException e) { |
| } |
| } |
| |
| @Override |
| public boolean shouldShowSystemDecors(int displayId) { |
| try { |
| return WindowManagerGlobal.getWindowManagerService().shouldShowSystemDecors(displayId); |
| } catch (RemoteException e) { |
| } |
| return false; |
| } |
| |
| @Override |
| public void setDisplayImePolicy(int displayId, @DisplayImePolicy int imePolicy) { |
| try { |
| WindowManagerGlobal.getWindowManagerService().setDisplayImePolicy(displayId, imePolicy); |
| } catch (RemoteException e) { |
| } |
| } |
| |
| @Override |
| public @DisplayImePolicy int getDisplayImePolicy(int displayId) { |
| try { |
| return WindowManagerGlobal.getWindowManagerService().getDisplayImePolicy(displayId); |
| } catch (RemoteException e) { |
| } |
| return DISPLAY_IME_POLICY_FALLBACK_DISPLAY; |
| } |
| |
| @Override |
| public boolean isGlobalKey(int keyCode) { |
| try { |
| return WindowManagerGlobal.getWindowManagerService().isGlobalKey(keyCode); |
| } catch (RemoteException e) { |
| } |
| return false; |
| } |
| |
| @Override |
| public WindowMetrics getCurrentWindowMetrics() { |
| return mWindowMetricsController.getCurrentWindowMetrics(); |
| } |
| |
| @Override |
| public WindowMetrics getMaximumWindowMetrics() { |
| return mWindowMetricsController.getMaximumWindowMetrics(); |
| } |
| |
| @Override |
| @NonNull |
| public Set<WindowMetrics> getPossibleMaximumWindowMetrics(int displayId) { |
| return mWindowMetricsController.getPossibleMaximumWindowMetrics(displayId); |
| } |
| |
| @Override |
| public void holdLock(IBinder token, int durationMs) { |
| try { |
| WindowManagerGlobal.getWindowManagerService().holdLock(token, durationMs); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| @Override |
| public boolean isCrossWindowBlurEnabled() { |
| return CrossWindowBlurListeners.getInstance().isCrossWindowBlurEnabled(); |
| } |
| |
| @Override |
| public void addCrossWindowBlurEnabledListener(@NonNull Consumer<Boolean> listener) { |
| addCrossWindowBlurEnabledListener(mContext.getMainExecutor(), listener); |
| } |
| |
| @Override |
| public void addCrossWindowBlurEnabledListener(@NonNull @CallbackExecutor Executor executor, |
| @NonNull Consumer<Boolean> listener) { |
| CrossWindowBlurListeners.getInstance().addListener(executor, listener); |
| } |
| |
| @Override |
| public void removeCrossWindowBlurEnabledListener(@NonNull Consumer<Boolean> listener) { |
| CrossWindowBlurListeners.getInstance().removeListener(listener); |
| } |
| |
| @Override |
| public void addProposedRotationListener(@NonNull @CallbackExecutor Executor executor, |
| @NonNull IntConsumer listener) { |
| Objects.requireNonNull(executor, "executor must not be null"); |
| Objects.requireNonNull(listener, "listener must not be null"); |
| final IBinder contextToken = Context.getToken(mContext); |
| if (contextToken == null) { |
| throw new UnsupportedOperationException("The context of this window manager instance " |
| + "must be a UI context, e.g. an Activity or a Context created by " |
| + "Context#createWindowContext()"); |
| } |
| mGlobal.registerProposedRotationListener(contextToken, executor, listener); |
| } |
| |
| @Override |
| public void removeProposedRotationListener(@NonNull IntConsumer listener) { |
| mGlobal.unregisterProposedRotationListener(Context.getToken(mContext), listener); |
| } |
| |
| @Override |
| public boolean isTaskSnapshotSupported() { |
| try { |
| return WindowManagerGlobal.getWindowManagerService().isTaskSnapshotSupported(); |
| } catch (RemoteException e) { |
| } |
| return false; |
| } |
| |
| @Override |
| public void registerTaskFpsCallback(@IntRange(from = 0) int taskId, @NonNull Executor executor, |
| TaskFpsCallback callback) { |
| final OnFpsCallbackListenerProxy onFpsCallbackListenerProxy = |
| new OnFpsCallbackListenerProxy(executor, callback); |
| try { |
| WindowManagerGlobal.getWindowManagerService().registerTaskFpsCallback( |
| taskId, onFpsCallbackListenerProxy); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| synchronized (mOnFpsCallbackListenerProxies) { |
| mOnFpsCallbackListenerProxies.add(onFpsCallbackListenerProxy); |
| } |
| } |
| |
| @Override |
| public void unregisterTaskFpsCallback(TaskFpsCallback callback) { |
| synchronized (mOnFpsCallbackListenerProxies) { |
| final Iterator<OnFpsCallbackListenerProxy> iterator = |
| mOnFpsCallbackListenerProxies.iterator(); |
| while (iterator.hasNext()) { |
| final OnFpsCallbackListenerProxy proxy = iterator.next(); |
| if (proxy.mCallback == callback) { |
| try { |
| WindowManagerGlobal.getWindowManagerService() |
| .unregisterTaskFpsCallback(proxy); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| iterator.remove(); |
| } |
| } |
| } |
| } |
| |
| private static class OnFpsCallbackListenerProxy |
| extends ITaskFpsCallback.Stub { |
| private final Executor mExecutor; |
| private final TaskFpsCallback mCallback; |
| |
| private OnFpsCallbackListenerProxy(Executor executor, TaskFpsCallback callback) { |
| mExecutor = executor; |
| mCallback = callback; |
| } |
| |
| @Override |
| public void onFpsReported(float fps) { |
| mExecutor.execute(() -> { |
| mCallback.onFpsReported(fps); |
| }); |
| } |
| } |
| |
| @Override |
| public Bitmap snapshotTaskForRecents(int taskId) { |
| try { |
| return WindowManagerGlobal.getWindowManagerService().snapshotTaskForRecents(taskId); |
| } catch (RemoteException e) { |
| e.rethrowAsRuntimeException(); |
| } |
| return null; |
| } |
| |
| @Override |
| @NonNull |
| public IBinder getDefaultToken() { |
| return mDefaultToken; |
| } |
| |
| @Override |
| @NonNull |
| public List<ComponentName> notifyScreenshotListeners(int displayId) { |
| try { |
| return List.copyOf(WindowManagerGlobal.getWindowManagerService() |
| .notifyScreenshotListeners(displayId)); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| @Override |
| public boolean replaceContentOnDisplayWithMirror(int displayId, @NonNull Window window) { |
| View decorView = window.peekDecorView(); |
| if (decorView == null) { |
| Log.e(TAG, "replaceContentOnDisplayWithMirror: Window's decorView was null."); |
| return false; |
| } |
| |
| ViewRootImpl viewRoot = decorView.getViewRootImpl(); |
| if (viewRoot == null) { |
| Log.e(TAG, "replaceContentOnDisplayWithMirror: Window's viewRootImpl was null."); |
| return false; |
| } |
| |
| SurfaceControl sc = viewRoot.getSurfaceControl(); |
| if (!sc.isValid()) { |
| Log.e(TAG, "replaceContentOnDisplayWithMirror: Window's SC is invalid."); |
| return false; |
| } |
| return replaceContentOnDisplayWithSc(displayId, SurfaceControl.mirrorSurface(sc)); |
| } |
| |
| @Override |
| public boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) { |
| if (!sc.isValid()) { |
| Log.e(TAG, "replaceContentOnDisplayWithSc: Invalid SC."); |
| return false; |
| } |
| |
| try { |
| return WindowManagerGlobal.getWindowManagerService() |
| .replaceContentOnDisplay(displayId, sc); |
| } catch (RemoteException e) { |
| e.rethrowAsRuntimeException(); |
| } |
| return false; |
| } |
| |
| @Override |
| public void registerTrustedPresentationListener(@NonNull IBinder window, |
| @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor, |
| @NonNull Consumer<Boolean> listener) { |
| Objects.requireNonNull(window, "window must not be null"); |
| Objects.requireNonNull(thresholds, "thresholds must not be null"); |
| Objects.requireNonNull(executor, "executor must not be null"); |
| Objects.requireNonNull(listener, "listener must not be null"); |
| mGlobal.registerTrustedPresentationListener(window, thresholds, executor, listener); |
| } |
| |
| @Override |
| public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) { |
| Objects.requireNonNull(listener, "listener must not be null"); |
| mGlobal.unregisterTrustedPresentationListener(listener); |
| } |
| |
| @NonNull |
| @Override |
| public InputTransferToken registerBatchedSurfaceControlInputReceiver( |
| @NonNull InputTransferToken hostInputTransferToken, |
| @NonNull SurfaceControl surfaceControl, @NonNull Choreographer choreographer, |
| @NonNull SurfaceControlInputReceiver receiver) { |
| Objects.requireNonNull(hostInputTransferToken); |
| Objects.requireNonNull(surfaceControl); |
| Objects.requireNonNull(choreographer); |
| Objects.requireNonNull(receiver); |
| return mGlobal.registerBatchedSurfaceControlInputReceiver(hostInputTransferToken, |
| surfaceControl, choreographer, receiver); |
| } |
| |
| @NonNull |
| @Override |
| public InputTransferToken registerUnbatchedSurfaceControlInputReceiver( |
| @NonNull InputTransferToken hostInputTransferToken, |
| @NonNull SurfaceControl surfaceControl, @NonNull Looper looper, |
| @NonNull SurfaceControlInputReceiver receiver) { |
| Objects.requireNonNull(hostInputTransferToken); |
| Objects.requireNonNull(surfaceControl); |
| Objects.requireNonNull(looper); |
| Objects.requireNonNull(receiver); |
| return mGlobal.registerUnbatchedSurfaceControlInputReceiver( |
| hostInputTransferToken, surfaceControl, looper, receiver); |
| } |
| |
| @Override |
| public void unregisterSurfaceControlInputReceiver(@NonNull SurfaceControl surfaceControl) { |
| Objects.requireNonNull(surfaceControl); |
| mGlobal.unregisterSurfaceControlInputReceiver(surfaceControl); |
| } |
| |
| @Override |
| @Nullable |
| public IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) { |
| Objects.requireNonNull(surfaceControl); |
| return mGlobal.getSurfaceControlInputClientToken(surfaceControl); |
| } |
| |
| @Override |
| public boolean transferTouchGesture(@NonNull InputTransferToken transferFromToken, |
| @NonNull InputTransferToken transferToToken) { |
| Objects.requireNonNull(transferFromToken); |
| Objects.requireNonNull(transferToToken); |
| return mGlobal.transferTouchGesture(transferFromToken, transferToToken); |
| } |
| |
| @Override |
| public @ScreenRecordingState int addScreenRecordingCallback( |
| @NonNull @CallbackExecutor Executor executor, |
| @NonNull Consumer<@ScreenRecordingState Integer> callback) { |
| if (screenRecordingCallbacks()) { |
| Objects.requireNonNull(executor, "executor must not be null"); |
| Objects.requireNonNull(callback, "callback must not be null"); |
| return ScreenRecordingCallbacks.getInstance().addCallback(executor, callback); |
| } |
| return SCREEN_RECORDING_STATE_NOT_VISIBLE; |
| } |
| |
| @Override |
| public void removeScreenRecordingCallback( |
| @NonNull Consumer<@ScreenRecordingState Integer> callback) { |
| if (screenRecordingCallbacks()) { |
| Objects.requireNonNull(callback, "callback must not be null"); |
| ScreenRecordingCallbacks.getInstance().removeCallback(callback); |
| } |
| } |
| } |