content: /**
   * Copyright (c) 2017 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.app;
  
  import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
  import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
  import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
  import static android.view.Display.INVALID_DISPLAY;
  
  import android.annotation.NonNull;
  import android.annotation.TestApi;
  import android.app.ActivityManager.StackInfo;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.graphics.Insets;
  import android.graphics.Matrix;
  import android.graphics.Region;
  import android.hardware.display.DisplayManager;
  import android.hardware.display.VirtualDisplay;
  import android.hardware.input.InputManager;
  import android.os.RemoteException;
  import android.os.SystemClock;
  import android.os.UserHandle;
  import android.util.AttributeSet;
  import android.util.DisplayMetrics;
  import android.util.Log;
  import android.view.IWindowManager;
  import android.view.InputDevice;
  import android.view.KeyCharacterMap;
  import android.view.KeyEvent;
  import android.view.SurfaceControl;
  import android.view.SurfaceHolder;
  import android.view.SurfaceSession;
  import android.view.SurfaceView;
  import android.view.View;
  import android.view.ViewGroup;
  import android.view.ViewParent;
  import android.view.WindowManager;
  import android.view.WindowManagerGlobal;
  import android.view.inputmethod.InputMethodManager;
  
  import dalvik.system.CloseGuard;
  
  import java.util.List;
  
  /**
   * Activity container that allows launching activities into itself.
   * <p>Activity launching into this container is restricted by the same rules that apply to launching
   * on VirtualDisplays.
   * @hide
   */
  @TestApi
  public class ActivityView extends ViewGroup {
  
      private static final String DISPLAY_NAME = "ActivityViewVirtualDisplay";
      private static final String TAG = "ActivityView";
  
      private VirtualDisplay mVirtualDisplay;
      private final SurfaceView mSurfaceView;
  
      /**
       * This is the root surface for the VirtualDisplay. The VirtualDisplay child surfaces will be
       * re-parented to this surface. This will also be a child of the SurfaceView's SurfaceControl.
       */
      private SurfaceControl mRootSurfaceControl;
  
      private final SurfaceCallback mSurfaceCallback;
      private StateCallback mActivityViewCallback;
  
      private IActivityTaskManager mActivityTaskManager;
      // Temp container to store view coordinates in window.
      private final int[] mLocationInWindow = new int[2];
  
      // The latest tap exclude region that we've sent to WM.
      private final Region mTapExcludeRegion = new Region();
  
      private TaskStackListener mTaskStackListener;
  
      private final CloseGuard mGuard = CloseGuard.get();
      private boolean mOpened; // Protected by mGuard.
  
      private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
  
      /** The ActivityView is only allowed to contain one task. */
      private final boolean mSingleTaskInstance;
  
      private Insets mForwardedInsets;
  
      public ActivityView(Context context) {
          this(context, null /* attrs */);
      }
  
      public ActivityView(Context context, AttributeSet attrs) {
          this(context, attrs, 0 /* defStyle */);
      }
  
      public ActivityView(Context context, AttributeSet attrs, int defStyle) {
          this(context, attrs, defStyle, false /*singleTaskInstance*/);
      }
  
      public ActivityView(
              Context context, AttributeSet attrs, int defStyle, boolean singleTaskInstance) {
          super(context, attrs, defStyle);
          mSingleTaskInstance = singleTaskInstance;
  
          mActivityTaskManager = ActivityTaskManager.getService();
          mSurfaceView = new SurfaceView(context);
          // Since ActivityView#getAlpha has been overridden, we should use parent class's alpha
          // as master to synchronize surface view's alpha value.
          mSurfaceView.setAlpha(super.getAlpha());
          mSurfaceView.setUseAlpha();
          mSurfaceCallback = new SurfaceCallback();
          mSurfaceView.getHolder().addCallback(mSurfaceCallback);
          addView(mSurfaceView);
  
          mOpened = true;
          mGuard.open("release");
      }
  
      /** Callback that notifies when the container is ready or destroyed. */
      public abstract static class StateCallback {
  
          /**
           * Called when the container is ready for launching activities. Calling
           * {@link #startActivity(Intent)} prior to this callback will result in an
           * {@link IllegalStateException}.
           *
           * @see #startActivity(Intent)
           */
          public abstract void onActivityViewReady(ActivityView view);
  
          /**
           * Called when the container can no longer launch activities. Calling
           * {@link #startActivity(Intent)} after this callback will result in an
           * {@link IllegalStateException}.
           *
           * @see #startActivity(Intent)
           */
          public abstract void onActivityViewDestroyed(ActivityView view);
  
          /**
           * Called when a task is created inside the container.
           * This is a filtered version of {@link TaskStackListener}
           */
          public void onTaskCreated(int taskId, ComponentName componentName) { }
  
          /**
           * Called when a task is moved to the front of the stack inside the container.
           * This is a filtered version of {@link TaskStackListener}
           */
          public void onTaskMovedToFront(int taskId) { }
  
          /**
           * Called when a task is about to be removed from the stack inside the container.
           * This is a filtered version of {@link TaskStackListener}
           */
          public void onTaskRemovalStarted(int taskId) { }
      }
  
      /**
       * Set the callback to be notified about state changes.
       * <p>This class must finish initializing before {@link #startActivity(Intent)} can be called.
       * <p>Note: If the instance was ready prior to this call being made, then
       * {@link StateCallback#onActivityViewReady(ActivityView)} will be called from within
       * this method call.
       *
       * @param callback The callback to report events to.
       *
       * @see StateCallback
       * @see #startActivity(Intent)
       */
      public void setCallback(StateCallback callback) {
          mActivityViewCallback = callback;
  
          if (mVirtualDisplay != null && mActivityViewCallback != null) {
              mActivityViewCallback.onActivityViewReady(this);
          }
      }
  
      /**
       * Sets the corner radius for the Activity displayed here. The corners will be
       * cropped from the window painted by the contained Activity.
       *
       * @param cornerRadius the radius for the corners, in pixels
       * @hide
       */
      public void setCornerRadius(float cornerRadius) {
          mSurfaceView.setCornerRadius(cornerRadius);
      }
  
      /**
       * Launch a new activity into this container.
       * <p>Activity resolved by the provided {@link Intent} must have
       * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
       * launched here. Also, if activity is not owned by the owner of this container, it must allow
       * embedding and the caller must have permission to embed.
       * <p>Note: This class must finish initializing and
       * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
       * this method can be called.
       *
       * @param intent Intent used to launch an activity.
       *
       * @see StateCallback
       * @see #startActivity(PendingIntent)
       */
      public void startActivity(@NonNull Intent intent) {
          final ActivityOptions options = prepareActivityOptions();
          getContext().startActivity(intent, options.toBundle());
      }
  
      /**
       * Launch a new activity into this container.
       * <p>Activity resolved by the provided {@link Intent} must have
       * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
       * launched here. Also, if activity is not owned by the owner of this container, it must allow
       * embedding and the caller must have permission to embed.
       * <p>Note: This class must finish initializing and
       * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
       * this method can be called.
       *
       * @param intent Intent used to launch an activity.
       * @param user The UserHandle of the user to start this activity for.
       *
       *
       * @see StateCallback
       * @see #startActivity(PendingIntent)
       */
      public void startActivity(@NonNull Intent intent, UserHandle user) {
          final ActivityOptions options = prepareActivityOptions();
          getContext().startActivityAsUser(intent, options.toBundle(), user);
      }
  
      /**
       * Launch a new activity into this container.
       * <p>Activity resolved by the provided {@link PendingIntent} must have
       * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
       * launched here. Also, if activity is not owned by the owner of this container, it must allow
       * embedding and the caller must have permission to embed.
       * <p>Note: This class must finish initializing and
       * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
       * this method can be called.
       *
       * @param pendingIntent Intent used to launch an activity.
       *
       * @see StateCallback
       * @see #startActivity(Intent)
       */
      public void startActivity(@NonNull PendingIntent pendingIntent) {
          final ActivityOptions options = prepareActivityOptions();
          try {
              pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
                      null /* onFinished */, null /* handler */, null /* requiredPermission */,
                      options.toBundle());
          } catch (PendingIntent.CanceledException e) {
              throw new RuntimeException(e);
          }
      }
  
      /**
       * Launch a new activity into this container.
       * <p>Activity resolved by the provided {@link PendingIntent} must have
       * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
       * launched here. Also, if activity is not owned by the owner of this container, it must allow
       * embedding and the caller must have permission to embed.
       * <p>Note: This class must finish initializing and
       * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
       * this method can be called.
       *
       * @param pendingIntent Intent used to launch an activity.
       * @param options options for the activity
       *
       * @see StateCallback
       * @see #startActivity(Intent)
       */
      public void startActivity(@NonNull PendingIntent pendingIntent,
              @NonNull ActivityOptions options) {
          options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
          try {
              pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
                      null /* onFinished */, null /* handler */, null /* requiredPermission */,
                      options.toBundle());
          } catch (PendingIntent.CanceledException e) {
              throw new RuntimeException(e);
          }
      }
  
      /**
       * Check if container is ready to launch and create {@link ActivityOptions} to target the
       * virtual display.
       */
      private ActivityOptions prepareActivityOptions() {
          if (mVirtualDisplay == null) {
              throw new IllegalStateException(
                      "Trying to start activity before ActivityView is ready.");
          }
          final ActivityOptions options = ActivityOptions.makeBasic();
          options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
          return options;
      }
  
      /**
       * Release this container. Activity launching will no longer be permitted.
       * <p>Note: Calling this method is allowed after
       * {@link StateCallback#onActivityViewReady(ActivityView)} callback was triggered and before
       * {@link StateCallback#onActivityViewDestroyed(ActivityView)}.
       *
       * @see StateCallback
       */
      public void release() {
          if (mVirtualDisplay == null) {
              throw new IllegalStateException(
                      "Trying to release container that is not initialized.");
          }
          performRelease();
      }
  
      /**
       * Triggers an update of {@link ActivityView}'s location in window to properly set tap exclude
       * regions and avoid focus switches by touches on this view.
       */
      public void onLocationChanged() {
          updateLocationAndTapExcludeRegion();
      }
  
      private void clearActivityViewGeometryForIme() {
          if (mVirtualDisplay == null) {
              return;
          }
          final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
          mContext.getSystemService(InputMethodManager.class).reportActivityView(displayId, null);
      }
  
      @Override
      public void onLayout(boolean changed, int l, int t, int r, int b) {
          mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */);
      }
  
      /**
       * Sets the alpha value when the content of {@link SurfaceView} needs to show or hide.
       * <p>Note: The surface view may ignore the alpha value in some cases. Refer to
       * {@link SurfaceView#setAlpha} for more details.
       *
       * @param alpha The opacity of the view.
       */
      @Override
      public void setAlpha(float alpha) {
          super.setAlpha(alpha);
  
          if (mSurfaceView != null) {
              mSurfaceView.setAlpha(alpha);
          }
      }
  
      @Override
      public float getAlpha() {
          return mSurfaceView.getAlpha();
      }
  
      @Override
      public boolean gatherTransparentRegion(Region region) {
          // The tap exclude region may be affected by any view on top of it, so we detect the
          // possible change by monitoring this function.
          updateLocationAndTapExcludeRegion();
          return super.gatherTransparentRegion(region);
      }
  
      /**
       * Sends current location in window and tap exclude region to WM for this view.
       */
      private void updateLocationAndTapExcludeRegion() {
          if (mVirtualDisplay == null || !isAttachedToWindow()) {
              return;
          }
          try {
              int x = mLocationInWindow[0];
              int y = mLocationInWindow[1];
              getLocationInWindow(mLocationInWindow);
              if (x != mLocationInWindow[0] || y != mLocationInWindow[1]) {
                  x = mLocationInWindow[0];
                  y = mLocationInWindow[1];
                  final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
                  WindowManagerGlobal.getWindowSession().updateDisplayContentLocation(
                          getWindow(), x, y, displayId);
  
                  // Also report this geometry information to InputMethodManagerService.
                  // TODO(b/115693908): Unify this logic into the above WMS-based one.
                  final Matrix matrix = new Matrix();
                  matrix.set(getMatrix());
                  matrix.postTranslate(x, y);
                  mContext.getSystemService(InputMethodManager.class)
                          .reportActivityView(displayId, matrix);
              }
              updateTapExcludeRegion(x, y);
          } catch (RemoteException e) {
              e.rethrowAsRuntimeException();
          }
      }
  
      /** Computes and sends current tap exclude region to WM for this view. */
      private void updateTapExcludeRegion(int x, int y) throws RemoteException {
          if (!canReceivePointerEvents()) {
              cleanTapExcludeRegion();
              return;
          }
          mTapExcludeRegion.set(x, y, x + getWidth(), y + getHeight());
  
          // There might be views on top of us. We need to subtract those areas from the tap
          // exclude region.
          final ViewParent parent = getParent();
          if (parent != null) {
              parent.subtractObscuredTouchableRegion(mTapExcludeRegion, this);
          }
  
          WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
                  mTapExcludeRegion);
      }
  
      private class SurfaceCallback implements SurfaceHolder.Callback {
          @Override
          public void surfaceCreated(SurfaceHolder surfaceHolder) {
              if (mVirtualDisplay == null) {
                  initVirtualDisplay(new SurfaceSession());
                  if (mVirtualDisplay != null && mActivityViewCallback != null) {
                      mActivityViewCallback.onActivityViewReady(ActivityView.this);
                  }
              } else {
                  mTmpTransaction.reparent(mRootSurfaceControl,
                          mSurfaceView.getSurfaceControl()).apply();
              }
  
              if (mVirtualDisplay != null) {
                  mVirtualDisplay.setDisplayState(true);
              }
  
              updateLocationAndTapExcludeRegion();
          }
  
          @Override
          public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
              if (mVirtualDisplay != null) {
                  mVirtualDisplay.resize(width, height, getBaseDisplayDensity());
              }
              updateLocationAndTapExcludeRegion();
          }
  
          @Override
          public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
              if (mVirtualDisplay != null) {
                  mVirtualDisplay.setDisplayState(false);
              }
              clearActivityViewGeometryForIme();
              cleanTapExcludeRegion();
          }
      }
  
      @Override
      protected void onVisibilityChanged(View changedView, int visibility) {
          super.onVisibilityChanged(changedView, visibility);
          mSurfaceView.setVisibility(visibility);
      }
  
      /**
       * @return the display id of the virtual display.
       */
      public int getVirtualDisplayId() {
          if (mVirtualDisplay != null) {
              return mVirtualDisplay.getDisplay().getDisplayId();
          }
          return INVALID_DISPLAY;
      }
  
      /**
       * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
       * virtual display.
       */
      public void performBackPress() {
          if (mVirtualDisplay == null) {
              return;
          }
          final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
          final InputManager im = InputManager.getInstance();
          im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, displayId),
                  InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
          im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, displayId),
                  InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
      }
  
      private static KeyEvent createKeyEvent(int action, int code, int displayId) {
          long when = SystemClock.uptimeMillis();
          final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
                  0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
                  KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
                  InputDevice.SOURCE_KEYBOARD);
          ev.setDisplayId(displayId);
          return ev;
      }
  
      private void initVirtualDisplay(SurfaceSession surfaceSession) {
          if (mVirtualDisplay != null) {
              throw new IllegalStateException("Trying to initialize for the second time.");
          }
  
          final int width = mSurfaceView.getWidth();
          final int height = mSurfaceView.getHeight();
          final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
  
          mVirtualDisplay = displayManager.createVirtualDisplay(
                  DISPLAY_NAME + "@" + System.identityHashCode(this), width, height,
                  getBaseDisplayDensity(), null,
                  VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
                          | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
          if (mVirtualDisplay == null) {
              Log.e(TAG, "Failed to initialize ActivityView");
              return;
          }
  
          final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
          final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
  
          mRootSurfaceControl = new SurfaceControl.Builder(surfaceSession)
                  .setContainerLayer()
                  .setParent(mSurfaceView.getSurfaceControl())
                  .setName(DISPLAY_NAME)
                  .build();
  
          try {
              // TODO: Find a way to consolidate these calls to the server.
              WindowManagerGlobal.getWindowSession().reparentDisplayContent(
                      getWindow(), mRootSurfaceControl, displayId);
              wm.dontOverrideDisplayInfo(displayId);
              if (mSingleTaskInstance) {
                  mActivityTaskManager.setDisplayToSingleTaskInstance(displayId);
              }
              wm.setForwardedInsets(displayId, mForwardedInsets);
          } catch (RemoteException e) {
              e.rethrowAsRuntimeException();
          }
  
          mTmpTransaction.show(mRootSurfaceControl).apply();
          mTaskStackListener = new TaskStackListenerImpl();
          try {
              mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
          } catch (RemoteException e) {
              Log.e(TAG, "Failed to register task stack listener", e);
          }
      }
  
      private void performRelease() {
          if (!mOpened) {
              return;
          }
  
          mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
  
          cleanTapExcludeRegion();
  
          if (mTaskStackListener != null) {
              try {
                  mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
              } catch (RemoteException e) {
                  Log.e(TAG, "Failed to unregister task stack listener", e);
              }
              mTaskStackListener = null;
          }
  
          final boolean displayReleased;
          if (mVirtualDisplay != null) {
              mVirtualDisplay.release();
              mVirtualDisplay = null;
              displayReleased = true;
          } else {
              displayReleased = false;
          }
  
          if (displayReleased && mActivityViewCallback != null) {
              mActivityViewCallback.onActivityViewDestroyed(this);
          }
  
          mGuard.close();
          mOpened = false;
      }
  
      /** Report to server that tap exclude region on hosting display should be cleared. */
      private void cleanTapExcludeRegion() {
          if (!isAttachedToWindow() || mTapExcludeRegion.isEmpty()) {
              return;
          }
          // Update tap exclude region with a null region to clean the state on server.
          try {
              WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
                      null /* region */);
              mTapExcludeRegion.setEmpty();
          } catch (RemoteException e) {
              e.rethrowAsRuntimeException();
          }
      }
  
      /** Get density of the hosting display. */
      private int getBaseDisplayDensity() {
          final WindowManager wm = mContext.getSystemService(WindowManager.class);
          final DisplayMetrics metrics = new DisplayMetrics();
          wm.getDefaultDisplay().getMetrics(metrics);
          return metrics.densityDpi;
      }
  
      @Override
      protected void finalize() throws Throwable {
          try {
              if (mGuard != null) {
                  mGuard.warnIfOpen();
                  performRelease();
              }
          } finally {
              super.finalize();
          }
      }
  
      /**
       * Set forwarded insets on the virtual display.
       *
       * @see IWindowManager#setForwardedInsets
       */
      public void setForwardedInsets(Insets insets) {
          mForwardedInsets = insets;
          if (mVirtualDisplay == null) {
              return;
          }
          try {
              final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
              wm.setForwardedInsets(mVirtualDisplay.getDisplay().getDisplayId(), mForwardedInsets);
          } catch (RemoteException e) {
              e.rethrowAsRuntimeException();
          }
      }
  
      /**
       * A task change listener that detects background color change of the topmost stack on our
       * virtual display and updates the background of the surface view. This background will be shown
       * when surface view is resized, but the app hasn't drawn its content in new size yet.
       * It also calls StateCallback.onTaskMovedToFront to notify interested parties that the stack
       * associated with the {@link ActivityView} has had a Task moved to the front. This is useful
       * when needing to also bring the host Activity to the foreground at the same time.
       */
      private class TaskStackListenerImpl extends TaskStackListener {
  
          @Override
          public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)
                  throws RemoteException {
              if (mVirtualDisplay == null
                      || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
                  return;
              }
  
              StackInfo stackInfo = getTopMostStackInfo();
              if (stackInfo == null) {
                  return;
              }
              // Found the topmost stack on target display. Now check if the topmost task's
              // description changed.
              if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
                  mSurfaceView.setResizeBackgroundColor(
                          taskInfo.taskDescription.getBackgroundColor());
              }
          }
  
          @Override
          public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
                  throws RemoteException {
              if (mActivityViewCallback  == null || mVirtualDisplay == null
                      || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
                  return;
              }
  
              StackInfo stackInfo = getTopMostStackInfo();
              // if StackInfo was null or unrelated to the "move to front" then there's no use
              // notifying the callback
              if (stackInfo != null
                      && taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
                  mActivityViewCallback.onTaskMovedToFront(taskInfo.taskId);
              }
          }
  
          @Override
          public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
              if (mActivityViewCallback == null || mVirtualDisplay == null) {
                  return;
              }
  
              StackInfo stackInfo = getTopMostStackInfo();
              // if StackInfo was null or unrelated to the task creation then there's no use
              // notifying the callback
              if (stackInfo != null
                      && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
                  mActivityViewCallback.onTaskCreated(taskId, componentName);
              }
          }
  
          @Override
          public void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo)
                  throws RemoteException {
              if (mActivityViewCallback == null || mVirtualDisplay == null
                      || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
                  return;
              }
              mActivityViewCallback.onTaskRemovalStarted(taskInfo.taskId);
          }
  
          private StackInfo getTopMostStackInfo() throws RemoteException {
              // Find the topmost task on our virtual display - it will define the background
              // color of the surface view during resizing.
              final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
              final List<StackInfo> stackInfoList = mActivityTaskManager.getAllStackInfos();
  
              // Iterate through stacks from top to bottom.
              final int stackCount = stackInfoList.size();
              for (int i = 0; i < stackCount; i++) {
                  final StackInfo stackInfo = stackInfoList.get(i);
                  // Only look for stacks on our virtual display.
                  if (stackInfo.displayId != displayId) {
                      continue;
                  }
                  // Found the topmost stack on target display.
                  return stackInfo;
              }
              return null;
          }
      }
  }
  