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.window; 18 19 import static android.view.Display.INVALID_DISPLAY; 20 21 import android.annotation.CallSuper; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.app.ActivityOptions; 25 import android.app.ActivityTaskManager; 26 import android.app.IActivityTaskManager; 27 import android.app.PendingIntent; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.pm.LauncherApps; 32 import android.content.pm.ShortcutInfo; 33 import android.graphics.Insets; 34 import android.graphics.Matrix; 35 import android.graphics.Point; 36 import android.graphics.Rect; 37 import android.graphics.Region; 38 import android.hardware.display.VirtualDisplay; 39 import android.os.RemoteException; 40 import android.os.UserHandle; 41 import android.view.IWindow; 42 import android.view.IWindowManager; 43 import android.view.IWindowSession; 44 import android.view.KeyEvent; 45 import android.view.SurfaceControl; 46 import android.view.WindowManagerGlobal; 47 48 import dalvik.system.CloseGuard; 49 50 /** 51 * A component which handles embedded display of tasks within another window. The embedded task can 52 * be presented using the SurfaceControl provided from {@link #getSurfaceControl()}. 53 * 54 * @hide 55 */ 56 public abstract class TaskEmbedder { 57 private static final String TAG = "TaskEmbedder"; 58 59 /** 60 * A component which will host the task. 61 */ 62 public interface Host { 63 /** @return the screen area where touches should be dispatched to the embedded Task */ getTapExcludeRegion()64 Region getTapExcludeRegion(); 65 66 /** @return a matrix which transforms from screen-space to the embedded task surface */ getScreenToTaskMatrix()67 Matrix getScreenToTaskMatrix(); 68 69 /** @return the window containing the parent surface, if attached and available */ getWindow()70 @Nullable IWindow getWindow(); 71 72 /** @return the x/y offset from the origin of the window to the surface */ getPositionInWindow()73 Point getPositionInWindow(); 74 75 /** @return the screen bounds of the host */ getScreenBounds()76 Rect getScreenBounds(); 77 78 /** @return whether this surface is able to receive pointer events */ canReceivePointerEvents()79 boolean canReceivePointerEvents(); 80 81 /** @return the width of the container for the embedded task */ getWidth()82 int getWidth(); 83 84 /** @return the height of the container for the embedded task */ getHeight()85 int getHeight(); 86 87 /** 88 * Called to inform the host of the task's background color. This can be used to 89 * fill unpainted areas if necessary. 90 */ onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor)91 void onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor); 92 93 /** 94 * Posts a runnable to be run on the host's handler. 95 */ post(Runnable r)96 boolean post(Runnable r); 97 } 98 99 /** 100 * Describes changes to the state of the TaskEmbedder as well the tasks within. 101 */ 102 public interface Listener { 103 /** Called when the container is ready for launching activities. */ onInitialized()104 default void onInitialized() {} 105 106 /** Called when the container can no longer launch activities. */ onReleased()107 default void onReleased() {} 108 109 /** Called when a task is created inside the container. */ onTaskCreated(int taskId, ComponentName name)110 default void onTaskCreated(int taskId, ComponentName name) {} 111 112 /** Called when a task visibility changes. */ onTaskVisibilityChanged(int taskId, boolean visible)113 default void onTaskVisibilityChanged(int taskId, boolean visible) {} 114 115 /** Called when a task is moved to the front of the stack inside the container. */ onTaskMovedToFront(int taskId)116 default void onTaskMovedToFront(int taskId) {} 117 118 /** Called when a task is about to be removed from the stack inside the container. */ onTaskRemovalStarted(int taskId)119 default void onTaskRemovalStarted(int taskId) {} 120 121 /** Called when a task is created inside the container. */ onBackPressedOnTaskRoot(int taskId)122 default void onBackPressedOnTaskRoot(int taskId) {} 123 } 124 125 protected IActivityTaskManager mActivityTaskManager = ActivityTaskManager.getService(); 126 127 protected final Context mContext; 128 protected TaskEmbedder.Host mHost; 129 130 protected SurfaceControl.Transaction mTransaction; 131 protected SurfaceControl mSurfaceControl; 132 protected Listener mListener; 133 protected boolean mOpened; // Protected by mGuard. 134 135 private final CloseGuard mGuard = CloseGuard.get(); 136 137 138 /** 139 * Constructs a new TaskEmbedder. 140 * 141 * @param context the context 142 * @param host the host for this embedded task 143 */ TaskEmbedder(Context context, TaskEmbedder.Host host)144 public TaskEmbedder(Context context, TaskEmbedder.Host host) { 145 mContext = context; 146 mHost = host; 147 } 148 149 /** 150 * Initialize this container when the ActivityView's SurfaceView is first created. 151 * 152 * @param parent the surface control for the parent surface 153 * @return true if initialized successfully 154 */ initialize(SurfaceControl parent)155 public boolean initialize(SurfaceControl parent) { 156 if (isInitialized()) { 157 throw new IllegalStateException("Trying to initialize for the second time."); 158 } 159 160 mTransaction = new SurfaceControl.Transaction(); 161 // Create a container surface to which the task content will be reparented 162 final String name = "TaskEmbedder - " + Integer.toHexString(System.identityHashCode(this)); 163 mSurfaceControl = new SurfaceControl.Builder() 164 .setContainerLayer() 165 .setParent(parent) 166 .setName(name) 167 .setCallsite("TaskEmbedder.initialize") 168 .build(); 169 170 if (!onInitialize()) { 171 return false; 172 } 173 if (mListener != null && isInitialized()) { 174 mListener.onInitialized(); 175 } 176 mOpened = true; 177 mGuard.open("release"); 178 mTransaction.show(getSurfaceControl()).apply(); 179 return true; 180 } 181 182 /** 183 * Whether this container has been initialized. 184 * 185 * @return true if initialized 186 */ isInitialized()187 public abstract boolean isInitialized(); 188 189 /** 190 * Called when the task embedder should be initialized. 191 * NOTE: all overriding methods should call this one after they finish their initialization. 192 * @return whether to report whether the embedder was initialized. 193 */ onInitialize()194 public boolean onInitialize() { 195 updateLocationAndTapExcludeRegion(); 196 return true; 197 } 198 199 /** 200 * Called when the task embedder should be released. 201 * @return whether to report whether the embedder was released. 202 */ onRelease()203 protected boolean onRelease() { 204 // Clear tap-exclude region (if any) for this window. 205 clearTapExcludeRegion(); 206 return true; 207 } 208 209 /** 210 * Starts presentation of tasks in this container. 211 */ start()212 public void start() { 213 updateLocationAndTapExcludeRegion(); 214 } 215 216 /** 217 * Stops presentation of tasks in this container. 218 */ stop()219 public void stop() { 220 clearTapExcludeRegion(); 221 } 222 223 /** 224 * This should be called whenever the position or size of the surface changes 225 * or if touchable areas above the surface are added or removed. 226 */ notifyBoundsChanged()227 public void notifyBoundsChanged() { 228 updateLocationAndTapExcludeRegion(); 229 } 230 231 /** 232 * Called to update the dimensions whenever the host size changes. 233 * 234 * @param width the new width of the surface 235 * @param height the new height of the surface 236 */ resizeTask(int width, int height)237 public void resizeTask(int width, int height) { 238 // Do nothing 239 } 240 241 /** 242 * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the 243 * virtual display. 244 */ performBackPress()245 public abstract void performBackPress(); 246 247 /** 248 * An opaque unique identifier for this task surface among others being managed by the app. 249 */ getId()250 public abstract int getId(); 251 252 /** 253 * Calculates and updates the {@param region} with the transparent region for this task 254 * embedder. 255 */ gatherTransparentRegion(Region region)256 public boolean gatherTransparentRegion(Region region) { 257 // Do nothing 258 return false; 259 } 260 261 /** 262 * Returns the surface control for the task surface. This should be parented to a screen 263 * surface for display/embedding purposes. 264 * 265 * @return the surface control for the task 266 */ getSurfaceControl()267 public SurfaceControl getSurfaceControl() { 268 return mSurfaceControl; 269 } 270 getDisplayId()271 public int getDisplayId() { 272 return INVALID_DISPLAY; 273 } 274 getVirtualDisplay()275 public VirtualDisplay getVirtualDisplay() { 276 return null; 277 } 278 279 /** 280 * Set forwarded insets on the task content. 281 * 282 * @see IWindowManager#setForwardedInsets 283 */ setForwardedInsets(Insets insets)284 public void setForwardedInsets(Insets insets) { 285 // Do nothing 286 } 287 288 /** 289 * Updates position and bounds information needed by WM and IME to manage window 290 * focus and touch events properly. 291 * <p> 292 * This should be called whenever the position or size of the surface changes 293 * or if touchable areas above the surface are added or removed. 294 */ updateLocationAndTapExcludeRegion()295 protected void updateLocationAndTapExcludeRegion() { 296 if (!isInitialized() || mHost.getWindow() == null) { 297 return; 298 } 299 applyTapExcludeRegion(mHost.getWindow(), mHost.getTapExcludeRegion()); 300 } 301 302 /** 303 * Call to update the tap exclude region for the window. 304 * <p> 305 * This should not normally be called directly, but through 306 * {@link #updateLocationAndTapExcludeRegion()}. This method 307 * is provided as an optimization when managing multiple TaskSurfaces within a view. 308 * 309 * @see IWindowSession#updateTapExcludeRegion(IWindow, Region) 310 */ applyTapExcludeRegion(IWindow window, @Nullable Region tapExcludeRegion)311 private void applyTapExcludeRegion(IWindow window, @Nullable Region tapExcludeRegion) { 312 try { 313 IWindowSession session = WindowManagerGlobal.getWindowSession(); 314 session.updateTapExcludeRegion(window, tapExcludeRegion); 315 } catch (RemoteException e) { 316 e.rethrowAsRuntimeException(); 317 } 318 } 319 320 /** 321 * Removes the tap exclude region set by {@link #updateLocationAndTapExcludeRegion()}. 322 */ clearTapExcludeRegion()323 private void clearTapExcludeRegion() { 324 if (!isInitialized() || mHost.getWindow() == null) { 325 return; 326 } 327 applyTapExcludeRegion(mHost.getWindow(), null); 328 } 329 330 /** 331 * Set the callback to be notified about state changes. 332 * <p>This class must finish initializing before {@link #startActivity(Intent)} can be called. 333 * <p>Note: If the instance was ready prior to this call being made, then 334 * {@link Listener#onInitialized()} will be called from within this method call. 335 * 336 * @param listener The listener to report events to. 337 * 338 * @see ActivityView.StateCallback 339 * @see #startActivity(Intent) 340 */ setListener(TaskEmbedder.Listener listener)341 public void setListener(TaskEmbedder.Listener listener) { 342 mListener = listener; 343 if (mListener != null && isInitialized()) { 344 mListener.onInitialized(); 345 } 346 } 347 348 /** 349 * Launch a new activity into this container. 350 * 351 * @param intent Intent used to launch an activity 352 * 353 * @see #startActivity(PendingIntent) 354 */ startActivity(@onNull Intent intent)355 public void startActivity(@NonNull Intent intent) { 356 final ActivityOptions options = prepareActivityOptions(null); 357 mContext.startActivity(intent, options.toBundle()); 358 } 359 360 /** 361 * Launch a new activity into this container. 362 * 363 * @param intent Intent used to launch an activity 364 * @param user The UserHandle of the user to start this activity for 365 * 366 * @see #startActivity(PendingIntent) 367 */ startActivity(@onNull Intent intent, UserHandle user)368 public void startActivity(@NonNull Intent intent, UserHandle user) { 369 final ActivityOptions options = prepareActivityOptions(null); 370 mContext.startActivityAsUser(intent, options.toBundle(), user); 371 } 372 373 /** 374 * Launch a new activity into this container. 375 * 376 * @param pendingIntent Intent used to launch an activity 377 * 378 * @see #startActivity(Intent) 379 */ startActivity(@onNull PendingIntent pendingIntent)380 public void startActivity(@NonNull PendingIntent pendingIntent) { 381 final ActivityOptions options = prepareActivityOptions(null); 382 try { 383 pendingIntent.send(null /* context */, 0 /* code */, null /* intent */, 384 null /* onFinished */, null /* handler */, null /* requiredPermission */, 385 options.toBundle()); 386 } catch (PendingIntent.CanceledException e) { 387 throw new RuntimeException(e); 388 } 389 } 390 391 /** 392 * Launch a new activity into this container. 393 * 394 * @param pendingIntent Intent used to launch an activity 395 * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()} 396 * @param options options for the activity 397 * 398 * @see #startActivity(Intent) 399 */ startActivity(@onNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, @NonNull ActivityOptions options)400 public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, 401 @NonNull ActivityOptions options) { 402 prepareActivityOptions(options); 403 try { 404 pendingIntent.send(mContext, 0 /* code */, fillInIntent, 405 null /* onFinished */, null /* handler */, null /* requiredPermission */, 406 options.toBundle()); 407 } catch (PendingIntent.CanceledException e) { 408 throw new RuntimeException(e); 409 } 410 } 411 412 /** 413 * Launch an activity represented by {@link ShortcutInfo} into this container. 414 * <p>The owner of this container must be allowed to access the shortcut information, 415 * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method. 416 * 417 * @param shortcut the shortcut used to launch the activity. 418 * @param options options for the activity. 419 * @param sourceBounds the rect containing the source bounds of the clicked icon to open 420 * this shortcut. 421 * 422 * @see #startActivity(Intent) 423 */ startShortcutActivity(@onNull ShortcutInfo shortcut, @NonNull ActivityOptions options, @Nullable Rect sourceBounds)424 public void startShortcutActivity(@NonNull ShortcutInfo shortcut, 425 @NonNull ActivityOptions options, @Nullable Rect sourceBounds) { 426 LauncherApps service = 427 (LauncherApps) mContext.getSystemService(Context.LAUNCHER_APPS_SERVICE); 428 prepareActivityOptions(options); 429 service.startShortcut(shortcut, sourceBounds, options.toBundle()); 430 } 431 432 /** 433 * Check if container is ready to launch and modify {@param options} to target the virtual 434 * display, creating them if necessary. 435 */ 436 @CallSuper prepareActivityOptions(ActivityOptions options)437 protected ActivityOptions prepareActivityOptions(ActivityOptions options) { 438 if (!isInitialized()) { 439 throw new IllegalStateException( 440 "Trying to start activity before ActivityView is ready."); 441 } 442 if (options == null) { 443 options = ActivityOptions.makeBasic(); 444 } 445 return options; 446 } 447 448 /** 449 * Releases the resources for this TaskEmbedder. Tasks will no longer be launchable 450 * within this container. 451 * 452 * <p>Note: Calling this method is allowed after {@link Listener#onInitialized()} callback is 453 * triggered and before {@link Listener#onReleased()}. 454 */ release()455 public void release() { 456 if (!isInitialized()) { 457 throw new IllegalStateException("Trying to release container that is not initialized."); 458 } 459 performRelease(); 460 } 461 performRelease()462 private boolean performRelease() { 463 if (!mOpened) { 464 return false; 465 } 466 467 mTransaction.reparent(mSurfaceControl, null).apply(); 468 mSurfaceControl.release(); 469 470 boolean reportReleased = onRelease(); 471 if (mListener != null && reportReleased) { 472 mListener.onReleased(); 473 } 474 mOpened = false; 475 mGuard.close(); 476 return true; 477 } 478 479 @Override finalize()480 protected void finalize() throws Throwable { 481 try { 482 if (mGuard != null) { 483 mGuard.warnIfOpen(); 484 performRelease(); 485 } 486 } finally { 487 super.finalize(); 488 } 489 } 490 } 491