1 /* 2 * Copyright (C) 2020 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.widget.inline; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.TestApi; 22 import android.content.Context; 23 import android.graphics.PixelFormat; 24 import android.graphics.PointF; 25 import android.graphics.Rect; 26 import android.util.AttributeSet; 27 import android.util.Log; 28 import android.view.SurfaceControl; 29 import android.view.SurfaceControlViewHost; 30 import android.view.SurfaceHolder; 31 import android.view.SurfaceView; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.view.ViewTreeObserver; 35 36 import java.lang.ref.WeakReference; 37 import java.util.function.Consumer; 38 39 /** 40 * This class represents a view that holds opaque content from another app that you can inline in 41 * your UI. 42 * 43 * <p>Since the content presented by this view is from another security domain,it is 44 * shown on a remote surface preventing the host application from accessing that content. Also the 45 * host application cannot interact with the inlined content by injecting touch events or clicking 46 * programmatically. 47 * 48 * <p>This view can be overlaid by other windows, i.e. redressed, but if this is the case 49 * the inlined UI would not be interactive. Sometimes this is desirable, e.g. animating transitions. 50 * 51 * <p>By default the surface backing this view is shown on top of the hosting window such 52 * that the inlined content is interactive. However, you can temporarily move the surface under the 53 * hosting window which could be useful in some cases, e.g. animating transitions. At this point the 54 * inlined content will not be interactive and the touch events would be delivered to your app. 55 * 56 * <p> Instances of this class are created by the platform and can be programmatically attached to 57 * your UI. Once the view is attached to the window, you may detach and reattach it to the window. 58 * It should work seamlessly from the hosting process's point of view. 59 */ 60 public class InlineContentView extends ViewGroup { 61 62 private static final String TAG = "InlineContentView"; 63 64 private static final boolean DEBUG = false; 65 66 /** 67 * Callback for observing the lifecycle of the surface control that manipulates the backing 68 * secure embedded UI surface. 69 */ 70 public interface SurfaceControlCallback { 71 /** 72 * Called when the backing surface is being created. 73 * 74 * @param surfaceControl The surface control to manipulate the surface. 75 */ onCreated(@onNull SurfaceControl surfaceControl)76 void onCreated(@NonNull SurfaceControl surfaceControl); 77 78 /** 79 * Called when the backing surface is being destroyed. 80 * 81 * @param surfaceControl The surface control to manipulate the surface. 82 */ onDestroyed(@onNull SurfaceControl surfaceControl)83 void onDestroyed(@NonNull SurfaceControl surfaceControl); 84 } 85 86 /** 87 * Callback for sending an updated surface package in case the previous one is released 88 * from the detached from window event, and for getting notified of such event. 89 * 90 * This is expected to be provided to the {@link InlineContentView} so it can get updates 91 * from and send updates to the remote content (i.e. surface package) provider. 92 * 93 * @hide 94 */ 95 @TestApi 96 public interface SurfacePackageUpdater { 97 98 99 /** 100 * Called when the previous surface package is released due to view being detached 101 * from the window. 102 */ onSurfacePackageReleased()103 void onSurfacePackageReleased(); 104 105 /** 106 * Called to request an updated surface package. 107 * 108 * @param consumer consumes the updated surface package. 109 */ getSurfacePackage(@onNull Consumer<SurfaceControlViewHost.SurfacePackage> consumer)110 void getSurfacePackage(@NonNull Consumer<SurfaceControlViewHost.SurfacePackage> consumer); 111 } 112 113 @NonNull 114 private final SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() { 115 @Override 116 public void surfaceCreated(@NonNull SurfaceHolder holder) { 117 final SurfaceControl surfaceControl = mSurfaceView.getSurfaceControl(); 118 surfaceControl.addOnReparentListener(mOnReparentListener); 119 mSurfaceControlCallback.onCreated(surfaceControl); 120 } 121 122 @Override 123 public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, 124 int height) { 125 /* do nothing */ 126 } 127 128 @Override 129 public void surfaceDestroyed(@NonNull SurfaceHolder holder) { 130 final SurfaceControl surfaceControl = mSurfaceView.getSurfaceControl(); 131 surfaceControl.removeOnReparentListener(mOnReparentListener); 132 mSurfaceControlCallback.onDestroyed(surfaceControl); 133 } 134 }; 135 136 @NonNull 137 private final SurfaceControl.OnReparentListener mOnReparentListener = 138 new SurfaceControl.OnReparentListener() { 139 @Override 140 public void onReparent(SurfaceControl.Transaction transaction, 141 SurfaceControl parent) { 142 final View parentSurfaceOwnerView = (parent != null) 143 ? parent.getLocalOwnerView() : null; 144 if (parentSurfaceOwnerView instanceof SurfaceView) { 145 mParentSurfaceOwnerView = new WeakReference<>( 146 (SurfaceView) parentSurfaceOwnerView); 147 } else { 148 mParentSurfaceOwnerView = null; 149 } 150 } 151 }; 152 153 @NonNull 154 private final ViewTreeObserver.OnDrawListener mOnDrawListener = 155 new ViewTreeObserver.OnDrawListener() { 156 @Override 157 public void onDraw() { 158 computeParentPositionAndScale(); 159 final int visibility = InlineContentView.this.isShown() ? VISIBLE : GONE; 160 mSurfaceView.setVisibility(visibility); 161 } 162 }; 163 164 @NonNull 165 private final SurfaceView mSurfaceView; 166 167 @Nullable 168 private WeakReference<SurfaceView> mParentSurfaceOwnerView; 169 170 @Nullable 171 private int[] mParentPosition; 172 173 @Nullable 174 private PointF mParentScale; 175 176 @Nullable 177 private SurfaceControlCallback mSurfaceControlCallback; 178 179 @Nullable 180 private SurfacePackageUpdater mSurfacePackageUpdater; 181 182 /** 183 * @inheritDoc 184 * @hide 185 */ InlineContentView(@onNull Context context)186 public InlineContentView(@NonNull Context context) { 187 this(context, null); 188 } 189 190 /** 191 * @inheritDoc 192 * @hide 193 */ InlineContentView(@onNull Context context, @Nullable AttributeSet attrs)194 public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs) { 195 this(context, attrs, 0); 196 } 197 198 /** 199 * @inheritDoc 200 * @hide 201 */ InlineContentView(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr)202 public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs, 203 int defStyleAttr) { 204 this(context, attrs, defStyleAttr, 0); 205 mSurfaceView.setEnableSurfaceClipping(true); 206 } 207 208 /** 209 * Gets the surface control. If the surface is not created this method returns {@code null}. 210 * 211 * @return The surface control. 212 * @see #setSurfaceControlCallback(SurfaceControlCallback) 213 */ 214 @Nullable getSurfaceControl()215 public SurfaceControl getSurfaceControl() { 216 return mSurfaceView.getSurfaceControl(); 217 } 218 219 @Override setClipBounds(Rect clipBounds)220 public void setClipBounds(Rect clipBounds) { 221 super.setClipBounds(clipBounds); 222 mSurfaceView.setClipBounds(clipBounds); 223 } 224 225 /** 226 * @inheritDoc 227 * @hide 228 */ InlineContentView(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)229 public InlineContentView(@NonNull Context context, @Nullable AttributeSet attrs, 230 int defStyleAttr, int defStyleRes) { 231 super(context, attrs, defStyleAttr, defStyleRes); 232 mSurfaceView = new SurfaceView(context, attrs, defStyleAttr, defStyleRes) { 233 @Override 234 protected void onSetSurfacePositionAndScaleRT( 235 @NonNull SurfaceControl.Transaction transaction, 236 @NonNull SurfaceControl surface, int positionLeft, int positionTop, 237 float postScaleX, float postScaleY) { 238 // If we have a parent position, we need to make our coordinates relative 239 // to the parent in the rendering space. 240 if (mParentPosition != null) { 241 positionLeft = (int) ((positionLeft - mParentPosition[0]) / mParentScale.x); 242 positionTop = (int) ((positionTop - mParentPosition[1]) / mParentScale.y); 243 } 244 245 // Any scaling done to the parent or its predecessors would be applied 246 // via the surfaces parent -> child relation, so we only propagate any 247 // scaling set on the InlineContentView itself. 248 postScaleX = InlineContentView.this.getScaleX(); 249 postScaleY = InlineContentView.this.getScaleY(); 250 251 super.onSetSurfacePositionAndScaleRT(transaction, surface, positionLeft, 252 positionTop, postScaleX, postScaleY); 253 } 254 }; 255 mSurfaceView.setZOrderOnTop(true); 256 mSurfaceView.getHolder().setFormat(PixelFormat.TRANSPARENT); 257 addView(mSurfaceView); 258 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); 259 } 260 261 /** 262 * Sets the embedded UI provider. 263 * 264 * @hide 265 */ 266 @TestApi setChildSurfacePackageUpdater( @ullable SurfacePackageUpdater surfacePackageUpdater)267 public void setChildSurfacePackageUpdater( 268 @Nullable SurfacePackageUpdater surfacePackageUpdater) { 269 mSurfacePackageUpdater = surfacePackageUpdater; 270 } 271 272 @Override onAttachedToWindow()273 protected void onAttachedToWindow() { 274 if (DEBUG) Log.v(TAG, "onAttachedToWindow"); 275 super.onAttachedToWindow(); 276 if (mSurfacePackageUpdater != null) { 277 mSurfacePackageUpdater.getSurfacePackage( 278 sp -> { 279 if (DEBUG) Log.v(TAG, "Received new SurfacePackage"); 280 if (getViewRootImpl() != null) { 281 mSurfaceView.setChildSurfacePackage(sp); 282 } 283 }); 284 } 285 286 mSurfaceView.setVisibility(getVisibility()); 287 getViewTreeObserver().addOnDrawListener(mOnDrawListener); 288 } 289 290 @Override onDetachedFromWindow()291 protected void onDetachedFromWindow() { 292 if (DEBUG) Log.v(TAG, "onDetachedFromWindow"); 293 super.onDetachedFromWindow(); 294 if (mSurfacePackageUpdater != null) { 295 mSurfacePackageUpdater.onSurfacePackageReleased(); 296 } 297 298 getViewTreeObserver().removeOnDrawListener(mOnDrawListener); 299 mSurfaceView.setVisibility(View.GONE); 300 } 301 302 @Override onLayout(boolean changed, int l, int t, int r, int b)303 public void onLayout(boolean changed, int l, int t, int r, int b) { 304 mSurfaceView.layout(0, 0, getMeasuredWidth(), getMeasuredHeight()); 305 } 306 307 /** 308 * Sets a callback to observe the lifecycle of the surface control for managing the backing 309 * surface. 310 * 311 * @param callback The callback to set or {@code null} to clear. 312 */ setSurfaceControlCallback(@ullable SurfaceControlCallback callback)313 public void setSurfaceControlCallback(@Nullable SurfaceControlCallback callback) { 314 if (mSurfaceControlCallback != null) { 315 mSurfaceView.getHolder().removeCallback(mSurfaceCallback); 316 } 317 mSurfaceControlCallback = callback; 318 if (mSurfaceControlCallback != null) { 319 mSurfaceView.getHolder().addCallback(mSurfaceCallback); 320 } 321 } 322 323 /** 324 * @return Whether the surface backing this view appears on top of its parent. 325 * @see #setZOrderedOnTop(boolean) 326 */ isZOrderedOnTop()327 public boolean isZOrderedOnTop() { 328 return mSurfaceView.isZOrderedOnTop(); 329 } 330 331 /** 332 * Controls whether the backing surface is placed on top of this view's window. Normally, it is 333 * placed on top of the window, to allow interaction with the inlined UI. Via this method, you 334 * can place the surface below the window. This means that all of the contents of the window 335 * this view is in will be visible on top of its surface. 336 * 337 * <p> The Z ordering can be changed dynamically if the backing surface is 338 * created, otherwise the ordering would be applied at surface construction time. 339 * 340 * @param onTop Whether to show the surface on top of this view's window. 341 * @see #isZOrderedOnTop() 342 */ setZOrderedOnTop(boolean onTop)343 public boolean setZOrderedOnTop(boolean onTop) { 344 return mSurfaceView.setZOrderedOnTop(onTop, /*allowDynamicChange*/ true); 345 } 346 347 computeParentPositionAndScale()348 private void computeParentPositionAndScale() { 349 boolean contentPositionOrScaleChanged = false; 350 351 // This method can be called on the UI or render thread but for the cases 352 // it is called these threads are not running concurrently, so no need to lock. 353 final SurfaceView parentSurfaceOwnerView = (mParentSurfaceOwnerView != null) 354 ? mParentSurfaceOwnerView.get() : null; 355 356 if (parentSurfaceOwnerView != null) { 357 if (mParentPosition == null) { 358 mParentPosition = new int[2]; 359 } 360 final int oldParentPositionX = mParentPosition[0]; 361 final int oldParentPositionY = mParentPosition[1]; 362 parentSurfaceOwnerView.getLocationInSurface(mParentPosition); 363 if (oldParentPositionX != mParentPosition[0] 364 || oldParentPositionY != mParentPosition[1]) { 365 contentPositionOrScaleChanged = true; 366 } 367 368 if (mParentScale == null) { 369 mParentScale = new PointF(); 370 } 371 372 final float lastParentSurfaceWidth = parentSurfaceOwnerView 373 .getSurfaceRenderPosition().width(); 374 final float oldParentScaleX = mParentScale.x; 375 if (lastParentSurfaceWidth > 0) { 376 mParentScale.x = lastParentSurfaceWidth / 377 (float) parentSurfaceOwnerView.getWidth(); 378 } else { 379 mParentScale.x = 1.0f; 380 } 381 if (!contentPositionOrScaleChanged 382 && Float.compare(oldParentScaleX, mParentScale.x) != 0) { 383 contentPositionOrScaleChanged = true; 384 } 385 386 final float lastParentSurfaceHeight = parentSurfaceOwnerView 387 .getSurfaceRenderPosition().height(); 388 final float oldParentScaleY = mParentScale.y; 389 if (lastParentSurfaceHeight > 0) { 390 mParentScale.y = lastParentSurfaceHeight 391 / (float) parentSurfaceOwnerView.getHeight(); 392 } else { 393 mParentScale.y = 1.0f; 394 } 395 if (!contentPositionOrScaleChanged 396 && Float.compare(oldParentScaleY, mParentScale.y) != 0) { 397 contentPositionOrScaleChanged = true; 398 } 399 } else if (mParentPosition != null || mParentScale != null) { 400 contentPositionOrScaleChanged = true; 401 mParentPosition = null; 402 mParentScale = null; 403 } 404 405 if (contentPositionOrScaleChanged) { 406 mSurfaceView.requestUpdateSurfacePositionAndScale(); 407 } 408 } 409 } 410