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