content: /*
   * Copyright (C) 2016 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 com.android.server.wm;
  
  import android.app.ActivityManager;
  import android.app.ActivityManager.RunningTaskInfo;
  import android.app.ActivityManager.TaskSnapshot;
  import android.app.ITaskStackListener;
  import android.app.TaskInfo;
  import android.content.ComponentName;
  import android.os.Binder;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.Looper;
  import android.os.Message;
  import android.os.RemoteCallbackList;
  import android.os.RemoteException;
  
  import java.util.ArrayList;
  
  class TaskChangeNotificationController {
      private static final int LOG_STACK_STATE_MSG = 1;
      private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_MSG = 2;
      private static final int NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG = 3;
      private static final int NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG = 4;
      private static final int NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG = 5;
      private static final int NOTIFY_FORCED_RESIZABLE_MSG = 6;
      private static final int NOTIFY_ACTIVITY_DISMISSING_DOCKED_STACK_MSG = 7;
      private static final int NOTIFY_TASK_ADDED_LISTENERS_MSG = 8;
      private static final int NOTIFY_TASK_REMOVED_LISTENERS_MSG = 9;
      private static final int NOTIFY_TASK_MOVED_TO_FRONT_LISTENERS_MSG = 10;
      private static final int NOTIFY_TASK_DESCRIPTION_CHANGED_LISTENERS_MSG = 11;
      private static final int NOTIFY_ACTIVITY_REQUESTED_ORIENTATION_CHANGED_LISTENERS = 12;
      private static final int NOTIFY_TASK_REMOVAL_STARTED_LISTENERS = 13;
      private static final int NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG = 14;
      private static final int NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG = 15;
      private static final int NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG = 16;
      private static final int NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG = 17;
      private static final int NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED_MSG = 18;
      private static final int NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED_MSG = 19;
      private static final int NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG = 20;
      private static final int NOTIFY_BACK_PRESSED_ON_TASK_ROOT = 21;
      private static final int NOTIFY_SINGLE_TASK_DISPLAY_DRAWN = 22;
      private static final int NOTIFY_SINGLE_TASK_DISPLAY_EMPTY = 23;
      private static final int NOTIFY_TASK_DISPLAY_CHANGED_LISTENERS_MSG = 24;
  
      // Delay in notifying task stack change listeners (in millis)
      private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
  
      // Global lock used by the service the instantiate objects of this class.
      private final Object mServiceLock;
      private final ActivityStackSupervisor mStackSupervisor;
      private final Handler mHandler;
  
      // Task stack change listeners in a remote process.
      private final RemoteCallbackList<ITaskStackListener> mRemoteTaskStackListeners =
              new RemoteCallbackList<>();
  
      /*
       * Task stack change listeners in a local process. Tracked separately so that they can be
       * called on the same thread.
       */
      private final ArrayList<ITaskStackListener> mLocalTaskStackListeners = new ArrayList<>();
  
      private final TaskStackConsumer mNotifyTaskStackChanged = (l, m) -> {
          l.onTaskStackChanged();
      };
  
      private final TaskStackConsumer mNotifyTaskCreated = (l, m) -> {
          l.onTaskCreated(m.arg1, (ComponentName) m.obj);
      };
  
      private final TaskStackConsumer mNotifyTaskRemoved = (l, m) -> {
          l.onTaskRemoved(m.arg1);
      };
  
      private final TaskStackConsumer mNotifyTaskMovedToFront = (l, m) -> {
          l.onTaskMovedToFront((RunningTaskInfo) m.obj);
      };
  
      private final TaskStackConsumer mNotifyTaskDescriptionChanged = (l, m) -> {
          l.onTaskDescriptionChanged((RunningTaskInfo) m.obj);
      };
  
      private final TaskStackConsumer mNotifyBackPressedOnTaskRoot = (l, m) -> {
          l.onBackPressedOnTaskRoot((RunningTaskInfo) m.obj);
      };
  
      private final TaskStackConsumer mNotifyActivityRequestedOrientationChanged = (l, m) -> {
          l.onActivityRequestedOrientationChanged(m.arg1, m.arg2);
      };
  
      private final TaskStackConsumer mNotifyTaskRemovalStarted = (l, m) -> {
          l.onTaskRemovalStarted((RunningTaskInfo) m.obj);
      };
  
      private final TaskStackConsumer mNotifyActivityPinned = (l, m) -> {
          l.onActivityPinned((String) m.obj /* packageName */, m.sendingUid /* userId */,
                  m.arg1 /* taskId */, m.arg2 /* stackId */);
      };
  
      private final TaskStackConsumer mNotifyActivityUnpinned = (l, m) -> {
          l.onActivityUnpinned();
      };
  
      private final TaskStackConsumer mNotifyPinnedActivityRestartAttempt = (l, m) -> {
          l.onPinnedActivityRestartAttempt(m.arg1 != 0);
      };
  
      private final TaskStackConsumer mNotifyPinnedStackAnimationStarted = (l, m) -> {
          l.onPinnedStackAnimationStarted();
      };
  
      private final TaskStackConsumer mNotifyPinnedStackAnimationEnded = (l, m) -> {
          l.onPinnedStackAnimationEnded();
      };
  
      private final TaskStackConsumer mNotifyActivityForcedResizable = (l, m) -> {
          l.onActivityForcedResizable((String) m.obj, m.arg1, m.arg2);
      };
  
      private final TaskStackConsumer mNotifyActivityDismissingDockedStack = (l, m) -> {
          l.onActivityDismissingDockedStack();
      };
  
      private final TaskStackConsumer mNotifyActivityLaunchOnSecondaryDisplayFailed = (l, m) -> {
          l.onActivityLaunchOnSecondaryDisplayFailed((RunningTaskInfo) m.obj, m.arg1);
      };
  
      private final TaskStackConsumer mNotifyActivityLaunchOnSecondaryDisplayRerouted = (l, m) -> {
          l.onActivityLaunchOnSecondaryDisplayRerouted((RunningTaskInfo) m.obj, m.arg1);
      };
  
      private final TaskStackConsumer mNotifyTaskProfileLocked = (l, m) -> {
          l.onTaskProfileLocked(m.arg1, m.arg2);
      };
  
      private final TaskStackConsumer mNotifyTaskSnapshotChanged = (l, m) -> {
          l.onTaskSnapshotChanged(m.arg1, (TaskSnapshot) m.obj);
      };
  
      private final TaskStackConsumer mOnSizeCompatModeActivityChanged = (l, m) -> {
          l.onSizeCompatModeActivityChanged(m.arg1, (IBinder) m.obj);
      };
  
      private final TaskStackConsumer mNotifySingleTaskDisplayDrawn = (l, m) -> {
          l.onSingleTaskDisplayDrawn(m.arg1);
      };
  
      private final TaskStackConsumer mNotifySingleTaskDisplayEmpty = (l, m) -> {
          l.onSingleTaskDisplayEmpty(m.arg1);
      };
  
      private final TaskStackConsumer mNotifyTaskDisplayChanged = (l, m) -> {
          l.onTaskDisplayChanged(m.arg1, m.arg2);
      };
  
      @FunctionalInterface
      public interface TaskStackConsumer {
          void accept(ITaskStackListener t, Message m) throws RemoteException;
      }
  
      private class MainHandler extends Handler {
          public MainHandler(Looper looper) {
              super(looper);
          }
  
          @Override
          public void handleMessage(Message msg) {
              switch (msg.what) {
                  case LOG_STACK_STATE_MSG: {
                      synchronized (mServiceLock) {
                          mStackSupervisor.logStackState();
                      }
                      break;
                  }
                  case NOTIFY_TASK_STACK_CHANGE_LISTENERS_MSG:
                      forAllRemoteListeners(mNotifyTaskStackChanged, msg);
                      break;
                  case NOTIFY_TASK_ADDED_LISTENERS_MSG:
                      forAllRemoteListeners(mNotifyTaskCreated, msg);
                      break;
                  case NOTIFY_TASK_REMOVED_LISTENERS_MSG:
                      forAllRemoteListeners(mNotifyTaskRemoved, msg);
                      break;
                  case NOTIFY_TASK_MOVED_TO_FRONT_LISTENERS_MSG:
                      forAllRemoteListeners(mNotifyTaskMovedToFront, msg);
                      break;
                  case NOTIFY_TASK_DESCRIPTION_CHANGED_LISTENERS_MSG:
                      forAllRemoteListeners(mNotifyTaskDescriptionChanged, msg);
                      break;
                  case NOTIFY_ACTIVITY_REQUESTED_ORIENTATION_CHANGED_LISTENERS:
                      forAllRemoteListeners(mNotifyActivityRequestedOrientationChanged, msg);
                      break;
                  case NOTIFY_TASK_REMOVAL_STARTED_LISTENERS:
                      forAllRemoteListeners(mNotifyTaskRemovalStarted, msg);
                      break;
                  case NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG:
                      forAllRemoteListeners(mNotifyActivityPinned, msg);
                      break;
                  case NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG:
                      forAllRemoteListeners(mNotifyActivityUnpinned, msg);
                      break;
                  case NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG:
                      forAllRemoteListeners(mNotifyPinnedActivityRestartAttempt, msg);
                      break;
                  case NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG:
                      forAllRemoteListeners(mNotifyPinnedStackAnimationStarted, msg);
                      break;
                  case NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG:
                      forAllRemoteListeners(mNotifyPinnedStackAnimationEnded, msg);
                      break;
                  case NOTIFY_FORCED_RESIZABLE_MSG:
                      forAllRemoteListeners(mNotifyActivityForcedResizable, msg);
                      break;
                  case NOTIFY_ACTIVITY_DISMISSING_DOCKED_STACK_MSG:
                      forAllRemoteListeners(mNotifyActivityDismissingDockedStack, msg);
                      break;
                  case NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED_MSG:
                      forAllRemoteListeners(mNotifyActivityLaunchOnSecondaryDisplayFailed, msg);
                      break;
                  case NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED_MSG:
                      forAllRemoteListeners(mNotifyActivityLaunchOnSecondaryDisplayRerouted, msg);
                      break;
                  case NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG:
                      forAllRemoteListeners(mNotifyTaskProfileLocked, msg);
                      break;
                  case NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG:
                      forAllRemoteListeners(mNotifyTaskSnapshotChanged, msg);
                      break;
                  case NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG:
                      forAllRemoteListeners(mOnSizeCompatModeActivityChanged, msg);
                      break;
                  case NOTIFY_BACK_PRESSED_ON_TASK_ROOT:
                      forAllRemoteListeners(mNotifyBackPressedOnTaskRoot, msg);
                      break;
                  case NOTIFY_SINGLE_TASK_DISPLAY_DRAWN:
                      forAllRemoteListeners(mNotifySingleTaskDisplayDrawn, msg);
                      break;
                  case NOTIFY_SINGLE_TASK_DISPLAY_EMPTY:
                      forAllRemoteListeners(mNotifySingleTaskDisplayEmpty, msg);
                      break;
                  case NOTIFY_TASK_DISPLAY_CHANGED_LISTENERS_MSG:
                      forAllRemoteListeners(mNotifyTaskDisplayChanged, msg);
                      break;
              }
          }
      }
  
      public TaskChangeNotificationController(Object serviceLock,
              ActivityStackSupervisor stackSupervisor, Handler handler) {
          mServiceLock = serviceLock;
          mStackSupervisor = stackSupervisor;
          mHandler = new MainHandler(handler.getLooper());
      }
  
      public void registerTaskStackListener(ITaskStackListener listener) {
          synchronized (mServiceLock) {
              if (listener != null) {
                  if (Binder.getCallingPid() == android.os.Process.myPid()) {
                      if (!mLocalTaskStackListeners.contains(listener)) {
                          mLocalTaskStackListeners.add(listener);
                      }
                  } else {
                      mRemoteTaskStackListeners.register(listener);
                  }
              }
          }
      }
  
      public void unregisterTaskStackListener(ITaskStackListener listener) {
          synchronized (mServiceLock) {
              if (listener != null) {
                  if (Binder.getCallingPid() == android.os.Process.myPid()) {
                      mLocalTaskStackListeners.remove(listener);
                  } else {
                      mRemoteTaskStackListeners.unregister(listener);
                  }
              }
          }
      }
  
      private void forAllRemoteListeners(TaskStackConsumer callback, Message message) {
          synchronized (mServiceLock) {
              for (int i = mRemoteTaskStackListeners.beginBroadcast() - 1; i >= 0; i--) {
                  try {
                      // Make a one-way callback to the listener
                      callback.accept(mRemoteTaskStackListeners.getBroadcastItem(i), message);
                  } catch (RemoteException e) {
                      // Handled by the RemoteCallbackList.
                  }
              }
              mRemoteTaskStackListeners.finishBroadcast();
          }
      }
  
      private void forAllLocalListeners(TaskStackConsumer callback, Message message) {
          synchronized (mServiceLock) {
              for (int i = mLocalTaskStackListeners.size() - 1; i >= 0; i--) {
                  try {
                      callback.accept(mLocalTaskStackListeners.get(i), message);
                  } catch (RemoteException e) {
                      // Never thrown since this is called locally.
                  }
              }
          }
      }
  
      /** Notifies all listeners when the task stack has changed. */
      void notifyTaskStackChanged() {
          mHandler.sendEmptyMessage(LOG_STACK_STATE_MSG);
          mHandler.removeMessages(NOTIFY_TASK_STACK_CHANGE_LISTENERS_MSG);
          final Message msg = mHandler.obtainMessage(NOTIFY_TASK_STACK_CHANGE_LISTENERS_MSG);
          forAllLocalListeners(mNotifyTaskStackChanged, msg);
          // Only the main task stack change notification requires a delay.
          mHandler.sendMessageDelayed(msg, NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY);
      }
  
      /** Notifies all listeners when an Activity is pinned. */
      void notifyActivityPinned(ActivityRecord r) {
          mHandler.removeMessages(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
          final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG,
                  r.getTaskRecord().taskId, r.getStackId(), r.packageName);
          msg.sendingUid = r.mUserId;
          forAllLocalListeners(mNotifyActivityPinned, msg);
          msg.sendToTarget();
      }
  
      /** Notifies all listeners when an Activity is unpinned. */
      void notifyActivityUnpinned() {
          mHandler.removeMessages(NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG);
          final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_UNPINNED_LISTENERS_MSG);
          forAllLocalListeners(mNotifyActivityUnpinned, msg);
          msg.sendToTarget();
      }
  
      /**
       * Notifies all listeners when an attempt was made to start an an activity that is already
       * running in the pinned stack and the activity was not actually started, but the task is
       * either brought to the front or a new Intent is delivered to it.
       */
      void notifyPinnedActivityRestartAttempt(boolean clearedTask) {
          mHandler.removeMessages(NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG);
          final Message msg =
                  mHandler.obtainMessage(NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG,
                          clearedTask ? 1 : 0, 0);
          forAllLocalListeners(mNotifyPinnedActivityRestartAttempt, msg);
          msg.sendToTarget();
      }
  
      /** Notifies all listeners when the pinned stack animation starts. */
      void notifyPinnedStackAnimationStarted() {
          mHandler.removeMessages(NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG);
          final Message msg =
                  mHandler.obtainMessage(NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG);
          forAllLocalListeners(mNotifyPinnedStackAnimationStarted, msg);
          msg.sendToTarget();
      }
  
      /** Notifies all listeners when the pinned stack animation ends. */
      void notifyPinnedStackAnimationEnded() {
          mHandler.removeMessages(NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG);
          final Message msg =
                  mHandler.obtainMessage(NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG);
          forAllLocalListeners(mNotifyPinnedStackAnimationEnded, msg);
          msg.sendToTarget();
      }
  
      void notifyActivityDismissingDockedStack() {
          mHandler.removeMessages(NOTIFY_ACTIVITY_DISMISSING_DOCKED_STACK_MSG);
          final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_DISMISSING_DOCKED_STACK_MSG);
          forAllLocalListeners(mNotifyActivityDismissingDockedStack, msg);
          msg.sendToTarget();
      }
  
      void notifyActivityForcedResizable(int taskId, int reason, String packageName) {
          mHandler.removeMessages(NOTIFY_FORCED_RESIZABLE_MSG);
          final Message msg = mHandler.obtainMessage(NOTIFY_FORCED_RESIZABLE_MSG, taskId, reason,
                  packageName);
          forAllLocalListeners(mNotifyActivityForcedResizable, msg);
          msg.sendToTarget();
      }
  
      void notifyActivityLaunchOnSecondaryDisplayFailed(TaskInfo ti, int requestedDisplayId) {
          mHandler.removeMessages(NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED_MSG);
          final Message msg = mHandler.obtainMessage(
                  NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED_MSG, requestedDisplayId,
                  0 /* unused */, ti);
          forAllLocalListeners(mNotifyActivityLaunchOnSecondaryDisplayFailed, msg);
          msg.sendToTarget();
      }
  
      void notifyActivityLaunchOnSecondaryDisplayRerouted(TaskInfo ti, int requestedDisplayId) {
          mHandler.removeMessages(NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED_MSG);
          final Message msg = mHandler.obtainMessage(
                  NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED_MSG, requestedDisplayId,
                  0 /* unused */, ti);
          forAllLocalListeners(mNotifyActivityLaunchOnSecondaryDisplayRerouted, msg);
          msg.sendToTarget();
      }
  
      void notifyTaskCreated(int taskId, ComponentName componentName) {
          final Message msg = mHandler.obtainMessage(NOTIFY_TASK_ADDED_LISTENERS_MSG,
                  taskId, 0 /* unused */, componentName);
          forAllLocalListeners(mNotifyTaskCreated, msg);
          msg.sendToTarget();
      }
  
      void notifyTaskRemoved(int taskId) {
          final Message msg = mHandler.obtainMessage(NOTIFY_TASK_REMOVED_LISTENERS_MSG,
                  taskId, 0 /* unused */);
          forAllLocalListeners(mNotifyTaskRemoved, msg);
          msg.sendToTarget();
      }
  
      void notifyTaskMovedToFront(TaskInfo ti) {
          final Message msg = mHandler.obtainMessage(NOTIFY_TASK_MOVED_TO_FRONT_LISTENERS_MSG, ti);
          forAllLocalListeners(mNotifyTaskMovedToFront, msg);
          msg.sendToTarget();
      }
  
      void notifyTaskDescriptionChanged(TaskInfo taskInfo) {
          final Message msg = mHandler.obtainMessage(NOTIFY_TASK_DESCRIPTION_CHANGED_LISTENERS_MSG,
                  taskInfo);
          forAllLocalListeners(mNotifyTaskDescriptionChanged, msg);
          msg.sendToTarget();
  
      }
  
      void notifyActivityRequestedOrientationChanged(int taskId, int orientation) {
          final Message msg = mHandler.obtainMessage(
                  NOTIFY_ACTIVITY_REQUESTED_ORIENTATION_CHANGED_LISTENERS, taskId, orientation);
          forAllLocalListeners(mNotifyActivityRequestedOrientationChanged, msg);
          msg.sendToTarget();
      }
  
      /**
       * Notify listeners that the task is about to be finished before its surfaces are removed from
       * the window manager. This allows interested parties to perform relevant animations before
       * the window disappears.
       */
      void notifyTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo) {
          final Message msg = mHandler.obtainMessage(NOTIFY_TASK_REMOVAL_STARTED_LISTENERS, taskInfo);
          forAllLocalListeners(mNotifyTaskRemovalStarted, msg);
          msg.sendToTarget();
      }
  
      /**
       * Notify listeners that the task has been put in a locked state because one or more of the
       * activities inside it belong to a managed profile user that has been locked.
       */
      void notifyTaskProfileLocked(int taskId, int userId) {
          final Message msg = mHandler.obtainMessage(NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG, taskId,
                  userId);
          forAllLocalListeners(mNotifyTaskProfileLocked, msg);
          msg.sendToTarget();
      }
  
      /**
       * Notify listeners that the snapshot of a task has changed.
       */
      void notifyTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) {
          final Message msg = mHandler.obtainMessage(NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG,
                  taskId, 0, snapshot);
          forAllLocalListeners(mNotifyTaskSnapshotChanged, msg);
          msg.sendToTarget();
      }
  
      /**
       * Notify listeners that whether a size compatibility mode activity is using the override
       * bounds which is not fit its parent.
       */
      void notifySizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
          final Message msg = mHandler.obtainMessage(NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG,
                  displayId, 0 /* unused */, activityToken);
          forAllLocalListeners(mOnSizeCompatModeActivityChanged, msg);
          msg.sendToTarget();
      }
  
      /**
       * Notify listeners that an activity received a back press when there are no other activities
       * in the back stack.
       */
      void notifyBackPressedOnTaskRoot(TaskInfo taskInfo) {
          final Message msg = mHandler.obtainMessage(NOTIFY_BACK_PRESSED_ON_TASK_ROOT,
                  taskInfo);
          forAllLocalListeners(mNotifyBackPressedOnTaskRoot, msg);
          msg.sendToTarget();
      }
  
      /**
       * Notify listeners that contents are drawn for the first time on a single task display.
       */
      void notifySingleTaskDisplayDrawn(int displayId) {
          final Message msg = mHandler.obtainMessage(NOTIFY_SINGLE_TASK_DISPLAY_DRAWN,
                  displayId, 0 /* unused */);
          forAllLocalListeners(mNotifySingleTaskDisplayDrawn, msg);
          msg.sendToTarget();
      }
  
      /**
       * Notify listeners that the last task is removed from a single task display.
       */
      void notifySingleTaskDisplayEmpty(int displayId) {
          final Message msg = mHandler.obtainMessage(
                  NOTIFY_SINGLE_TASK_DISPLAY_EMPTY,
                  displayId, 0 /* unused */);
          forAllLocalListeners(mNotifySingleTaskDisplayEmpty, msg);
          msg.sendToTarget();
      }
  
      /**
       * Notify listeners that a task is reparented to another display.
       */
      void notifyTaskDisplayChanged(int taskId, int newDisplayId) {
          final Message msg = mHandler.obtainMessage(NOTIFY_TASK_DISPLAY_CHANGED_LISTENERS_MSG,
                  taskId, newDisplayId);
          forAllLocalListeners(mNotifyTaskStackChanged, msg);
          msg.sendToTarget();
      }
  }
  