blob: 406281d4cade44ca00d0aeb687e3e96238920bf5 [file] [log] [blame]
/*
* 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.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
import android.view.InsetsState;
import android.view.WindowManagerGlobal;
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 static String TAG = "SurfaceControlViewHost";
private final ViewRootImpl mViewRoot;
private WindowlessWindowManager mWm;
private SurfaceControl mSurfaceControl;
private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
private boolean mReleased = false;
private final class ISurfaceControlViewHostImpl extends ISurfaceControlViewHost.Stub {
@Override
public void onConfigurationChanged(Configuration configuration) {
if (mViewRoot == null) {
return;
}
mViewRoot.mHandler.post(() -> {
if (mWm != null) {
mWm.setConfiguration(configuration);
}
if (mViewRoot != null) {
mViewRoot.forceWmRelayout();
}
});
}
@Override
public void onDispatchDetachedFromWindow() {
if (mViewRoot == null) {
return;
}
mViewRoot.mHandler.post(() -> {
release();
});
}
@Override
public void onInsetsChanged(InsetsState state, Rect frame) {
if (mViewRoot != null) {
mViewRoot.mHandler.post(() -> {
mViewRoot.setOverrideInsetsFrame(frame);
});
}
mWm.setInsetsState(state);
}
}
private ISurfaceControlViewHost mRemoteInterface = new ISurfaceControlViewHostImpl();
/**
* 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;
private final ISurfaceControlViewHost mRemoteInterface;
SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection,
IBinder inputToken, ISurfaceControlViewHost ri) {
mSurfaceControl = sc;
mAccessibilityEmbeddedConnection = connection;
mInputToken = inputToken;
mRemoteInterface = ri;
}
/**
* 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;
mRemoteInterface = other.mRemoteInterface;
}
private SurfacePackage(Parcel in) {
mSurfaceControl = new SurfaceControl();
mSurfaceControl.readFromParcel(in);
mAccessibilityEmbeddedConnection = IAccessibilityEmbeddedConnection.Stub.asInterface(
in.readStrongBinder());
mInputToken = in.readStrongBinder();
mRemoteInterface = ISurfaceControlViewHost.Stub.asInterface(
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;
}
/**
* @hide
*/
public ISurfaceControlViewHost getRemoteInterface() {
return mRemoteInterface;
}
/**
* Forward a configuration to the remote SurfaceControlViewHost.
* This will cause View#onConfigurationChanged to be invoked on the remote
* end. This does not automatically cause the SurfaceControlViewHost
* to be resized. The root View of a SurfaceControlViewHost
* is more akin to a PopupWindow in that the size is user specified
* independent of configuration width and height.
*
* @param c The configuration to forward
*/
public void notifyConfigurationChanged(@NonNull Configuration c) {
try {
getRemoteInterface().onConfigurationChanged(c);
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
}
/**
* Tear down the remote SurfaceControlViewHost and cause
* View#onDetachedFromWindow to be invoked on the other side.
*/
public void notifyDetachedFromWindow() {
try {
getRemoteInterface().onDispatchDetachedFromWindow();
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
}
@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);
out.writeStrongBinder(mRemoteInterface.asBinder());
}
/**
* 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<SurfacePackage> CREATOR
= new Creator<SurfacePackage>() {
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);
WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
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);
WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection();
}
/**
* @hide
*/
@Override
protected void finalize() throws Throwable {
if (mReleased) {
return;
}
Log.e(TAG, "SurfaceControlViewHost finalized without being released: " + this);
// We aren't on the UI thread here so we need to pass false to doDie
mViewRoot.die(false /* immediate */);
WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot);
}
/**
* 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(new SurfaceControl(mSurfaceControl, "getSurfacePackage"),
mAccessibilityEmbeddedConnection,
mWm.getFocusGrantToken(), mRemoteInterface);
} 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(true /* syncBuffer */);
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 */);
WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot);
mReleased = true;
}
/**
* @hide
*/
public IBinder getFocusGrantToken() {
return mWm.getFocusGrantToken();
}
}