1 /** 2 * Copyright (c) 2017 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.app; 18 19 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL; 20 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; 21 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; 22 import static android.view.Display.INVALID_DISPLAY; 23 24 import android.annotation.NonNull; 25 import android.annotation.TestApi; 26 import android.app.ActivityManager.StackInfo; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.graphics.Insets; 31 import android.graphics.Matrix; 32 import android.graphics.Region; 33 import android.hardware.display.DisplayManager; 34 import android.hardware.display.VirtualDisplay; 35 import android.hardware.input.InputManager; 36 import android.os.RemoteException; 37 import android.os.SystemClock; 38 import android.os.UserHandle; 39 import android.util.AttributeSet; 40 import android.util.DisplayMetrics; 41 import android.util.Log; 42 import android.view.IWindowManager; 43 import android.view.InputDevice; 44 import android.view.KeyCharacterMap; 45 import android.view.KeyEvent; 46 import android.view.SurfaceControl; 47 import android.view.SurfaceHolder; 48 import android.view.SurfaceSession; 49 import android.view.SurfaceView; 50 import android.view.View; 51 import android.view.ViewGroup; 52 import android.view.ViewParent; 53 import android.view.WindowManager; 54 import android.view.WindowManagerGlobal; 55 import android.view.inputmethod.InputMethodManager; 56 57 import dalvik.system.CloseGuard; 58 59 import java.util.List; 60 61 /** 62 * Activity container that allows launching activities into itself. 63 * <p>Activity launching into this container is restricted by the same rules that apply to launching 64 * on VirtualDisplays. 65 * @hide 66 */ 67 @TestApi 68 public class ActivityView extends ViewGroup { 69 70 private static final String DISPLAY_NAME = "ActivityViewVirtualDisplay"; 71 private static final String TAG = "ActivityView"; 72 73 private VirtualDisplay mVirtualDisplay; 74 private final SurfaceView mSurfaceView; 75 76 /** 77 * This is the root surface for the VirtualDisplay. The VirtualDisplay child surfaces will be 78 * re-parented to this surface. This will also be a child of the SurfaceView's SurfaceControl. 79 */ 80 private SurfaceControl mRootSurfaceControl; 81 82 private final SurfaceCallback mSurfaceCallback; 83 private StateCallback mActivityViewCallback; 84 85 private IActivityTaskManager mActivityTaskManager; 86 // Temp container to store view coordinates in window. 87 private final int[] mLocationInWindow = new int[2]; 88 89 // The latest tap exclude region that we've sent to WM. 90 private final Region mTapExcludeRegion = new Region(); 91 92 private TaskStackListener mTaskStackListener; 93 94 private final CloseGuard mGuard = CloseGuard.get(); 95 private boolean mOpened; // Protected by mGuard. 96 97 private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction(); 98 99 /** The ActivityView is only allowed to contain one task. */ 100 private final boolean mSingleTaskInstance; 101 102 private Insets mForwardedInsets; 103 ActivityView(Context context)104 public ActivityView(Context context) { 105 this(context, null /* attrs */); 106 } 107 ActivityView(Context context, AttributeSet attrs)108 public ActivityView(Context context, AttributeSet attrs) { 109 this(context, attrs, 0 /* defStyle */); 110 } 111 ActivityView(Context context, AttributeSet attrs, int defStyle)112 public ActivityView(Context context, AttributeSet attrs, int defStyle) { 113 this(context, attrs, defStyle, false /*singleTaskInstance*/); 114 } 115 ActivityView( Context context, AttributeSet attrs, int defStyle, boolean singleTaskInstance)116 public ActivityView( 117 Context context, AttributeSet attrs, int defStyle, boolean singleTaskInstance) { 118 super(context, attrs, defStyle); 119 mSingleTaskInstance = singleTaskInstance; 120 121 mActivityTaskManager = ActivityTaskManager.getService(); 122 mSurfaceView = new SurfaceView(context); 123 mSurfaceCallback = new SurfaceCallback(); 124 mSurfaceView.getHolder().addCallback(mSurfaceCallback); 125 addView(mSurfaceView); 126 127 mOpened = true; 128 mGuard.open("release"); 129 } 130 131 /** Callback that notifies when the container is ready or destroyed. */ 132 public abstract static class StateCallback { 133 134 /** 135 * Called when the container is ready for launching activities. Calling 136 * {@link #startActivity(Intent)} prior to this callback will result in an 137 * {@link IllegalStateException}. 138 * 139 * @see #startActivity(Intent) 140 */ onActivityViewReady(ActivityView view)141 public abstract void onActivityViewReady(ActivityView view); 142 143 /** 144 * Called when the container can no longer launch activities. Calling 145 * {@link #startActivity(Intent)} after this callback will result in an 146 * {@link IllegalStateException}. 147 * 148 * @see #startActivity(Intent) 149 */ onActivityViewDestroyed(ActivityView view)150 public abstract void onActivityViewDestroyed(ActivityView view); 151 152 /** 153 * Called when a task is created inside the container. 154 * This is a filtered version of {@link TaskStackListener} 155 */ onTaskCreated(int taskId, ComponentName componentName)156 public void onTaskCreated(int taskId, ComponentName componentName) { } 157 158 /** 159 * Called when a task is moved to the front of the stack inside the container. 160 * This is a filtered version of {@link TaskStackListener} 161 */ onTaskMovedToFront(int taskId)162 public void onTaskMovedToFront(int taskId) { } 163 164 /** 165 * Called when a task is about to be removed from the stack inside the container. 166 * This is a filtered version of {@link TaskStackListener} 167 */ onTaskRemovalStarted(int taskId)168 public void onTaskRemovalStarted(int taskId) { } 169 } 170 171 /** 172 * Set the callback to be notified about state changes. 173 * <p>This class must finish initializing before {@link #startActivity(Intent)} can be called. 174 * <p>Note: If the instance was ready prior to this call being made, then 175 * {@link StateCallback#onActivityViewReady(ActivityView)} will be called from within 176 * this method call. 177 * 178 * @param callback The callback to report events to. 179 * 180 * @see StateCallback 181 * @see #startActivity(Intent) 182 */ setCallback(StateCallback callback)183 public void setCallback(StateCallback callback) { 184 mActivityViewCallback = callback; 185 186 if (mVirtualDisplay != null && mActivityViewCallback != null) { 187 mActivityViewCallback.onActivityViewReady(this); 188 } 189 } 190 191 /** 192 * Sets the corner radius for the Activity displayed here. The corners will be 193 * cropped from the window painted by the contained Activity. 194 * 195 * @param cornerRadius the radius for the corners, in pixels 196 * @hide 197 */ setCornerRadius(float cornerRadius)198 public void setCornerRadius(float cornerRadius) { 199 mSurfaceView.setCornerRadius(cornerRadius); 200 } 201 202 /** 203 * Launch a new activity into this container. 204 * <p>Activity resolved by the provided {@link Intent} must have 205 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be 206 * launched here. Also, if activity is not owned by the owner of this container, it must allow 207 * embedding and the caller must have permission to embed. 208 * <p>Note: This class must finish initializing and 209 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before 210 * this method can be called. 211 * 212 * @param intent Intent used to launch an activity. 213 * 214 * @see StateCallback 215 * @see #startActivity(PendingIntent) 216 */ startActivity(@onNull Intent intent)217 public void startActivity(@NonNull Intent intent) { 218 final ActivityOptions options = prepareActivityOptions(); 219 getContext().startActivity(intent, options.toBundle()); 220 } 221 222 /** 223 * Launch a new activity into this container. 224 * <p>Activity resolved by the provided {@link Intent} must have 225 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be 226 * launched here. Also, if activity is not owned by the owner of this container, it must allow 227 * embedding and the caller must have permission to embed. 228 * <p>Note: This class must finish initializing and 229 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before 230 * this method can be called. 231 * 232 * @param intent Intent used to launch an activity. 233 * @param user The UserHandle of the user to start this activity for. 234 * 235 * 236 * @see StateCallback 237 * @see #startActivity(PendingIntent) 238 */ startActivity(@onNull Intent intent, UserHandle user)239 public void startActivity(@NonNull Intent intent, UserHandle user) { 240 final ActivityOptions options = prepareActivityOptions(); 241 getContext().startActivityAsUser(intent, options.toBundle(), user); 242 } 243 244 /** 245 * Launch a new activity into this container. 246 * <p>Activity resolved by the provided {@link PendingIntent} must have 247 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be 248 * launched here. Also, if activity is not owned by the owner of this container, it must allow 249 * embedding and the caller must have permission to embed. 250 * <p>Note: This class must finish initializing and 251 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before 252 * this method can be called. 253 * 254 * @param pendingIntent Intent used to launch an activity. 255 * 256 * @see StateCallback 257 * @see #startActivity(Intent) 258 */ startActivity(@onNull PendingIntent pendingIntent)259 public void startActivity(@NonNull PendingIntent pendingIntent) { 260 final ActivityOptions options = prepareActivityOptions(); 261 try { 262 pendingIntent.send(null /* context */, 0 /* code */, null /* intent */, 263 null /* onFinished */, null /* handler */, null /* requiredPermission */, 264 options.toBundle()); 265 } catch (PendingIntent.CanceledException e) { 266 throw new RuntimeException(e); 267 } 268 } 269 270 /** 271 * Launch a new activity into this container. 272 * <p>Activity resolved by the provided {@link PendingIntent} must have 273 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be 274 * launched here. Also, if activity is not owned by the owner of this container, it must allow 275 * embedding and the caller must have permission to embed. 276 * <p>Note: This class must finish initializing and 277 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before 278 * this method can be called. 279 * 280 * @param pendingIntent Intent used to launch an activity. 281 * @param options options for the activity 282 * 283 * @see StateCallback 284 * @see #startActivity(Intent) 285 */ startActivity(@onNull PendingIntent pendingIntent, @NonNull ActivityOptions options)286 public void startActivity(@NonNull PendingIntent pendingIntent, 287 @NonNull ActivityOptions options) { 288 options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId()); 289 try { 290 pendingIntent.send(null /* context */, 0 /* code */, null /* intent */, 291 null /* onFinished */, null /* handler */, null /* requiredPermission */, 292 options.toBundle()); 293 } catch (PendingIntent.CanceledException e) { 294 throw new RuntimeException(e); 295 } 296 } 297 298 /** 299 * Check if container is ready to launch and create {@link ActivityOptions} to target the 300 * virtual display. 301 */ prepareActivityOptions()302 private ActivityOptions prepareActivityOptions() { 303 if (mVirtualDisplay == null) { 304 throw new IllegalStateException( 305 "Trying to start activity before ActivityView is ready."); 306 } 307 final ActivityOptions options = ActivityOptions.makeBasic(); 308 options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId()); 309 return options; 310 } 311 312 /** 313 * Release this container. Activity launching will no longer be permitted. 314 * <p>Note: Calling this method is allowed after 315 * {@link StateCallback#onActivityViewReady(ActivityView)} callback was triggered and before 316 * {@link StateCallback#onActivityViewDestroyed(ActivityView)}. 317 * 318 * @see StateCallback 319 */ release()320 public void release() { 321 if (mVirtualDisplay == null) { 322 throw new IllegalStateException( 323 "Trying to release container that is not initialized."); 324 } 325 performRelease(); 326 } 327 328 /** 329 * Triggers an update of {@link ActivityView}'s location in window to properly set tap exclude 330 * regions and avoid focus switches by touches on this view. 331 */ onLocationChanged()332 public void onLocationChanged() { 333 updateLocationAndTapExcludeRegion(); 334 } 335 clearActivityViewGeometryForIme()336 private void clearActivityViewGeometryForIme() { 337 if (mVirtualDisplay == null) { 338 return; 339 } 340 final int displayId = mVirtualDisplay.getDisplay().getDisplayId(); 341 mContext.getSystemService(InputMethodManager.class).reportActivityView(displayId, null); 342 } 343 344 @Override onLayout(boolean changed, int l, int t, int r, int b)345 public void onLayout(boolean changed, int l, int t, int r, int b) { 346 mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */); 347 } 348 349 @Override gatherTransparentRegion(Region region)350 public boolean gatherTransparentRegion(Region region) { 351 // The tap exclude region may be affected by any view on top of it, so we detect the 352 // possible change by monitoring this function. 353 updateLocationAndTapExcludeRegion(); 354 return super.gatherTransparentRegion(region); 355 } 356 357 /** 358 * Sends current location in window and tap exclude region to WM for this view. 359 */ updateLocationAndTapExcludeRegion()360 private void updateLocationAndTapExcludeRegion() { 361 if (mVirtualDisplay == null || !isAttachedToWindow()) { 362 return; 363 } 364 try { 365 int x = mLocationInWindow[0]; 366 int y = mLocationInWindow[1]; 367 getLocationInWindow(mLocationInWindow); 368 if (x != mLocationInWindow[0] || y != mLocationInWindow[1]) { 369 x = mLocationInWindow[0]; 370 y = mLocationInWindow[1]; 371 final int displayId = mVirtualDisplay.getDisplay().getDisplayId(); 372 WindowManagerGlobal.getWindowSession().updateDisplayContentLocation( 373 getWindow(), x, y, displayId); 374 375 // Also report this geometry information to InputMethodManagerService. 376 // TODO(b/115693908): Unify this logic into the above WMS-based one. 377 final Matrix matrix = new Matrix(); 378 matrix.set(getMatrix()); 379 matrix.postTranslate(x, y); 380 mContext.getSystemService(InputMethodManager.class) 381 .reportActivityView(displayId, matrix); 382 } 383 updateTapExcludeRegion(x, y); 384 } catch (RemoteException e) { 385 e.rethrowAsRuntimeException(); 386 } 387 } 388 389 /** Computes and sends current tap exclude region to WM for this view. */ updateTapExcludeRegion(int x, int y)390 private void updateTapExcludeRegion(int x, int y) throws RemoteException { 391 if (!canReceivePointerEvents()) { 392 cleanTapExcludeRegion(); 393 return; 394 } 395 mTapExcludeRegion.set(x, y, x + getWidth(), y + getHeight()); 396 397 // There might be views on top of us. We need to subtract those areas from the tap 398 // exclude region. 399 final ViewParent parent = getParent(); 400 if (parent != null) { 401 parent.subtractObscuredTouchableRegion(mTapExcludeRegion, this); 402 } 403 404 WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(), 405 mTapExcludeRegion); 406 } 407 408 private class SurfaceCallback implements SurfaceHolder.Callback { 409 @Override surfaceCreated(SurfaceHolder surfaceHolder)410 public void surfaceCreated(SurfaceHolder surfaceHolder) { 411 if (mVirtualDisplay == null) { 412 initVirtualDisplay(new SurfaceSession()); 413 if (mVirtualDisplay != null && mActivityViewCallback != null) { 414 mActivityViewCallback.onActivityViewReady(ActivityView.this); 415 } 416 } else { 417 mTmpTransaction.reparent(mRootSurfaceControl, 418 mSurfaceView.getSurfaceControl()).apply(); 419 } 420 421 if (mVirtualDisplay != null) { 422 mVirtualDisplay.setDisplayState(true); 423 } 424 425 updateLocationAndTapExcludeRegion(); 426 } 427 428 @Override surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height)429 public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) { 430 if (mVirtualDisplay != null) { 431 mVirtualDisplay.resize(width, height, getBaseDisplayDensity()); 432 } 433 updateLocationAndTapExcludeRegion(); 434 } 435 436 @Override surfaceDestroyed(SurfaceHolder surfaceHolder)437 public void surfaceDestroyed(SurfaceHolder surfaceHolder) { 438 if (mVirtualDisplay != null) { 439 mVirtualDisplay.setDisplayState(false); 440 } 441 clearActivityViewGeometryForIme(); 442 cleanTapExcludeRegion(); 443 } 444 } 445 446 @Override onVisibilityChanged(View changedView, int visibility)447 protected void onVisibilityChanged(View changedView, int visibility) { 448 super.onVisibilityChanged(changedView, visibility); 449 mSurfaceView.setVisibility(visibility); 450 } 451 452 /** 453 * @return the display id of the virtual display. 454 */ getVirtualDisplayId()455 public int getVirtualDisplayId() { 456 if (mVirtualDisplay != null) { 457 return mVirtualDisplay.getDisplay().getDisplayId(); 458 } 459 return INVALID_DISPLAY; 460 } 461 462 /** 463 * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the 464 * virtual display. 465 */ performBackPress()466 public void performBackPress() { 467 if (mVirtualDisplay == null) { 468 return; 469 } 470 final int displayId = mVirtualDisplay.getDisplay().getDisplayId(); 471 final InputManager im = InputManager.getInstance(); 472 im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, displayId), 473 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 474 im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, displayId), 475 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 476 } 477 createKeyEvent(int action, int code, int displayId)478 private static KeyEvent createKeyEvent(int action, int code, int displayId) { 479 long when = SystemClock.uptimeMillis(); 480 final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */, 481 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, 482 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, 483 InputDevice.SOURCE_KEYBOARD); 484 ev.setDisplayId(displayId); 485 return ev; 486 } 487 initVirtualDisplay(SurfaceSession surfaceSession)488 private void initVirtualDisplay(SurfaceSession surfaceSession) { 489 if (mVirtualDisplay != null) { 490 throw new IllegalStateException("Trying to initialize for the second time."); 491 } 492 493 final int width = mSurfaceView.getWidth(); 494 final int height = mSurfaceView.getHeight(); 495 final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); 496 497 mVirtualDisplay = displayManager.createVirtualDisplay( 498 DISPLAY_NAME + "@" + System.identityHashCode(this), width, height, 499 getBaseDisplayDensity(), null, 500 VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY 501 | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL); 502 if (mVirtualDisplay == null) { 503 Log.e(TAG, "Failed to initialize ActivityView"); 504 return; 505 } 506 507 final int displayId = mVirtualDisplay.getDisplay().getDisplayId(); 508 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); 509 510 mRootSurfaceControl = new SurfaceControl.Builder(surfaceSession) 511 .setContainerLayer() 512 .setParent(mSurfaceView.getSurfaceControl()) 513 .setName(DISPLAY_NAME) 514 .build(); 515 516 try { 517 // TODO: Find a way to consolidate these calls to the server. 518 WindowManagerGlobal.getWindowSession().reparentDisplayContent( 519 getWindow(), mRootSurfaceControl, displayId); 520 wm.dontOverrideDisplayInfo(displayId); 521 if (mSingleTaskInstance) { 522 mActivityTaskManager.setDisplayToSingleTaskInstance(displayId); 523 } 524 wm.setForwardedInsets(displayId, mForwardedInsets); 525 } catch (RemoteException e) { 526 e.rethrowAsRuntimeException(); 527 } 528 529 mTmpTransaction.show(mRootSurfaceControl).apply(); 530 mTaskStackListener = new TaskStackListenerImpl(); 531 try { 532 mActivityTaskManager.registerTaskStackListener(mTaskStackListener); 533 } catch (RemoteException e) { 534 Log.e(TAG, "Failed to register task stack listener", e); 535 } 536 } 537 performRelease()538 private void performRelease() { 539 if (!mOpened) { 540 return; 541 } 542 543 mSurfaceView.getHolder().removeCallback(mSurfaceCallback); 544 545 cleanTapExcludeRegion(); 546 547 if (mTaskStackListener != null) { 548 try { 549 mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); 550 } catch (RemoteException e) { 551 Log.e(TAG, "Failed to unregister task stack listener", e); 552 } 553 mTaskStackListener = null; 554 } 555 556 final boolean displayReleased; 557 if (mVirtualDisplay != null) { 558 mVirtualDisplay.release(); 559 mVirtualDisplay = null; 560 displayReleased = true; 561 } else { 562 displayReleased = false; 563 } 564 565 if (displayReleased && mActivityViewCallback != null) { 566 mActivityViewCallback.onActivityViewDestroyed(this); 567 } 568 569 mGuard.close(); 570 mOpened = false; 571 } 572 573 /** Report to server that tap exclude region on hosting display should be cleared. */ cleanTapExcludeRegion()574 private void cleanTapExcludeRegion() { 575 if (!isAttachedToWindow() || mTapExcludeRegion.isEmpty()) { 576 return; 577 } 578 // Update tap exclude region with a null region to clean the state on server. 579 try { 580 WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(), 581 null /* region */); 582 mTapExcludeRegion.setEmpty(); 583 } catch (RemoteException e) { 584 e.rethrowAsRuntimeException(); 585 } 586 } 587 588 /** Get density of the hosting display. */ getBaseDisplayDensity()589 private int getBaseDisplayDensity() { 590 final WindowManager wm = mContext.getSystemService(WindowManager.class); 591 final DisplayMetrics metrics = new DisplayMetrics(); 592 wm.getDefaultDisplay().getMetrics(metrics); 593 return metrics.densityDpi; 594 } 595 596 @Override finalize()597 protected void finalize() throws Throwable { 598 try { 599 if (mGuard != null) { 600 mGuard.warnIfOpen(); 601 performRelease(); 602 } 603 } finally { 604 super.finalize(); 605 } 606 } 607 608 /** 609 * Set forwarded insets on the virtual display. 610 * 611 * @see IWindowManager#setForwardedInsets 612 */ setForwardedInsets(Insets insets)613 public void setForwardedInsets(Insets insets) { 614 mForwardedInsets = insets; 615 if (mVirtualDisplay == null) { 616 return; 617 } 618 try { 619 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); 620 wm.setForwardedInsets(mVirtualDisplay.getDisplay().getDisplayId(), mForwardedInsets); 621 } catch (RemoteException e) { 622 e.rethrowAsRuntimeException(); 623 } 624 } 625 626 /** 627 * A task change listener that detects background color change of the topmost stack on our 628 * virtual display and updates the background of the surface view. This background will be shown 629 * when surface view is resized, but the app hasn't drawn its content in new size yet. 630 * It also calls StateCallback.onTaskMovedToFront to notify interested parties that the stack 631 * associated with the {@link ActivityView} has had a Task moved to the front. This is useful 632 * when needing to also bring the host Activity to the foreground at the same time. 633 */ 634 private class TaskStackListenerImpl extends TaskStackListener { 635 636 @Override onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)637 public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo) 638 throws RemoteException { 639 if (mVirtualDisplay == null 640 || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) { 641 return; 642 } 643 644 StackInfo stackInfo = getTopMostStackInfo(); 645 if (stackInfo == null) { 646 return; 647 } 648 // Found the topmost stack on target display. Now check if the topmost task's 649 // description changed. 650 if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { 651 mSurfaceView.setResizeBackgroundColor( 652 taskInfo.taskDescription.getBackgroundColor()); 653 } 654 } 655 656 @Override onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)657 public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) 658 throws RemoteException { 659 if (mActivityViewCallback == null || mVirtualDisplay == null 660 || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) { 661 return; 662 } 663 664 StackInfo stackInfo = getTopMostStackInfo(); 665 // if StackInfo was null or unrelated to the "move to front" then there's no use 666 // notifying the callback 667 if (stackInfo != null 668 && taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { 669 mActivityViewCallback.onTaskMovedToFront(taskInfo.taskId); 670 } 671 } 672 673 @Override onTaskCreated(int taskId, ComponentName componentName)674 public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException { 675 if (mActivityViewCallback == null || mVirtualDisplay == null) { 676 return; 677 } 678 679 StackInfo stackInfo = getTopMostStackInfo(); 680 // if StackInfo was null or unrelated to the task creation then there's no use 681 // notifying the callback 682 if (stackInfo != null 683 && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) { 684 mActivityViewCallback.onTaskCreated(taskId, componentName); 685 } 686 } 687 688 @Override onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo)689 public void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo) 690 throws RemoteException { 691 if (mActivityViewCallback == null || mVirtualDisplay == null 692 || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) { 693 return; 694 } 695 mActivityViewCallback.onTaskRemovalStarted(taskInfo.taskId); 696 } 697 getTopMostStackInfo()698 private StackInfo getTopMostStackInfo() throws RemoteException { 699 // Find the topmost task on our virtual display - it will define the background 700 // color of the surface view during resizing. 701 final int displayId = mVirtualDisplay.getDisplay().getDisplayId(); 702 final List<StackInfo> stackInfoList = mActivityTaskManager.getAllStackInfos(); 703 704 // Iterate through stacks from top to bottom. 705 final int stackCount = stackInfoList.size(); 706 for (int i = 0; i < stackCount; i++) { 707 final StackInfo stackInfo = stackInfoList.get(i); 708 // Only look for stacks on our virtual display. 709 if (stackInfo.displayId != displayId) { 710 continue; 711 } 712 // Found the topmost stack on target display. 713 return stackInfo; 714 } 715 return null; 716 } 717 } 718 } 719