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