/* * Copyright (C) 2019 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 android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.content.Context; import android.graphics.PixelFormat; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.view.accessibility.IAccessibilityEmbeddedConnection; import java.util.Objects; /** * Utility class for adding a View hierarchy to a {@link SurfaceControl}. The View hierarchy * will render in to a root SurfaceControl, and receive input based on the SurfaceControl's * placement on-screen. The primary usage of this class is to embed a View hierarchy from * one process in to another. After the SurfaceControlViewHost has been set up in the embedded * content provider, we can send the {@link SurfaceControlViewHost.SurfacePackage} * to the host process. The host process can then attach the hierarchy to a SurfaceView within * its own by calling * {@link SurfaceView#setChildSurfacePackage}. */ public class SurfaceControlViewHost { private final ViewRootImpl mViewRoot; private WindowlessWindowManager mWm; private SurfaceControl mSurfaceControl; private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; /** * Package encapsulating a Surface hierarchy which contains interactive view * elements. It's expected to get this object from * {@link SurfaceControlViewHost#getSurfacePackage} afterwards it can be embedded within * a SurfaceView by calling {@link SurfaceView#setChildSurfacePackage}. * * Note that each {@link SurfacePackage} must be released by calling * {@link SurfacePackage#release}. However, if you use the recommended flow, * the framework will automatically handle the lifetime for you. * * 1. When sending the package to the remote process, return it from an AIDL method * or manually use FLAG_WRITE_RETURN_VALUE in writeToParcel. This will automatically * release the package in the local process. * 2. In the remote process, consume the package using SurfaceView. This way the * SurfaceView will take over the lifetime and call {@link SurfacePackage#release} * for the user. * * One final note: The {@link SurfacePackage} lifetime is totally de-coupled * from the lifetime of the underlying {@link SurfaceControlViewHost}. Regardless * of the lifetime of the package the user should still call * {@link SurfaceControlViewHost#release} when finished. */ public static final class SurfacePackage implements Parcelable { private SurfaceControl mSurfaceControl; private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; private final IBinder mInputToken; SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection, IBinder inputToken) { mSurfaceControl = sc; mAccessibilityEmbeddedConnection = connection; mInputToken = inputToken; } /** * Constructs a copy of {@code SurfacePackage} with an independent lifetime. * * The caller can use this to create an independent copy in situations where ownership of * the {@code SurfacePackage} would be transferred elsewhere, such as attaching to a * {@code SurfaceView}, returning as {@code Binder} result value, etc. The caller is * responsible for releasing this copy when its done. * * @param other {@code SurfacePackage} to create a copy of. */ public SurfacePackage(@NonNull SurfacePackage other) { SurfaceControl otherSurfaceControl = other.mSurfaceControl; if (otherSurfaceControl != null && otherSurfaceControl.isValid()) { mSurfaceControl = new SurfaceControl(); mSurfaceControl.copyFrom(otherSurfaceControl, "SurfacePackage"); } mAccessibilityEmbeddedConnection = other.mAccessibilityEmbeddedConnection; mInputToken = other.mInputToken; } private SurfacePackage(Parcel in) { mSurfaceControl = new SurfaceControl(); mSurfaceControl.readFromParcel(in); mAccessibilityEmbeddedConnection = IAccessibilityEmbeddedConnection.Stub.asInterface( in.readStrongBinder()); mInputToken = in.readStrongBinder(); } /** * Use {@link SurfaceView#setChildSurfacePackage} or manually fix * accessibility (see SurfaceView implementation). * @hide */ public @NonNull SurfaceControl getSurfaceControl() { return mSurfaceControl; } /** * Gets an accessibility embedded connection interface for this SurfaceControlViewHost. * * @return {@link IAccessibilityEmbeddedConnection} interface. * @hide */ public IAccessibilityEmbeddedConnection getAccessibilityEmbeddedConnection() { return mAccessibilityEmbeddedConnection; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel out, int flags) { mSurfaceControl.writeToParcel(out, flags); out.writeStrongBinder(mAccessibilityEmbeddedConnection.asBinder()); out.writeStrongBinder(mInputToken); } /** * Release the {@link SurfaceControl} associated with this package. * It's not necessary to call this if you pass the package to * {@link SurfaceView#setChildSurfacePackage} as {@link SurfaceView} will * take ownership in that case. */ public void release() { if (mSurfaceControl != null) { mSurfaceControl.release(); } mSurfaceControl = null; } /** * Returns an input token used which can be used to request focus on the embedded surface. * * @hide */ public IBinder getInputToken() { return mInputToken; } public static final @NonNull Creator CREATOR = new Creator() { public SurfacePackage createFromParcel(Parcel in) { return new SurfacePackage(in); } public SurfacePackage[] newArray(int size) { return new SurfacePackage[size]; } }; } /** @hide */ public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d, @NonNull WindowlessWindowManager wwm) { this(c, d, wwm, false /* useSfChoreographer */); } /** @hide */ public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d, @NonNull WindowlessWindowManager wwm, boolean useSfChoreographer) { mWm = wwm; mViewRoot = new ViewRootImpl(c, d, mWm, useSfChoreographer); mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection(); } /** * Construct a new SurfaceControlViewHost. The root Surface will be * allocated internally and is accessible via getSurfacePackage(). * * The {@param hostToken} parameter, primarily used for ANR reporting, * must be obtained from whomever will be hosting the embedded hierarchy. * It's accessible from {@link SurfaceView#getHostToken}. * * @param context The Context object for your activity or application. * @param display The Display the hierarchy will be placed on. * @param hostToken The host token, as discussed above. */ public SurfaceControlViewHost(@NonNull Context context, @NonNull Display display, @Nullable IBinder hostToken) { mSurfaceControl = new SurfaceControl.Builder() .setContainerLayer() .setName("SurfaceControlViewHost") .setCallsite("SurfaceControlViewHost") .build(); mWm = new WindowlessWindowManager(context.getResources().getConfiguration(), mSurfaceControl, hostToken); mViewRoot = new ViewRootImpl(context, display, mWm); mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection(); } /** * @hide */ @Override protected void finalize() throws Throwable { // We aren't on the UI thread here so we need to pass false to // doDie mViewRoot.die(false /* immediate */); } /** * Return a SurfacePackage for the root SurfaceControl of the embedded hierarchy. * Rather than be directly reparented using {@link SurfaceControl.Transaction} this * SurfacePackage should be passed to {@link SurfaceView#setChildSurfacePackage} * which will not only reparent the Surface, but ensure the accessibility hierarchies * are linked. */ public @Nullable SurfacePackage getSurfacePackage() { if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) { return new SurfacePackage(mSurfaceControl, mAccessibilityEmbeddedConnection, mViewRoot.getInputToken()); } else { return null; } } /** * Set the root view of the SurfaceControlViewHost. This view will render in to * the SurfaceControl, and receive input based on the SurfaceControls positioning on * screen. It will be laid as if it were in a window of the passed in width and height. * * @param view The View to add * @param width The width to layout the View within, in pixels. * @param height The height to layout the View within, in pixels. */ public void setView(@NonNull View view, int width, int height) { final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height, WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT); setView(view, lp); } /** * @hide */ @TestApi public void setView(@NonNull View view, @NonNull WindowManager.LayoutParams attrs) { Objects.requireNonNull(view); attrs.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; view.setLayoutParams(attrs); mViewRoot.setView(view, attrs, null); } /** * @return The view passed to setView, or null if none has been passed. */ public @Nullable View getView() { return mViewRoot.getView(); } /** * @return the ViewRootImpl wrapped by this host. * @hide */ public IWindow getWindowToken() { return mViewRoot.mWindow; } /** * @return the WindowlessWindowManager instance that this host is attached to. * @hide */ public @NonNull WindowlessWindowManager getWindowlessWM() { return mWm; } /** * @hide */ @TestApi public void relayout(WindowManager.LayoutParams attrs) { relayout(attrs, SurfaceControl.Transaction::apply); } /** * Forces relayout and draw and allows to set a custom callback when it is finished * @hide */ public void relayout(WindowManager.LayoutParams attrs, WindowlessWindowManager.ResizeCompleteCallback callback) { mViewRoot.setLayoutParams(attrs, false); mViewRoot.setReportNextDraw(); mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), callback); } /** * Modify the size of the root view. * * @param width Width in pixels * @param height Height in pixels */ public void relayout(int width, int height) { final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height, WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT); relayout(lp); } /** * Trigger the tear down of the embedded view hierarchy and release the SurfaceControl. * This will result in onDispatchedFromWindow being dispatched to the embedded view hierarchy * and render the object unusable. */ public void release() { // ViewRoot will release mSurfaceControl for us. mViewRoot.die(true /* immediate */); } }