content: /*
   * Copyright (C) 2015 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 static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
  import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
  import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
  import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
  import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
  import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
  
  import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
  import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
  import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
  import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
  import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
  import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
  import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
  import static com.android.server.wm.WindowManagerService.H.WALLPAPER_DRAW_PENDING_TIMEOUT;
  
  import android.graphics.Bitmap;
  import android.graphics.Rect;
  import android.os.Bundle;
  import android.os.Debug;
  import android.os.IBinder;
  import android.os.RemoteException;
  import android.os.SystemClock;
  import android.util.ArraySet;
  import android.util.Slog;
  import android.view.DisplayInfo;
  import android.view.SurfaceControl;
  import android.view.WindowManager;
  import android.view.animation.Animation;
  
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.util.ToBooleanFunction;
  
  import java.io.PrintWriter;
  import java.util.ArrayList;
  
  /**
   * Controls wallpaper windows visibility, ordering, and so on.
   * NOTE: All methods in this class must be called with the window manager service lock held.
   */
  class WallpaperController {
      private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperController" : TAG_WM;
      private WindowManagerService mService;
      private final DisplayContent mDisplayContent;
  
      private final ArrayList<WallpaperWindowToken> mWallpaperTokens = new ArrayList<>();
  
      // If non-null, this is the currently visible window that is associated
      // with the wallpaper.
      private WindowState mWallpaperTarget = null;
      // If non-null, we are in the middle of animating from one wallpaper target
      // to another, and this is the previous wallpaper target.
      private WindowState mPrevWallpaperTarget = null;
  
      private float mLastWallpaperX = -1;
      private float mLastWallpaperY = -1;
      private float mLastWallpaperXStep = -1;
      private float mLastWallpaperYStep = -1;
      private int mLastWallpaperDisplayOffsetX = Integer.MIN_VALUE;
      private int mLastWallpaperDisplayOffsetY = Integer.MIN_VALUE;
  
      // This is set when we are waiting for a wallpaper to tell us it is done
      // changing its scroll position.
      private WindowState mWaitingOnWallpaper;
  
      // The last time we had a timeout when waiting for a wallpaper.
      private long mLastWallpaperTimeoutTime;
      // We give a wallpaper up to 150ms to finish scrolling.
      private static final long WALLPAPER_TIMEOUT = 150;
      // Time we wait after a timeout before trying to wait again.
      private static final long WALLPAPER_TIMEOUT_RECOVERY = 10000;
  
      // Set to the wallpaper window we would like to hide once the transition animations are done.
      // This is useful in cases where we don't want the wallpaper to be hidden when the close app
      // is a wallpaper target and is done animating out, but the opening app isn't a wallpaper
      // target and isn't done animating in.
      WindowState mDeferredHideWallpaper = null;
  
      // We give a wallpaper up to 500ms to finish drawing before playing app transitions.
      private static final long WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION = 500;
      private static final int WALLPAPER_DRAW_NORMAL = 0;
      private static final int WALLPAPER_DRAW_PENDING = 1;
      private static final int WALLPAPER_DRAW_TIMEOUT = 2;
      private int mWallpaperDrawState = WALLPAPER_DRAW_NORMAL;
  
      /**
       * Temporary storage for taking a screenshot of the wallpaper.
       * @see #screenshotWallpaperLocked()
       */
      private WindowState mTmpTopWallpaper;
  
      private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult();
  
      private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> {
          final WindowAnimator winAnimator = mService.mAnimator;
          if ((w.mAttrs.type == TYPE_WALLPAPER)) {
              if (mFindResults.topWallpaper == null || mFindResults.resetTopWallpaper) {
                  mFindResults.setTopWallpaper(w);
                  mFindResults.resetTopWallpaper = false;
              }
              return false;
          }
  
          mFindResults.resetTopWallpaper = true;
          if (w.mAppToken != null && w.mAppToken.isHidden() && !w.mAppToken.isSelfAnimating()) {
  
              // If this window's app token is hidden and not animating, it is of no interest to us.
              if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping hidden and not animating token: " + w);
              return false;
          }
          if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": isOnScreen=" + w.isOnScreen()
                  + " mDrawState=" + w.mWinAnimator.mDrawState);
  
          if (w.mWillReplaceWindow && mWallpaperTarget == null
                  && !mFindResults.useTopWallpaperAsTarget) {
              // When we are replacing a window and there was wallpaper before replacement, we want to
              // keep the window until the new windows fully appear and can determine the visibility,
              // to avoid flickering.
              mFindResults.setUseTopWallpaperAsTarget(true);
          }
  
          final boolean keyguardGoingAwayWithWallpaper = (w.mAppToken != null
                  && w.mAppToken.isSelfAnimating()
                  && AppTransition.isKeyguardGoingAwayTransit(w.mAppToken.getTransit())
                  && (w.mAppToken.getTransitFlags()
                          & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0);
  
          boolean needsShowWhenLockedWallpaper = false;
          if ((w.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0
                  && mService.mPolicy.isKeyguardLocked()
                  && mService.mPolicy.isKeyguardOccluded()) {
              // The lowest show when locked window decides whether we need to put the wallpaper
              // behind.
              needsShowWhenLockedWallpaper = !isFullscreen(w.mAttrs)
                      || (w.mAppToken != null && !w.mAppToken.fillsParent());
          }
  
          if (keyguardGoingAwayWithWallpaper || needsShowWhenLockedWallpaper) {
              // Keep the wallpaper during Keyguard exit but also when it's needed for a
              // non-fullscreen show when locked activity.
              mFindResults.setUseTopWallpaperAsTarget(true);
          }
  
          final RecentsAnimationController recentsAnimationController =
                  mService.getRecentsAnimationController();
          final boolean animationWallpaper = w.mAppToken != null && w.mAppToken.getAnimation() != null
                  && w.mAppToken.getAnimation().getShowWallpaper();
          final boolean hasWallpaper = (w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0
                  || animationWallpaper;
          final boolean isRecentsTransitionTarget = (recentsAnimationController != null
                  && recentsAnimationController.isWallpaperVisible(w));
          if (isRecentsTransitionTarget) {
              if (DEBUG_WALLPAPER) Slog.v(TAG, "Found recents animation wallpaper target: " + w);
              mFindResults.setWallpaperTarget(w);
              return true;
          } else if (hasWallpaper && w.isOnScreen()
                  && (mWallpaperTarget == w || w.isDrawFinishedLw())) {
              if (DEBUG_WALLPAPER) Slog.v(TAG, "Found wallpaper target: " + w);
              mFindResults.setWallpaperTarget(w);
              if (w == mWallpaperTarget && w.isAnimating()) {
                  // The current wallpaper target is animating, so we'll look behind it for
                  // another possible target and figure out what is going on later.
                  if (DEBUG_WALLPAPER) Slog.v(TAG,
                          "Win " + w + ": token animating, looking behind.");
              }
              // Found a target! End search.
              return true;
          }
          return false;
      };
  
      WallpaperController(WindowManagerService service, DisplayContent displayContent) {
          mService = service;
          mDisplayContent = displayContent;
      }
  
      WindowState getWallpaperTarget() {
          return mWallpaperTarget;
      }
  
      boolean isWallpaperTarget(WindowState win) {
          return win == mWallpaperTarget;
      }
  
      boolean isBelowWallpaperTarget(WindowState win) {
          return mWallpaperTarget != null && mWallpaperTarget.mLayer >= win.mBaseLayer;
      }
  
      boolean isWallpaperVisible() {
          return isWallpaperVisible(mWallpaperTarget);
      }
  
      /**
       * Starts {@param a} on all wallpaper windows.
       */
      void startWallpaperAnimation(Animation a) {
          for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
              final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
              token.startAnimation(a);
          }
      }
  
      private final boolean isWallpaperVisible(WindowState wallpaperTarget) {
          final RecentsAnimationController recentsAnimationController =
                  mService.getRecentsAnimationController();
          boolean isAnimatingWithRecentsComponent = recentsAnimationController != null
                  && recentsAnimationController.isWallpaperVisible(wallpaperTarget);
          if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured="
                  + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??")
                  + " animating=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null)
                  ? wallpaperTarget.mAppToken.isSelfAnimating() : null)
                  + " prev=" + mPrevWallpaperTarget
                  + " recentsAnimationWallpaperVisible=" + isAnimatingWithRecentsComponent);
          return (wallpaperTarget != null
                  && (!wallpaperTarget.mObscured
                          || isAnimatingWithRecentsComponent
                          || (wallpaperTarget.mAppToken != null
                                  && wallpaperTarget.mAppToken.isSelfAnimating())))
                  || mPrevWallpaperTarget != null;
      }
  
      boolean isWallpaperTargetAnimating() {
          return mWallpaperTarget != null && mWallpaperTarget.isAnimating()
                  && (mWallpaperTarget.mAppToken == null
                          || !mWallpaperTarget.mAppToken.isWaitingForTransitionStart());
      }
  
      void updateWallpaperVisibility() {
          final boolean visible = isWallpaperVisible(mWallpaperTarget);
  
          for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
              final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
              token.updateWallpaperVisibility(visible);
          }
      }
  
      void hideDeferredWallpapersIfNeeded() {
          if (mDeferredHideWallpaper != null) {
              hideWallpapers(mDeferredHideWallpaper);
              mDeferredHideWallpaper = null;
          }
      }
  
      void hideWallpapers(final WindowState winGoingAway) {
          if (mWallpaperTarget != null
                  && (mWallpaperTarget != winGoingAway || mPrevWallpaperTarget != null)) {
              return;
          }
          if (mWallpaperTarget != null
                  && mWallpaperTarget.getDisplayContent().mAppTransition.isRunning()) {
              // Defer hiding the wallpaper when app transition is running until the animations
              // are done.
              mDeferredHideWallpaper = winGoingAway;
              return;
          }
  
          final boolean wasDeferred = (mDeferredHideWallpaper == winGoingAway);
          for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
              final WallpaperWindowToken token = mWallpaperTokens.get(i);
              token.hideWallpaperToken(wasDeferred, "hideWallpapers");
              if (DEBUG_WALLPAPER_LIGHT && !token.isHidden()) Slog.d(TAG, "Hiding wallpaper " + token
                      + " from " + winGoingAway + " target=" + mWallpaperTarget + " prev="
                      + mPrevWallpaperTarget + "\n" + Debug.getCallers(5, "  "));
          }
      }
  
      boolean updateWallpaperOffset(WindowState wallpaperWin, int dw, int dh, boolean sync) {
          int xOffset = 0;
          int yOffset = 0;
          boolean rawChanged = false;
          // Set the default wallpaper x-offset to either edge of the screen (depending on RTL), to
          // match the behavior of most Launchers
          float defaultWallpaperX = wallpaperWin.isRtl() ? 1f : 0f;
          float wpx = mLastWallpaperX >= 0 ? mLastWallpaperX : defaultWallpaperX;
          float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f;
          int availw = wallpaperWin.getFrameLw().right - wallpaperWin.getFrameLw().left - dw;
          int offset = availw > 0 ? -(int)(availw * wpx + .5f) : 0;
          if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
              offset += mLastWallpaperDisplayOffsetX;
          }
          xOffset = offset;
  
          if (wallpaperWin.mWallpaperX != wpx || wallpaperWin.mWallpaperXStep != wpxs) {
              wallpaperWin.mWallpaperX = wpx;
              wallpaperWin.mWallpaperXStep = wpxs;
              rawChanged = true;
          }
  
          float wpy = mLastWallpaperY >= 0 ? mLastWallpaperY : 0.5f;
          float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f;
          int availh = wallpaperWin.getFrameLw().bottom - wallpaperWin.getFrameLw().top - dh;
          offset = availh > 0 ? -(int)(availh * wpy + .5f) : 0;
          if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
              offset += mLastWallpaperDisplayOffsetY;
          }
          yOffset = offset;
  
          if (wallpaperWin.mWallpaperY != wpy || wallpaperWin.mWallpaperYStep != wpys) {
              wallpaperWin.mWallpaperY = wpy;
              wallpaperWin.mWallpaperYStep = wpys;
              rawChanged = true;
          }
  
          boolean changed = wallpaperWin.mWinAnimator.setWallpaperOffset(xOffset, yOffset);
  
          if (rawChanged && (wallpaperWin.mAttrs.privateFlags &
                  WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS) != 0) {
              try {
                  if (DEBUG_WALLPAPER) Slog.v(TAG, "Report new wp offset "
                          + wallpaperWin + " x=" + wallpaperWin.mWallpaperX
                          + " y=" + wallpaperWin.mWallpaperY);
                  if (sync) {
                      mWaitingOnWallpaper = wallpaperWin;
                  }
                  wallpaperWin.mClient.dispatchWallpaperOffsets(
                          wallpaperWin.mWallpaperX, wallpaperWin.mWallpaperY,
                          wallpaperWin.mWallpaperXStep, wallpaperWin.mWallpaperYStep, sync);
                  if (sync) {
                      if (mWaitingOnWallpaper != null) {
                          long start = SystemClock.uptimeMillis();
                          if ((mLastWallpaperTimeoutTime + WALLPAPER_TIMEOUT_RECOVERY)
                                  < start) {
                              try {
                                  if (DEBUG_WALLPAPER) Slog.v(TAG,
                                          "Waiting for offset complete...");
                                  mService.mGlobalLock.wait(WALLPAPER_TIMEOUT);
                              } catch (InterruptedException e) {
                              }
                              if (DEBUG_WALLPAPER) Slog.v(TAG, "Offset complete!");
                              if ((start + WALLPAPER_TIMEOUT) < SystemClock.uptimeMillis()) {
                                  Slog.i(TAG, "Timeout waiting for wallpaper to offset: "
                                          + wallpaperWin);
                                  mLastWallpaperTimeoutTime = start;
                              }
                          }
                          mWaitingOnWallpaper = null;
                      }
                  }
              } catch (RemoteException e) {
              }
          }
  
          return changed;
      }
  
      void setWindowWallpaperPosition(
              WindowState window, float x, float y, float xStep, float yStep) {
          if (window.mWallpaperX != x || window.mWallpaperY != y)  {
              window.mWallpaperX = x;
              window.mWallpaperY = y;
              window.mWallpaperXStep = xStep;
              window.mWallpaperYStep = yStep;
              updateWallpaperOffsetLocked(window, true);
          }
      }
  
      void setWindowWallpaperDisplayOffset(WindowState window, int x, int y) {
          if (window.mWallpaperDisplayOffsetX != x || window.mWallpaperDisplayOffsetY != y)  {
              window.mWallpaperDisplayOffsetX = x;
              window.mWallpaperDisplayOffsetY = y;
              updateWallpaperOffsetLocked(window, true);
          }
      }
  
      Bundle sendWindowWallpaperCommand(
              WindowState window, String action, int x, int y, int z, Bundle extras, boolean sync) {
          if (window == mWallpaperTarget || window == mPrevWallpaperTarget) {
              boolean doWait = sync;
              for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
                  final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
                  token.sendWindowWallpaperCommand(action, x, y, z, extras, sync);
              }
  
              if (doWait) {
                  // TODO: Need to wait for result.
              }
          }
  
          return null;
      }
  
      private void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) {
          final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
          final int dw = displayInfo.logicalWidth;
          final int dh = displayInfo.logicalHeight;
  
          WindowState target = mWallpaperTarget;
          if (target != null) {
              if (target.mWallpaperX >= 0) {
                  mLastWallpaperX = target.mWallpaperX;
              } else if (changingTarget.mWallpaperX >= 0) {
                  mLastWallpaperX = changingTarget.mWallpaperX;
              }
              if (target.mWallpaperY >= 0) {
                  mLastWallpaperY = target.mWallpaperY;
              } else if (changingTarget.mWallpaperY >= 0) {
                  mLastWallpaperY = changingTarget.mWallpaperY;
              }
              if (target.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
                  mLastWallpaperDisplayOffsetX = target.mWallpaperDisplayOffsetX;
              } else if (changingTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
                  mLastWallpaperDisplayOffsetX = changingTarget.mWallpaperDisplayOffsetX;
              }
              if (target.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
                  mLastWallpaperDisplayOffsetY = target.mWallpaperDisplayOffsetY;
              } else if (changingTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
                  mLastWallpaperDisplayOffsetY = changingTarget.mWallpaperDisplayOffsetY;
              }
              if (target.mWallpaperXStep >= 0) {
                  mLastWallpaperXStep = target.mWallpaperXStep;
              } else if (changingTarget.mWallpaperXStep >= 0) {
                  mLastWallpaperXStep = changingTarget.mWallpaperXStep;
              }
              if (target.mWallpaperYStep >= 0) {
                  mLastWallpaperYStep = target.mWallpaperYStep;
              } else if (changingTarget.mWallpaperYStep >= 0) {
                  mLastWallpaperYStep = changingTarget.mWallpaperYStep;
              }
          }
  
          for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
              mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(dw, dh, sync);
          }
      }
  
      void clearLastWallpaperTimeoutTime() {
          mLastWallpaperTimeoutTime = 0;
      }
  
      void wallpaperCommandComplete(IBinder window) {
          if (mWaitingOnWallpaper != null &&
                  mWaitingOnWallpaper.mClient.asBinder() == window) {
              mWaitingOnWallpaper = null;
              mService.mGlobalLock.notifyAll();
          }
      }
  
      void wallpaperOffsetsComplete(IBinder window) {
          if (mWaitingOnWallpaper != null &&
                  mWaitingOnWallpaper.mClient.asBinder() == window) {
              mWaitingOnWallpaper = null;
              mService.mGlobalLock.notifyAll();
          }
      }
  
      private void findWallpaperTarget() {
          mFindResults.reset();
          if (mDisplayContent.isStackVisible(WINDOWING_MODE_FREEFORM)) {
              // In freeform mode we set the wallpaper as its own target, so we don't need an
              // additional window to make it visible.
              mFindResults.setUseTopWallpaperAsTarget(true);
          }
  
          mDisplayContent.forAllWindows(mFindWallpaperTargetFunction, true /* traverseTopToBottom */);
  
          if (mFindResults.wallpaperTarget == null && mFindResults.useTopWallpaperAsTarget) {
              mFindResults.setWallpaperTarget(mFindResults.topWallpaper);
          }
      }
  
      private boolean isFullscreen(WindowManager.LayoutParams attrs) {
          return attrs.x == 0 && attrs.y == 0
                  && attrs.width == MATCH_PARENT && attrs.height == MATCH_PARENT;
      }
  
      /** Updates the target wallpaper if needed and returns true if an update happened. */
      private void updateWallpaperWindowsTarget(FindWallpaperTargetResult result) {
  
          WindowState wallpaperTarget = result.wallpaperTarget;
  
          if (mWallpaperTarget == wallpaperTarget
                  || (mPrevWallpaperTarget != null && mPrevWallpaperTarget == wallpaperTarget)) {
  
              if (mPrevWallpaperTarget == null) {
                  return;
              }
  
              // Is it time to stop animating?
              if (!mPrevWallpaperTarget.isAnimatingLw()) {
                  if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "No longer animating wallpaper targets!");
                  mPrevWallpaperTarget = null;
                  mWallpaperTarget = wallpaperTarget;
              }
              return;
          }
  
          if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
                  "New wallpaper target: " + wallpaperTarget + " prevTarget: " + mWallpaperTarget);
  
          mPrevWallpaperTarget = null;
  
          final WindowState prevWallpaperTarget = mWallpaperTarget;
          mWallpaperTarget = wallpaperTarget;
  
          if (wallpaperTarget == null || prevWallpaperTarget == null) {
              return;
          }
  
          // Now what is happening...  if the current and new targets are animating,
          // then we are in our super special mode!
          boolean oldAnim = prevWallpaperTarget.isAnimatingLw();
          boolean foundAnim = wallpaperTarget.isAnimatingLw();
          if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
                  "New animation: " + foundAnim + " old animation: " + oldAnim);
  
          if (!foundAnim || !oldAnim) {
              return;
          }
  
          if (mDisplayContent.getWindow(w -> w == prevWallpaperTarget) == null) {
              return;
          }
  
          final boolean newTargetHidden = wallpaperTarget.mAppToken != null
                  && wallpaperTarget.mAppToken.hiddenRequested;
          final boolean oldTargetHidden = prevWallpaperTarget.mAppToken != null
                  && prevWallpaperTarget.mAppToken.hiddenRequested;
  
          if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Animating wallpapers:" + " old: "
                  + prevWallpaperTarget + " hidden=" + oldTargetHidden + " new: " + wallpaperTarget
                  + " hidden=" + newTargetHidden);
  
          mPrevWallpaperTarget = prevWallpaperTarget;
  
          if (newTargetHidden && !oldTargetHidden) {
              if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Old wallpaper still the target.");
              // Use the old target if new target is hidden but old target
              // is not. If they're both hidden, still use the new target.
              mWallpaperTarget = prevWallpaperTarget;
          } else if (newTargetHidden == oldTargetHidden
                  && !mDisplayContent.mOpeningApps.contains(wallpaperTarget.mAppToken)
                  && (mDisplayContent.mOpeningApps.contains(prevWallpaperTarget.mAppToken)
                  || mDisplayContent.mClosingApps.contains(prevWallpaperTarget.mAppToken))) {
              // If they're both hidden (or both not hidden), prefer the one that's currently in
              // opening or closing app list, this allows transition selection logic to better
              // determine the wallpaper status of opening/closing apps.
              mWallpaperTarget = prevWallpaperTarget;
          }
  
          result.setWallpaperTarget(wallpaperTarget);
      }
  
      private void updateWallpaperTokens(boolean visible) {
          for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
              final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
              token.updateWallpaperWindows(visible);
              token.getDisplayContent().assignWindowLayers(false);
          }
      }
  
      void adjustWallpaperWindows() {
          mDisplayContent.mWallpaperMayChange = false;
  
          // First find top-most window that has asked to be on top of the wallpaper;
          // all wallpapers go behind it.
          findWallpaperTarget();
          updateWallpaperWindowsTarget(mFindResults);
  
          // The window is visible to the compositor...but is it visible to the user?
          // That is what the wallpaper cares about.
          final boolean visible = mWallpaperTarget != null && isWallpaperVisible(mWallpaperTarget);
          if (DEBUG_WALLPAPER) {
              Slog.v(TAG, "Wallpaper visibility: " + visible + " at display "
                      + mDisplayContent.getDisplayId());
          }
  
          if (visible) {
              if (mWallpaperTarget.mWallpaperX >= 0) {
                  mLastWallpaperX = mWallpaperTarget.mWallpaperX;
                  mLastWallpaperXStep = mWallpaperTarget.mWallpaperXStep;
              }
              if (mWallpaperTarget.mWallpaperY >= 0) {
                  mLastWallpaperY = mWallpaperTarget.mWallpaperY;
                  mLastWallpaperYStep = mWallpaperTarget.mWallpaperYStep;
              }
              if (mWallpaperTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
                  mLastWallpaperDisplayOffsetX = mWallpaperTarget.mWallpaperDisplayOffsetX;
              }
              if (mWallpaperTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
                  mLastWallpaperDisplayOffsetY = mWallpaperTarget.mWallpaperDisplayOffsetY;
              }
          }
  
          updateWallpaperTokens(visible);
  
          if (DEBUG_WALLPAPER_LIGHT)  Slog.d(TAG, "New wallpaper: target=" + mWallpaperTarget
                  + " prev=" + mPrevWallpaperTarget);
      }
  
      boolean processWallpaperDrawPendingTimeout() {
          if (mWallpaperDrawState == WALLPAPER_DRAW_PENDING) {
              mWallpaperDrawState = WALLPAPER_DRAW_TIMEOUT;
              if (DEBUG_APP_TRANSITIONS || DEBUG_WALLPAPER) Slog.v(TAG,
                      "*** WALLPAPER DRAW TIMEOUT");
  
              // If there was a pending recents animation, start the animation anyways (it's better
              // to not see the wallpaper than for the animation to not start)
              if (mService.getRecentsAnimationController() != null) {
                  mService.getRecentsAnimationController().startAnimation();
              }
              return true;
          }
          return false;
      }
  
      boolean wallpaperTransitionReady() {
          boolean transitionReady = true;
          boolean wallpaperReady = true;
          for (int curTokenIndex = mWallpaperTokens.size() - 1;
                  curTokenIndex >= 0 && wallpaperReady; curTokenIndex--) {
              final WallpaperWindowToken token = mWallpaperTokens.get(curTokenIndex);
              if (token.hasVisibleNotDrawnWallpaper()) {
                  // We've told this wallpaper to be visible, but it is not drawn yet
                  wallpaperReady = false;
                  if (mWallpaperDrawState != WALLPAPER_DRAW_TIMEOUT) {
                      // wait for this wallpaper until it is drawn or timeout
                      transitionReady = false;
                  }
                  if (mWallpaperDrawState == WALLPAPER_DRAW_NORMAL) {
                      mWallpaperDrawState = WALLPAPER_DRAW_PENDING;
                      mService.mH.removeMessages(WALLPAPER_DRAW_PENDING_TIMEOUT, this);
                      mService.mH.sendMessageDelayed(
                                  mService.mH.obtainMessage(WALLPAPER_DRAW_PENDING_TIMEOUT, this),
                                  WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION);
  
                  }
                  if (DEBUG_APP_TRANSITIONS || DEBUG_WALLPAPER) Slog.v(TAG,
                          "Wallpaper should be visible but has not been drawn yet. " +
                                  "mWallpaperDrawState=" + mWallpaperDrawState);
                  break;
              }
          }
          if (wallpaperReady) {
              mWallpaperDrawState = WALLPAPER_DRAW_NORMAL;
              mService.mH.removeMessages(WALLPAPER_DRAW_PENDING_TIMEOUT, this);
          }
  
          return transitionReady;
      }
  
      /**
       * Adjusts the wallpaper windows if the input display has a pending wallpaper layout or one of
       * the opening apps should be a wallpaper target.
       */
      void adjustWallpaperWindowsForAppTransitionIfNeeded(ArraySet<AppWindowToken> openingApps,
              ArraySet<AppWindowToken> changingApps) {
          boolean adjust = false;
          if ((mDisplayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
              adjust = true;
          } else {
              for (int i = openingApps.size() - 1; i >= 0; --i) {
                  final AppWindowToken token = openingApps.valueAt(i);
                  if (token.windowsCanBeWallpaperTarget()) {
                      adjust = true;
                      break;
                  }
              }
              if (!adjust) {
                  for (int i = changingApps.size() - 1; i >= 0; --i) {
                      final AppWindowToken token = changingApps.valueAt(i);
                      if (token.windowsCanBeWallpaperTarget()) {
                          adjust = true;
                          break;
                      }
                  }
              }
          }
  
          if (adjust) {
              adjustWallpaperWindows();
          }
      }
  
      void addWallpaperToken(WallpaperWindowToken token) {
          mWallpaperTokens.add(token);
      }
  
      void removeWallpaperToken(WallpaperWindowToken token) {
          mWallpaperTokens.remove(token);
      }
  
  
      @VisibleForTesting
      boolean canScreenshotWallpaper() {
          return canScreenshotWallpaper(getTopVisibleWallpaper());
      }
  
      private boolean canScreenshotWallpaper(WindowState wallpaperWindowState) {
          if (!mService.mPolicy.isScreenOn()) {
              if (DEBUG_SCREENSHOT) {
                  Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
              }
              return false;
          }
  
          if (wallpaperWindowState == null) {
              if (DEBUG_SCREENSHOT) {
                  Slog.i(TAG_WM, "No visible wallpaper to screenshot");
              }
              return false;
          }
          return true;
      }
  
      /**
       * Take a screenshot of the wallpaper if it's visible.
       *
       * @return Bitmap of the wallpaper
       */
      Bitmap screenshotWallpaperLocked() {
          final WindowState wallpaperWindowState = getTopVisibleWallpaper();
          if (!canScreenshotWallpaper(wallpaperWindowState)) {
              return null;
          }
  
          final Rect bounds = wallpaperWindowState.getBounds();
          bounds.offsetTo(0, 0);
  
          SurfaceControl.ScreenshotGraphicBuffer wallpaperBuffer = SurfaceControl.captureLayers(
                  wallpaperWindowState.getSurfaceControl().getHandle(), bounds, 1 /* frameScale */);
  
          if (wallpaperBuffer == null) {
              Slog.w(TAG_WM, "Failed to screenshot wallpaper");
              return null;
          }
          return Bitmap.wrapHardwareBuffer(
                  wallpaperBuffer.getGraphicBuffer(), wallpaperBuffer.getColorSpace());
      }
  
      private WindowState getTopVisibleWallpaper() {
          mTmpTopWallpaper = null;
  
          for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
              final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
              token.forAllWindows(w -> {
                  final WindowStateAnimator winAnim = w.mWinAnimator;
                  if (winAnim != null && winAnim.getShown() && winAnim.mLastAlpha > 0f) {
                      mTmpTopWallpaper = w;
                      return true;
                  }
                  return false;
              }, true /* traverseTopToBottom */);
          }
  
          return mTmpTopWallpaper;
      }
  
      void dump(PrintWriter pw, String prefix) {
          pw.print(prefix); pw.print("displayId="); pw.println(mDisplayContent.getDisplayId());
          pw.print(prefix); pw.print("mWallpaperTarget="); pw.println(mWallpaperTarget);
          if (mPrevWallpaperTarget != null) {
              pw.print(prefix); pw.print("mPrevWallpaperTarget="); pw.println(mPrevWallpaperTarget);
          }
          pw.print(prefix); pw.print("mLastWallpaperX="); pw.print(mLastWallpaperX);
          pw.print(" mLastWallpaperY="); pw.println(mLastWallpaperY);
          if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE
                  || mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
              pw.print(prefix);
              pw.print("mLastWallpaperDisplayOffsetX="); pw.print(mLastWallpaperDisplayOffsetX);
              pw.print(" mLastWallpaperDisplayOffsetY="); pw.println(mLastWallpaperDisplayOffsetY);
          }
      }
  
      /** Helper class for storing the results of a wallpaper target find operation. */
      final private static class FindWallpaperTargetResult {
          WindowState topWallpaper = null;
          boolean useTopWallpaperAsTarget = false;
          WindowState wallpaperTarget = null;
          boolean resetTopWallpaper = false;
  
          void setTopWallpaper(WindowState win) {
              topWallpaper = win;
          }
  
          void setWallpaperTarget(WindowState win) {
              wallpaperTarget = win;
          }
  
          void setUseTopWallpaperAsTarget(boolean topWallpaperAsTarget) {
              useTopWallpaperAsTarget = topWallpaperAsTarget;
          }
  
          void reset() {
              topWallpaper = null;
              wallpaperTarget = null;
              useTopWallpaperAsTarget = false;
              resetTopWallpaper = false;
          }
      }
  }
  