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 // b/219807628 234 @Override 235 protected void onSetSurfacePositionAndScale( 236 @NonNull SurfaceControl.Transaction transaction, 237 @NonNull SurfaceControl surface, int positionLeft, int positionTop, 238 float postScaleX, float postScaleY) { 239 // If we have a parent position, we need to make our coordinates relative 240 // to the parent in the rendering space. 241 if (mParentPosition != null) { 242 positionLeft = (int) ((positionLeft - mParentPosition[0]) / mParentScale.x); 243 positionTop = (int) ((positionTop - mParentPosition[1]) / mParentScale.y); 244 } 245 246 // Any scaling done to the parent or its predecessors would be applied 247 // via the surfaces parent -> child relation, so we only propagate any 248 // scaling set on the InlineContentView itself. 249 postScaleX = InlineContentView.this.getScaleX(); 250 postScaleY = InlineContentView.this.getScaleY(); 251 252 super.onSetSurfacePositionAndScale(transaction, surface, positionLeft, 253 positionTop, postScaleX, postScaleY); 254 } 255 }; 256 mSurfaceView.setZOrderOnTop(true); 257 mSurfaceView.getHolder().setFormat(PixelFormat.TRANSPARENT); 258 addView(mSurfaceView); 259 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); 260 } 261 262 /** 263 * Sets the embedded UI provider. 264 * 265 * @hide 266 */ 267 @TestApi setChildSurfacePackageUpdater( @ullable SurfacePackageUpdater surfacePackageUpdater)268 public void setChildSurfacePackageUpdater( 269 @Nullable SurfacePackageUpdater surfacePackageUpdater) { 270 mSurfacePackageUpdater = surfacePackageUpdater; 271 } 272 273 @Override onAttachedToWindow()274 protected void onAttachedToWindow() { 275 if (DEBUG) Log.v(TAG, "onAttachedToWindow"); 276 super.onAttachedToWindow(); 277 if (mSurfacePackageUpdater != null) { 278 mSurfacePackageUpdater.getSurfacePackage( 279 sp -> { 280 if (DEBUG) Log.v(TAG, "Received new SurfacePackage"); 281 if (getViewRootImpl() != null) { 282 mSurfaceView.setChildSurfacePackage(sp); 283 } 284 }); 285 } 286 287 mSurfaceView.setVisibility(getVisibility()); 288 getViewTreeObserver().addOnDrawListener(mOnDrawListener); 289 } 290 291 @Override onDetachedFromWindow()292 protected void onDetachedFromWindow() { 293 if (DEBUG) Log.v(TAG, "onDetachedFromWindow"); 294 super.onDetachedFromWindow(); 295 if (mSurfacePackageUpdater != null) { 296 mSurfacePackageUpdater.onSurfacePackageReleased(); 297 } 298 299 getViewTreeObserver().removeOnDrawListener(mOnDrawListener); 300 mSurfaceView.setVisibility(View.GONE); 301 } 302 303 @Override onLayout(boolean changed, int l, int t, int r, int b)304 public void onLayout(boolean changed, int l, int t, int r, int b) { 305 mSurfaceView.layout(0, 0, getMeasuredWidth(), getMeasuredHeight()); 306 } 307 308 /** 309 * Sets a callback to observe the lifecycle of the surface control for managing the backing 310 * surface. 311 * 312 * @param callback The callback to set or {@code null} to clear. 313 */ setSurfaceControlCallback(@ullable SurfaceControlCallback callback)314 public void setSurfaceControlCallback(@Nullable SurfaceControlCallback callback) { 315 if (mSurfaceControlCallback != null) { 316 mSurfaceView.getHolder().removeCallback(mSurfaceCallback); 317 } 318 mSurfaceControlCallback = callback; 319 if (mSurfaceControlCallback != null) { 320 mSurfaceView.getHolder().addCallback(mSurfaceCallback); 321 } 322 } 323 324 /** 325 * @return Whether the surface backing this view appears on top of its parent. 326 * @see #setZOrderedOnTop(boolean) 327 */ isZOrderedOnTop()328 public boolean isZOrderedOnTop() { 329 return mSurfaceView.isZOrderedOnTop(); 330 } 331 332 /** 333 * Controls whether the backing surface is placed on top of this view's window. Normally, it is 334 * placed on top of the window, to allow interaction with the inlined UI. Via this method, you 335 * can place the surface below the window. This means that all of the contents of the window 336 * this view is in will be visible on top of its surface. 337 * 338 * <p> The Z ordering can be changed dynamically if the backing surface is 339 * created, otherwise the ordering would be applied at surface construction time. 340 * 341 * @param onTop Whether to show the surface on top of this view's window. 342 * @see #isZOrderedOnTop() 343 */ setZOrderedOnTop(boolean onTop)344 public boolean setZOrderedOnTop(boolean onTop) { 345 return mSurfaceView.setZOrderedOnTop(onTop, /*allowDynamicChange*/ true); 346 } 347 348 computeParentPositionAndScale()349 private void computeParentPositionAndScale() { 350 boolean contentPositionOrScaleChanged = false; 351 352 // This method can be called on the UI or render thread but for the cases 353 // it is called these threads are not running concurrently, so no need to lock. 354 final SurfaceView parentSurfaceOwnerView = (mParentSurfaceOwnerView != null) 355 ? mParentSurfaceOwnerView.get() : null; 356 357 if (parentSurfaceOwnerView != null) { 358 if (mParentPosition == null) { 359 mParentPosition = new int[2]; 360 } 361 final int oldParentPositionX = mParentPosition[0]; 362 final int oldParentPositionY = mParentPosition[1]; 363 parentSurfaceOwnerView.getLocationInSurface(mParentPosition); 364 if (oldParentPositionX != mParentPosition[0] 365 || oldParentPositionY != mParentPosition[1]) { 366 contentPositionOrScaleChanged = true; 367 } 368 369 if (mParentScale == null) { 370 mParentScale = new PointF(); 371 } 372 373 final float lastParentSurfaceWidth = parentSurfaceOwnerView 374 .getSurfaceRenderPosition().width(); 375 final float oldParentScaleX = mParentScale.x; 376 if (lastParentSurfaceWidth > 0) { 377 mParentScale.x = lastParentSurfaceWidth / 378 (float) parentSurfaceOwnerView.getWidth(); 379 } else { 380 mParentScale.x = 1.0f; 381 } 382 if (!contentPositionOrScaleChanged 383 && Float.compare(oldParentScaleX, mParentScale.x) != 0) { 384 contentPositionOrScaleChanged = true; 385 } 386 387 final float lastParentSurfaceHeight = parentSurfaceOwnerView 388 .getSurfaceRenderPosition().height(); 389 final float oldParentScaleY = mParentScale.y; 390 if (lastParentSurfaceHeight > 0) { 391 mParentScale.y = lastParentSurfaceHeight 392 / (float) parentSurfaceOwnerView.getHeight(); 393 } else { 394 mParentScale.y = 1.0f; 395 } 396 if (!contentPositionOrScaleChanged 397 && Float.compare(oldParentScaleY, mParentScale.y) != 0) { 398 contentPositionOrScaleChanged = true; 399 } 400 } else if (mParentPosition != null || mParentScale != null) { 401 contentPositionOrScaleChanged = true; 402 mParentPosition = null; 403 mParentScale = null; 404 } 405 406 if (contentPositionOrScaleChanged) { 407 mSurfaceView.requestUpdateSurfacePositionAndScale(); 408 } 409 } 410 } 411