1 /*
2  * Copyright (C) 2019 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.view;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.TestApi;
22 import android.content.Context;
23 import android.content.res.Configuration;
24 import android.graphics.PixelFormat;
25 import android.graphics.Rect;
26 import android.os.IBinder;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.os.RemoteException;
30 import android.util.Log;
31 import android.view.accessibility.IAccessibilityEmbeddedConnection;
32 import android.window.ISurfaceSyncGroup;
33 import android.window.WindowTokenClient;
34 
35 import dalvik.system.CloseGuard;
36 
37 import java.util.Objects;
38 import java.util.concurrent.CompletableFuture;
39 import java.util.concurrent.ExecutionException;
40 import java.util.concurrent.TimeUnit;
41 import java.util.concurrent.TimeoutException;
42 
43 /**
44  * Utility class for adding a View hierarchy to a {@link SurfaceControl}. The View hierarchy
45  * will render in to a root SurfaceControl, and receive input based on the SurfaceControl's
46  * placement on-screen. The primary usage of this class is to embed a View hierarchy from
47  * one process in to another. After the SurfaceControlViewHost has been set up in the embedded
48  * content provider, we can send the {@link SurfaceControlViewHost.SurfacePackage}
49  * to the host process. The host process can then attach the hierarchy to a SurfaceView within
50  * its own by calling
51  * {@link SurfaceView#setChildSurfacePackage}.
52  */
53 public class SurfaceControlViewHost {
54     private final static String TAG = "SurfaceControlViewHost";
55     private final ViewRootImpl mViewRoot;
56     private final CloseGuard mCloseGuard = CloseGuard.get();
57     private final WindowlessWindowManager mWm;
58 
59     private SurfaceControl mSurfaceControl;
60     private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
61     private boolean mReleased = false;
62 
63     private final class ISurfaceControlViewHostImpl extends ISurfaceControlViewHost.Stub {
64         @Override
onConfigurationChanged(Configuration configuration)65         public void onConfigurationChanged(Configuration configuration) {
66             if (mViewRoot == null) {
67                 return;
68             }
69             mViewRoot.mHandler.post(() -> {
70                 mWm.setConfiguration(configuration);
71                 if (mViewRoot != null) {
72                     mViewRoot.forceWmRelayout();
73                 }
74             });
75         }
76 
77         @Override
onDispatchDetachedFromWindow()78         public void onDispatchDetachedFromWindow() {
79             if (mViewRoot == null) {
80                 return;
81             }
82             mViewRoot.mHandler.post(() -> {
83                 release();
84             });
85         }
86 
87         @Override
onInsetsChanged(InsetsState state, Rect frame)88         public void onInsetsChanged(InsetsState state, Rect frame) {
89             if (mViewRoot != null) {
90                 mViewRoot.mHandler.post(() -> {
91                     mViewRoot.setOverrideInsetsFrame(frame);
92                 });
93             }
94             mWm.setInsetsState(state);
95         }
96 
97         @Override
getSurfaceSyncGroup()98         public ISurfaceSyncGroup getSurfaceSyncGroup() {
99             CompletableFuture<ISurfaceSyncGroup> surfaceSyncGroup = new CompletableFuture<>();
100             // If the call came from in process and it's already running on the UI thread, return
101             // results immediately instead of posting to the main thread. If we post to the main
102             // thread, it will block itself and the return value will always be null.
103             if (Thread.currentThread() == mViewRoot.mThread) {
104                 return mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup;
105             } else {
106                 mViewRoot.mHandler.post(
107                         () -> surfaceSyncGroup.complete(
108                                 mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup));
109             }
110             try {
111                 return surfaceSyncGroup.get(1, TimeUnit.SECONDS);
112             } catch (InterruptedException | ExecutionException | TimeoutException e) {
113                 Log.e(TAG, "Failed to get SurfaceSyncGroup for SCVH", e);
114             }
115             return null;
116         }
117 
118         @Override
attachParentInterface(@ullable ISurfaceControlViewHostParent parentInterface)119         public void attachParentInterface(@Nullable ISurfaceControlViewHostParent parentInterface) {
120             mViewRoot.mHandler.post(() -> mWm.setParentInterface(parentInterface));
121         }
122     }
123 
124     private ISurfaceControlViewHost mRemoteInterface = new ISurfaceControlViewHostImpl();
125 
126     private ViewRootImpl.ConfigChangedCallback mConfigChangedCallback;
127 
128     /**
129      * Package encapsulating a Surface hierarchy which contains interactive view
130      * elements. It's expected to get this object from
131      * {@link SurfaceControlViewHost#getSurfacePackage} afterwards it can be embedded within
132      * a SurfaceView by calling {@link SurfaceView#setChildSurfacePackage}.
133      *
134      * Note that each {@link SurfacePackage} must be released by calling
135      * {@link SurfacePackage#release}. However, if you use the recommended flow,
136      *  the framework will automatically handle the lifetime for you.
137      *
138      * 1. When sending the package to the remote process, return it from an AIDL method
139      * or manually use FLAG_WRITE_RETURN_VALUE in writeToParcel. This will automatically
140      * release the package in the local process.
141      * 2. In the remote process, consume the package using SurfaceView. This way the
142      * SurfaceView will take over the lifetime and call {@link SurfacePackage#release}
143      * for the user.
144      *
145      * One final note: The {@link SurfacePackage} lifetime is totally de-coupled
146      * from the lifetime of the underlying {@link SurfaceControlViewHost}. Regardless
147      * of the lifetime of the package the user should still call
148      * {@link SurfaceControlViewHost#release} when finished.
149      */
150     public static final class SurfacePackage implements Parcelable {
151         private SurfaceControl mSurfaceControl;
152         private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
153         private final IBinder mInputToken;
154         @NonNull
155         private final ISurfaceControlViewHost mRemoteInterface;
156 
SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection, IBinder inputToken, @NonNull ISurfaceControlViewHost ri)157         SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection,
158                 IBinder inputToken, @NonNull ISurfaceControlViewHost ri) {
159             mSurfaceControl = sc;
160             mAccessibilityEmbeddedConnection = connection;
161             mInputToken = inputToken;
162             mRemoteInterface = ri;
163         }
164 
165         /**
166          * Constructs a copy of {@code SurfacePackage} with an independent lifetime.
167          *
168          * The caller can use this to create an independent copy in situations where ownership of
169          * the {@code SurfacePackage} would be transferred elsewhere, such as attaching to a
170          * {@code SurfaceView}, returning as {@code Binder} result value, etc. The caller is
171          * responsible for releasing this copy when its done.
172          *
173          * @param other {@code SurfacePackage} to create a copy of.
174          */
SurfacePackage(@onNull SurfacePackage other)175         public SurfacePackage(@NonNull SurfacePackage other) {
176             SurfaceControl otherSurfaceControl = other.mSurfaceControl;
177             if (otherSurfaceControl != null && otherSurfaceControl.isValid()) {
178                 mSurfaceControl = new SurfaceControl(otherSurfaceControl, "SurfacePackage");
179             }
180             mAccessibilityEmbeddedConnection = other.mAccessibilityEmbeddedConnection;
181             mInputToken = other.mInputToken;
182             mRemoteInterface = other.mRemoteInterface;
183         }
184 
SurfacePackage(Parcel in)185         private SurfacePackage(Parcel in) {
186             mSurfaceControl = new SurfaceControl();
187             mSurfaceControl.readFromParcel(in);
188             mSurfaceControl.setUnreleasedWarningCallSite("SurfacePackage(Parcel)");
189             mAccessibilityEmbeddedConnection = IAccessibilityEmbeddedConnection.Stub.asInterface(
190                     in.readStrongBinder());
191             mInputToken = in.readStrongBinder();
192             mRemoteInterface = ISurfaceControlViewHost.Stub.asInterface(
193                 in.readStrongBinder());
194         }
195 
196         /**
197          * Returns the {@link android.view.SurfaceControl} associated with this SurfacePackage for
198          * cases where more control is required.
199          *
200          * @return the SurfaceControl associated with this SurfacePackage and its containing
201          *     SurfaceControlViewHost
202          */
getSurfaceControl()203         public @NonNull SurfaceControl getSurfaceControl() {
204             return mSurfaceControl;
205         }
206 
207         /**
208          * Gets an accessibility embedded connection interface for this SurfaceControlViewHost.
209          *
210          * @return {@link IAccessibilityEmbeddedConnection} interface.
211          * @hide
212          */
getAccessibilityEmbeddedConnection()213         public IAccessibilityEmbeddedConnection getAccessibilityEmbeddedConnection() {
214             return mAccessibilityEmbeddedConnection;
215         }
216 
217         /**
218          * @hide
219          */
220         @NonNull
getRemoteInterface()221         public ISurfaceControlViewHost getRemoteInterface() {
222             return mRemoteInterface;
223         }
224 
225         /**
226          * Forward a configuration to the remote SurfaceControlViewHost.
227          * This will cause View#onConfigurationChanged to be invoked on the remote
228          * end. This does not automatically cause the SurfaceControlViewHost
229          * to be resized. The root View of a SurfaceControlViewHost
230          * is more akin to a PopupWindow in that the size is user specified
231          * independent of configuration width and height.
232          *
233          * In order to receive the configuration change via
234          * {@link View#onConfigurationChanged}, the context used with the
235          * SurfaceControlViewHost and it's embedded view hierarchy must
236          * be a WindowContext obtained from {@link Context#createWindowContext}.
237          *
238          * If a regular service context is used, then your embedded view hierarchy
239          * will always perceive the global configuration.
240          *
241          * @param c The configuration to forward
242          */
notifyConfigurationChanged(@onNull Configuration c)243         public void notifyConfigurationChanged(@NonNull Configuration c) {
244             try {
245                 getRemoteInterface().onConfigurationChanged(c);
246             } catch (RemoteException e) {
247                 e.rethrowAsRuntimeException();
248             }
249         }
250 
251         /**
252          * Tear down the remote SurfaceControlViewHost and cause
253          * View#onDetachedFromWindow to be invoked on the other side.
254          */
notifyDetachedFromWindow()255         public void notifyDetachedFromWindow() {
256             try {
257                 getRemoteInterface().onDispatchDetachedFromWindow();
258             } catch (RemoteException e) {
259                 e.rethrowAsRuntimeException();
260             }
261         }
262 
263         @Override
describeContents()264         public int describeContents() {
265             return 0;
266         }
267 
268         @Override
writeToParcel(@onNull Parcel out, int flags)269         public void writeToParcel(@NonNull Parcel out, int flags) {
270             mSurfaceControl.writeToParcel(out, flags);
271             out.writeStrongBinder(mAccessibilityEmbeddedConnection.asBinder());
272             out.writeStrongBinder(mInputToken);
273             out.writeStrongBinder(mRemoteInterface.asBinder());
274         }
275 
276         /**
277          * Release the {@link SurfaceControl} associated with this package.
278          * It's not necessary to call this if you pass the package to
279          * {@link SurfaceView#setChildSurfacePackage} as {@link SurfaceView} will
280          * take ownership in that case.
281          */
release()282         public void release() {
283             if (mSurfaceControl != null) {
284                 mSurfaceControl.release();
285              }
286              mSurfaceControl = null;
287         }
288 
289         /**
290          * Returns an input token used which can be used to request focus on the embedded surface.
291          *
292          * @hide
293          */
getInputToken()294         public IBinder getInputToken() {
295             return mInputToken;
296         }
297 
298         public static final @NonNull Creator<SurfacePackage> CREATOR
299              = new Creator<SurfacePackage>() {
300                      public SurfacePackage createFromParcel(Parcel in) {
301                          return new SurfacePackage(in);
302                      }
303                      public SurfacePackage[] newArray(int size) {
304                          return new SurfacePackage[size];
305                      }
306              };
307     }
308 
309     /** @hide */
SurfaceControlViewHost(@onNull Context c, @NonNull Display d, @NonNull WindowlessWindowManager wwm, @NonNull String callsite)310     public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
311             @NonNull WindowlessWindowManager wwm, @NonNull String callsite) {
312         mSurfaceControl = wwm.mRootSurface;
313         mWm = wwm;
314         mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout());
315         mCloseGuard.openWithCallSite("release", callsite);
316         setConfigCallback(c, d);
317 
318         WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
319 
320         mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection();
321     }
322 
323     /**
324      * Construct a new SurfaceControlViewHost. The root Surface will be
325      * allocated internally and is accessible via getSurfacePackage().
326      *
327      * The {@param hostToken} parameter, primarily used for ANR reporting,
328      * must be obtained from whomever will be hosting the embedded hierarchy.
329      * It's accessible from {@link SurfaceView#getHostToken}.
330      *
331      * @param context The Context object for your activity or application.
332      * @param display The Display the hierarchy will be placed on.
333      * @param hostToken The host token, as discussed above.
334      */
SurfaceControlViewHost(@onNull Context context, @NonNull Display display, @Nullable IBinder hostToken)335     public SurfaceControlViewHost(@NonNull Context context, @NonNull Display display,
336             @Nullable IBinder hostToken) {
337         this(context, display, hostToken, "untracked");
338     }
339 
340     /**
341      * Construct a new SurfaceControlViewHost. The root Surface will be
342      * allocated internally and is accessible via getSurfacePackage().
343      *
344      * The {@param hostToken} parameter, primarily used for ANR reporting,
345      * must be obtained from whomever will be hosting the embedded hierarchy.
346      * It's accessible from {@link SurfaceView#getHostToken}.
347      *
348      * @param context The Context object for your activity or application.
349      * @param display The Display the hierarchy will be placed on.
350      * @param hostToken The host token, as discussed above.
351      * @param callsite The call site, used for tracking leakage of the host
352      * @hide
353      */
SurfaceControlViewHost(@onNull Context context, @NonNull Display display, @Nullable IBinder hostToken, @NonNull String callsite)354     public SurfaceControlViewHost(@NonNull Context context, @NonNull Display display,
355             @Nullable IBinder hostToken, @NonNull String callsite) {
356         mSurfaceControl = new SurfaceControl.Builder()
357                 .setContainerLayer()
358                 .setName("SurfaceControlViewHost")
359                 .setCallsite("SurfaceControlViewHost[" + callsite + "]")
360                 .build();
361         mWm = new WindowlessWindowManager(context.getResources().getConfiguration(),
362                 mSurfaceControl, hostToken);
363 
364         mViewRoot = new ViewRootImpl(context, display, mWm, new WindowlessWindowLayout());
365         mCloseGuard.openWithCallSite("release", callsite);
366         setConfigCallback(context, display);
367 
368         WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
369 
370         mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection();
371     }
372 
setConfigCallback(Context c, Display d)373     private void setConfigCallback(Context c, Display d) {
374         final IBinder token = c.getWindowContextToken();
375         mConfigChangedCallback = conf -> {
376             if (token instanceof WindowTokenClient) {
377                 final WindowTokenClient w = (WindowTokenClient)  token;
378                 w.onConfigurationChanged(conf, d.getDisplayId(), true);
379             }
380         };
381 
382         ViewRootImpl.addConfigCallback(mConfigChangedCallback);
383     }
384 
385     /**
386      * @hide
387      */
388     @Override
finalize()389     protected void finalize() throws Throwable {
390         if (mReleased) {
391             return;
392         }
393         if (mCloseGuard != null) {
394             mCloseGuard.warnIfOpen();
395         }
396         // We aren't on the UI thread here so we need to pass false to doDie
397         doRelease(false /* immediate */);
398     }
399 
400     /**
401      * Return a SurfacePackage for the root SurfaceControl of the embedded hierarchy.
402      * Rather than be directly reparented using {@link SurfaceControl.Transaction} this
403      * SurfacePackage should be passed to {@link SurfaceView#setChildSurfacePackage}
404      * which will not only reparent the Surface, but ensure the accessibility hierarchies
405      * are linked.
406      */
getSurfacePackage()407     public @Nullable SurfacePackage getSurfacePackage() {
408         if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) {
409             return new SurfacePackage(new SurfaceControl(mSurfaceControl, "getSurfacePackage"),
410                 mAccessibilityEmbeddedConnection, getFocusGrantToken(), mRemoteInterface);
411         } else {
412             return null;
413         }
414     }
415 
416     /**
417      * @hide
418      */
getRootSurfaceControl()419     public @NonNull AttachedSurfaceControl getRootSurfaceControl() {
420         return mViewRoot;
421     }
422 
423     /**
424      * Set the root view of the SurfaceControlViewHost. This view will render in to
425      * the SurfaceControl, and receive input based on the SurfaceControls positioning on
426      * screen. It will be laid as if it were in a window of the passed in width and height.
427      *
428      * @param view The View to add
429      * @param width The width to layout the View within, in pixels.
430      * @param height The height to layout the View within, in pixels.
431      */
setView(@onNull View view, int width, int height)432     public void setView(@NonNull View view, int width, int height) {
433         final WindowManager.LayoutParams lp =
434                 new WindowManager.LayoutParams(width, height,
435                         WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT);
436         setView(view, lp);
437     }
438 
439     /**
440      * @hide
441      */
442     @TestApi
setView(@onNull View view, @NonNull WindowManager.LayoutParams attrs)443     public void setView(@NonNull View view, @NonNull WindowManager.LayoutParams attrs) {
444         Objects.requireNonNull(view);
445         attrs.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
446         addWindowToken(attrs);
447         view.setLayoutParams(attrs);
448         mViewRoot.setView(view, attrs, null);
449     }
450 
451     /**
452      * @return The view passed to setView, or null if none has been passed.
453      */
getView()454     public @Nullable View getView() {
455         return mViewRoot.getView();
456     }
457 
458     /**
459      * @return the ViewRootImpl wrapped by this host.
460      * @hide
461      */
getWindowToken()462     public IWindow getWindowToken() {
463         return mViewRoot.mWindow;
464     }
465 
466     /**
467      * @return the WindowlessWindowManager instance that this host is attached to.
468      * @hide
469      */
getWindowlessWM()470     public @NonNull WindowlessWindowManager getWindowlessWM() {
471         return mWm;
472     }
473 
474     /**
475      * Forces relayout and draw and allows to set a custom callback when it is finished
476      * @hide
477      */
relayout(WindowManager.LayoutParams attrs, WindowlessWindowManager.ResizeCompleteCallback callback)478     public void relayout(WindowManager.LayoutParams attrs,
479             WindowlessWindowManager.ResizeCompleteCallback callback) {
480         mViewRoot.setLayoutParams(attrs, false);
481         mViewRoot.setReportNextDraw(true /* syncBuffer */, "scvh_relayout");
482         mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), callback);
483     }
484 
485     /**
486      * @hide
487      */
488     @TestApi
relayout(WindowManager.LayoutParams attrs)489     public void relayout(WindowManager.LayoutParams attrs) {
490         mViewRoot.setLayoutParams(attrs, false);
491     }
492 
493     /**
494      * Modify the size of the root view.
495      *
496      * @param width Width in pixels
497      * @param height Height in pixels
498      */
relayout(int width, int height)499     public void relayout(int width, int height) {
500         final WindowManager.LayoutParams lp =
501                 new WindowManager.LayoutParams(width, height,
502                         WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT);
503         relayout(lp);
504     }
505 
506     /**
507      * Trigger the tear down of the embedded view hierarchy and release the SurfaceControl.
508      * This will result in onDispatchedFromWindow being dispatched to the embedded view hierarchy
509      * and render the object unusable.
510      */
release()511     public void release() {
512         // ViewRoot will release mSurfaceControl for us.
513         doRelease(true /* immediate */);
514     }
515 
doRelease(boolean immediate)516     private void doRelease(boolean immediate) {
517         if (mConfigChangedCallback != null) {
518             ViewRootImpl.removeConfigCallback(mConfigChangedCallback);
519             mConfigChangedCallback = null;
520         }
521 
522         mViewRoot.die(immediate);
523         WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot);
524         mReleased = true;
525         mCloseGuard.close();
526     }
527 
528     /**
529      * @hide
530      */
getFocusGrantToken()531     public IBinder getFocusGrantToken() {
532         return mWm.getFocusGrantToken(getWindowToken().asBinder());
533     }
534 
addWindowToken(WindowManager.LayoutParams attrs)535     private void addWindowToken(WindowManager.LayoutParams attrs) {
536         final WindowManagerImpl wm =
537                 (WindowManagerImpl) mViewRoot.mContext.getSystemService(Context.WINDOW_SERVICE);
538         attrs.token = wm.getDefaultToken();
539     }
540 
541     /**
542      * Transfer the currently in progress touch gesture to the parent
543      * (if any) of this SurfaceControlViewHost. This requires that the
544      * SurfaceControlViewHost was created with an associated hostInputToken.
545      *
546      * @return Whether the touch stream was transferred.
547      */
transferTouchGestureToHost()548     public boolean transferTouchGestureToHost() {
549         if (mViewRoot == null) {
550             return false;
551         }
552 
553         final IWindowSession realWm = WindowManagerGlobal.getWindowSession();
554         try {
555             return realWm.transferEmbeddedTouchFocusToHost(mViewRoot.mWindow);
556         } catch (RemoteException e) {
557             e.rethrowAsRuntimeException();
558         }
559         return false;
560     }
561 }
562