content: package com.android.server.wm;
  
  import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
  import static android.view.Display.DEFAULT_DISPLAY;
  import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
  
  import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
  import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
  import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS;
  
  import android.os.Debug;
  import android.os.IBinder;
  import android.util.Slog;
  import android.view.KeyEvent;
  import android.view.WindowManager;
  
  import com.android.server.input.InputManagerService;
  
  import java.io.PrintWriter;
  
  final class InputManagerCallback implements InputManagerService.WindowManagerCallbacks {
      private final WindowManagerService mService;
  
      // Set to true when the first input device configuration change notification
      // is received to indicate that the input devices are ready.
      private final Object mInputDevicesReadyMonitor = new Object();
      private boolean mInputDevicesReady;
  
      // When true, prevents input dispatch from proceeding until set to false again.
      private boolean mInputDispatchFrozen;
  
      // The reason the input is currently frozen or null if the input isn't frozen.
      private String mInputFreezeReason = null;
  
      // When true, input dispatch proceeds normally.  Otherwise all events are dropped.
      // Initially false, so that input does not get dispatched until boot is finished at
      // which point the ActivityManager will enable dispatching.
      private boolean mInputDispatchEnabled;
  
      public InputManagerCallback(WindowManagerService service) {
          mService = service;
      }
  
      /**
       * Notifies the window manager about a broken input channel.
       *
       * Called by the InputManager.
       */
      @Override
      public void notifyInputChannelBroken(IBinder token) {
          if (token == null) {
              return;
          }
  
          synchronized (mService.mGlobalLock) {
              WindowState windowState = mService.windowForClientLocked(null, token, false);
              if (windowState != null) {
                  Slog.i(TAG_WM, "WINDOW DIED " + windowState);
                  windowState.removeIfPossible();
              }
          }
      }
  
      /**
       * Notifies the window manager about an application that is not responding.
       * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch.
       *
       * Called by the InputManager.
       */
      @Override
      public long notifyANR(IBinder token, String reason) {
          AppWindowToken appWindowToken = null;
          WindowState windowState = null;
          boolean aboveSystem = false;
          synchronized (mService.mGlobalLock) {
              if (token != null) {
                  windowState = mService.windowForClientLocked(null, token, false);
                  if (windowState != null) {
                      appWindowToken = windowState.mAppToken;
                  }
              }
  
              if (windowState != null) {
                  Slog.i(TAG_WM, "Input event dispatching timed out "
                          + "sending to " + windowState.mAttrs.getTitle()
                          + ".  Reason: " + reason);
                  // Figure out whether this window is layered above system windows.
                  // We need to do this here to help the activity manager know how to
                  // layer its ANR dialog.
                  int systemAlertLayer = mService.mPolicy.getWindowLayerFromTypeLw(
                          TYPE_APPLICATION_OVERLAY, windowState.mOwnerCanAddInternalSystemWindow);
                  aboveSystem = windowState.mBaseLayer > systemAlertLayer;
              } else if (appWindowToken != null) {
                  Slog.i(TAG_WM, "Input event dispatching timed out "
                          + "sending to application " + appWindowToken.stringName
                          + ".  Reason: " + reason);
              } else {
                  Slog.i(TAG_WM, "Input event dispatching timed out "
                          + ".  Reason: " + reason);
              }
  
              mService.saveANRStateLocked(appWindowToken, windowState, reason);
          }
  
          // All the calls below need to happen without the WM lock held since they call into AM.
          mService.mAtmInternal.saveANRState(reason);
  
          if (appWindowToken != null && appWindowToken.appToken != null) {
              // Notify the activity manager about the timeout and let it decide whether
              // to abort dispatching or keep waiting.
              final boolean abort = appWindowToken.keyDispatchingTimedOut(reason,
                      (windowState != null) ? windowState.mSession.mPid : -1);
              if (!abort) {
                  // The activity manager declined to abort dispatching.
                  // Wait a bit longer and timeout again later.
                  return appWindowToken.mInputDispatchingTimeoutNanos;
              }
          } else if (windowState != null) {
              // Notify the activity manager about the timeout and let it decide whether
              // to abort dispatching or keep waiting.
              long timeout = mService.mAmInternal.inputDispatchingTimedOut(
                      windowState.mSession.mPid, aboveSystem, reason);
              if (timeout >= 0) {
                  // The activity manager declined to abort dispatching.
                  // Wait a bit longer and timeout again later.
                  return timeout * 1000000L; // nanoseconds
              }
          }
          return 0; // abort dispatching
      }
  
      /** Notifies that the input device configuration has changed. */
      @Override
      public void notifyConfigurationChanged() {
          // TODO(multi-display): Notify proper displays that are associated with this input device.
          mService.sendNewConfiguration(DEFAULT_DISPLAY);
  
          synchronized (mInputDevicesReadyMonitor) {
              if (!mInputDevicesReady) {
                  mInputDevicesReady = true;
                  mInputDevicesReadyMonitor.notifyAll();
              }
          }
      }
  
      /** Notifies that the lid switch changed state. */
      @Override
      public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
          mService.mPolicy.notifyLidSwitchChanged(whenNanos, lidOpen);
      }
  
      /** Notifies that the camera lens cover state has changed. */
      @Override
      public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered) {
          mService.mPolicy.notifyCameraLensCoverSwitchChanged(whenNanos, lensCovered);
      }
  
      /**
       * Provides an opportunity for the window manager policy to intercept early key
       * processing as soon as the key has been read from the device.
       */
      @Override
      public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
          return mService.mPolicy.interceptKeyBeforeQueueing(event, policyFlags);
      }
  
      /** {@inheritDoc} */
      @Override
      public int interceptMotionBeforeQueueingNonInteractive(int displayId, long whenNanos,
              int policyFlags) {
          return mService.mPolicy.interceptMotionBeforeQueueingNonInteractive(
                  displayId, whenNanos, policyFlags);
      }
  
      /**
       * Provides an opportunity for the window manager policy to process a key before
       * ordinary dispatch.
       */
      @Override
      public long interceptKeyBeforeDispatching(
              IBinder focus, KeyEvent event, int policyFlags) {
          WindowState windowState = mService.windowForClientLocked(null, focus, false);
          return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags);
      }
  
      /**
       * Provides an opportunity for the window manager policy to process a key that
       * the application did not handle.
       */
      @Override
      public KeyEvent dispatchUnhandledKey(
              IBinder focus, KeyEvent event, int policyFlags) {
          WindowState windowState = mService.windowForClientLocked(null, focus, false);
          return mService.mPolicy.dispatchUnhandledKey(windowState, event, policyFlags);
      }
  
      /** Callback to get pointer layer. */
      @Override
      public int getPointerLayer() {
          return mService.mPolicy.getWindowLayerFromTypeLw(WindowManager.LayoutParams.TYPE_POINTER)
                  * WindowManagerService.TYPE_LAYER_MULTIPLIER
                  + WindowManagerService.TYPE_LAYER_OFFSET;
      }
  
      /** Callback to get pointer display id. */
      @Override
      public int getPointerDisplayId() {
          synchronized (mService.mGlobalLock) {
              // If desktop mode is not enabled, show on the default display.
              if (!mService.mForceDesktopModeOnExternalDisplays) {
                  return DEFAULT_DISPLAY;
              }
  
              // Look for the topmost freeform display.
              int firstExternalDisplayId = DEFAULT_DISPLAY;
              for (int i = mService.mRoot.mChildren.size() - 1; i >= 0; --i) {
                  final DisplayContent displayContent = mService.mRoot.mChildren.get(i);
                  // Heuristic solution here. Currently when "Freeform windows" developer option is
                  // enabled we automatically put secondary displays in freeform mode and emulating
                  // "desktop mode". It also makes sense to show the pointer on the same display.
                  if (displayContent.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
                      return displayContent.getDisplayId();
                  }
  
                  if (firstExternalDisplayId == DEFAULT_DISPLAY
                          && displayContent.getDisplayId() != DEFAULT_DISPLAY) {
                      firstExternalDisplayId = displayContent.getDisplayId();
                  }
              }
  
              // Look for the topmost non-default display
              return firstExternalDisplayId;
          }
      }
  
      @Override
      public void onPointerDownOutsideFocus(IBinder touchedToken) {
          mService.mH.obtainMessage(ON_POINTER_DOWN_OUTSIDE_FOCUS, touchedToken).sendToTarget();
      }
  
      /** Waits until the built-in input devices have been configured. */
      public boolean waitForInputDevicesReady(long timeoutMillis) {
          synchronized (mInputDevicesReadyMonitor) {
              if (!mInputDevicesReady) {
                  try {
                      mInputDevicesReadyMonitor.wait(timeoutMillis);
                  } catch (InterruptedException ex) {
                  }
              }
              return mInputDevicesReady;
          }
      }
  
      public void freezeInputDispatchingLw() {
          if (!mInputDispatchFrozen) {
              if (DEBUG_INPUT) {
                  Slog.v(TAG_WM, "Freezing input dispatching");
              }
  
              mInputDispatchFrozen = true;
  
              if (DEBUG_INPUT) {
                  mInputFreezeReason = Debug.getCallers(6);
              }
              updateInputDispatchModeLw();
          }
      }
  
      public void thawInputDispatchingLw() {
          if (mInputDispatchFrozen) {
              if (DEBUG_INPUT) {
                  Slog.v(TAG_WM, "Thawing input dispatching");
              }
  
              mInputDispatchFrozen = false;
              mInputFreezeReason = null;
              updateInputDispatchModeLw();
          }
      }
  
      public void setEventDispatchingLw(boolean enabled) {
          if (mInputDispatchEnabled != enabled) {
              if (DEBUG_INPUT) {
                  Slog.v(TAG_WM, "Setting event dispatching to " + enabled);
              }
  
              mInputDispatchEnabled = enabled;
              updateInputDispatchModeLw();
          }
      }
  
      private void updateInputDispatchModeLw() {
          mService.mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen);
      }
  
      void dump(PrintWriter pw, String prefix) {
          if (mInputFreezeReason != null) {
              pw.println(prefix + "mInputFreezeReason=" + mInputFreezeReason);
          }
      }
  }
  